Search code examples
batch-filecmdvbscript

To control a certain MP object to alter volume settings while playing an audio file


I have this code in my .bat script.

( echo Set Sound = CreateObject("WMPlayer.OCX.7"^)
  echo Sound.URL = "music\sia.mp3"
  echo Sound.settings.volume = %volume%
  echo Sound.settings.setMode "loop", True
  echo Sound.Controls.play
  echo While Sound.playState ^<^> 1
  echo      WScript.Sleep 100
  echo Wend
)>sound22.vbs
start /min sound22.vbs
del *.vbs

It simply plays music\sia.mp3 in an infinite loop in the background.

Sometimes it fails to execute the sound22.vbs because it deletes it before the poor code could even execute it.

However, here are my main issues:

( echo Set Sound = CreateObject("WMPlayer.OCX.7"^)
  echo Sound.settings.volume = %volume2%
  echo While Sound.playState ^<^> 1
  echo      WScript.Sleep 100
  echo Wend
)>sound22.vbs
start /min sound22.vbs
del *.vbs

This code was supposed to, but doesn't, change the current volume to %volume2% without replaying the music.

  1. How can I do that?
  2. How can I control the WMPlayer object I created before?
  3. Even if I do, 2., will this second code be able to change the volume without replaying or pausing the music?
  4. Can I pause it using sound.controls.pause the same way and continue to play it again?

Solution

  • You can not control the previously created WMPlayer object by creating a new one, but you can control it by incorporating a mechanism which communicates with the created object.

    Since batch scripts have no standard means for interprocess communication, the only option will be left, is to use an intermediate file as the communication medium.
    Following is an standalone example code which demonstrate one way of doing it through batch script. It could be written differently but I've followed you design path for writing the code.

    It plays the specified music/sound file and prompts the user to enter a command to send to the player to control it.

    Implemented control command are: [0-100] for volume, mute, unmute, play, pause, replay, stop, open, quit

    :: MusicPlayerAndController.bat
    @echo off
    setlocal DisableDelayedExpansion
    
    set "PlayerMutex=BatchVbsSoundPlayer.mutex"
    if "%~1"=="/LaunchAsyncPipedVbsPlayer" (
      %= Asynchronous Background Player =%
      %= Invoking only one instance of background player in the current directory =%
    
      9>&2 2>nul ( 8>"%PlayerMutex%" 2>&9 (
        call :LaunchAsyncPipedVbsPlayer
        (call,) %= Masks all other errors except than failure to hold PlayerMutex =%
      )) && del "%PlayerMutex%" >nul 2>&1 || echo WARNING: Another instance of player is already running.
    
      exit /b
    )
    
    %= Asynchronous Controller =%
    cls
    title Batch/VBS Music Player And Controller
    set "PlayerCommandFile=VBSPlayerCommand.txt"
    set "VBScriptPlayerApp=MediaPlayer.vbs"
    
    :: Sample Music from Windows 7
    set "SoundFile=%PUBLIC%\Music\Sample Music\Sleep Away.mp3"
    if not exist "%SoundFile%" (
      set "SoundFile="
      echo Open a media file to start playing.
    )
    
    set "InitialVolume=30"
    set "PingPipe=pingpipe"
    (
      echo Dim Input : Dim Open
      echo Open = False
      echo Set Sound = CreateObject("WMPlayer.OCX.7"^)
      echo Sound.URL = "%SoundFile%"
      echo Sound.settings.volume = %InitialVolume%
      echo Sound.settings.setMode "loop", True
      echo Sound.Controls.play
      echo 'Give enough time for preparing new media, not relying on 'pingpipe' intervals
      echo WScript.Sleep 1000 
      echo Do Until WScript.StdIn.AtEndOfStream
      echo      Input = WScript.StdIn.ReadLine^(^)
      echo      If IsNumeric^(Input^) Then
      echo          Sound.settings.volume = Input
      echo      ElseIf Open = True Then
      echo          Open = False
      echo          Sound.Controls.stop
      echo          Sound.URL = Input
      echo          Sound.Controls.play
      echo          'Give enough time for preparing new media, not relying on 'pingpipe' intervals
      echo          WScript.Sleep 1000
      echo      Else
      echo          Select Case Trim^(LCase^(Input^)^)
      echo              Case "quit"
      echo                  Exit Do
      echo              Case "stop"
      echo                  Sound.Controls.stop
      echo              Case "pause"
      echo                  Sound.Controls.pause
      echo              Case "play"
      echo                  Sound.Controls.play
      echo              Case "replay"
      echo                  Sound.Controls.stop
      echo                  Sound.Controls.play
      echo              Case "mute"
      echo                  Sound.settings.mute = True
      echo              Case "unmute"
      echo                  Sound.settings.mute = False
      echo              Case "open"
      echo                  Open = True
      echo              Case "%PingPipe%"
      echo                  'Workaround for StdIn blokcing player from looping media
      echo                  WScript.Sleep 1
      echo          End Select
      echo      End If
      echo Loop
      echo Sound.Controls.stop
      echo Sound.close
    )>"%VBScriptPlayerApp%"
    
    :: Workaround for CMD %~0 bug
    call :getBatFullPath @f0
    :: Starting player
    start "" /B "%COMSPEC%" /D /C "%@f0%" /LaunchAsyncPipedVbsPlayer
    :: Give the player enough time to start
    timeout /t 1 /nobreak >nul
    del "%VBScriptPlayerApp%" >nul 2>&1
    
    
    set "PlayerCommand=NONE"
    set "QuitMsg=Player Closed."
    :: Controller Command Loop
    :PlayerCommandPrompt
    set "LastCommand=%PlayerCommand%"
    :PromptPreserveLastCommand
    :: Making sure player is alive
    del "%PlayerMutex%" >nul 2>&1
    if not exist "%PlayerMutex%" (
      set "QuitMsg=Player Closed unexpectedly."
      goto :Quit
    )
    echo,
    echo VbsPlayer Command Prompt
    echo Valid Commands:
    echo   [0-100] for volume, mute, unmute, play, pause, replay, stop, open, quit, cls (Clears Screen)
    echo,
    echo Last Command Sent: %LastCommand%
    echo,
    set "PlayerCommand="
    set /p "PlayerCommand=Enter Command to send to VBS Player>"
    if not defined PlayerCommand goto :PromptPreserveLastCommand
    if /i "%PlayerCommand%"=="cls" cls & goto :PromptPreserveLastCommand
    if /i "%PlayerCommand%"=="open" (
      setlocal EnableDelayedExpansion
      set "OK="
      set "NewMedia="
      set /p "NewMedia=Media File (Drag'n'Drop): "
      if defined NewMedia (
        set ^"NewMedia=!NewMedia:^"=!^"
        if exist "!NewMedia!" (
          (echo open&echo !NewMedia!)>"!PlayerCommandFile!"
          set "OK=1"
        )
      )
      if defined OK (
        endlocal
        goto :PlayerCommandPrompt
      ) else (
        endlocal
        goto :PromptPreserveLastCommand
      )
    )
    (echo %PlayerCommand%)>"%PlayerCommandFile%"
    if /i "%PlayerCommand%"=="quit" goto :Quit
    goto :PlayerCommandPrompt
    
    :Quit
    echo,
    echo %QuitMsg%
    pause
    exit /b
    
    :LaunchAsyncPipedVbsPlayer
    del /f "%PlayerCommandFile%" >nul 2>&1
    (
      (
        for /L %%# in (0,0,1) do @(
          type "%PlayerCommandFile%" && del "%PlayerCommandFile%" >&2
          timeout /t 1 /nobreak >&2
          %= This guarantees we will not loop infinitely when the player is closed =%
          %= Also used as a workaround for player 'loop music' to function correctly =%
          echo %PingPipe% 2>&1
        )
      )2>nul
    )|cscript //nologo "%VBScriptPlayerApp%"
    exit /b
    
    :getBatFullPath
    set "%~1=%~f0" & exit /b
    

    It reads the file by batch code and sends the data though an established pipe to the VBS code.

    The VBS code could simply read the file by itself without the need to use pipe, but then I had to embed a larger and more complex VBS code into the batch file with the echo commands which would be somewhat counterintuitive unless you decide to keep the VBS code separate from the batch code.

    Or alternatively the VBS portion can be converted to JS which enables you to use true BAT/JS hybrid code without the need to write the JS part to a separate file each time the script is executed.