View Sidebar

A Million Little Pieces Of My Mind

Step 6: Implement Pause/Resume

By: Paul S. Cilwa Viewed: 5/6/2024
Posted: 11/18/2017
Page Views: 997
Topics: #Computers #Programming #Projects #WebAudioAPI #JavaScript #MusicPlayer #Cross-fadingMusicPlayer #OrganicaAudio
In this step we'll add the ability to pause and resume the currently playing track.

Any music player needs the ability to pause and resume playback at any point, at the user's request. When I started researching how I might accomplish this, I found a lot of posts bemoaning the fact that it was so difficult, or impossible. However, those posts were years old. The Web Audio API has been undergoing improvements since its introduction; it now includes suspend() and resume() methods.

However, I feared that I would need to save the time the current track was paused, as most of those older posts suggested, and use it to restart the track at that offset when the user wished. And, since we will have two start() operations in the queue at the same time (the current track and the next one to be played), I was afraid I would need to also cancel the track that was waiting, and then do the calculations to figure when to re-schedule the start() for that track as well.

Luckily, the suspend() and resume() methods operate on the Audio Context, rather than on separate sound streams, such as our tracks. suspend() literally freezes the whole thing; resume() gets the whole kaboodle going again.

So: Our task becomes one of giving an additional ability to the "Play, Dammit!" button we have on our test bed web page: When the user clicks it the first time, in addition to starting the first track playing, we must change the text on the button to read, "Pause". When it is clicked subsequently, it should change back to "Resume".

Now, when one is designing a class library, one wants to built into it as few dependencies as possible. The code for changing the button text will be in the library; but the actual button is on the web page that references the library. We do not want to require a Play button to have any particular ID; but we have to know what that ID is, in order to work with it.

To this end, we'll add a single line of code to the OrganicaAudio constructor:

function OrganicaAudio()
	{
	console.log ('OrganicaAudio initializing...');
	
	var ContextClass = (window.AudioContext || 
		window.webkitAudioContext || 
		window.mozAudioContext || 
		window.oAudioContext || 
		window.msAudioContext);
	
	if (ContextClass) 
		{
		// Web Audio API is available.
		this.Context = new ContextClass();
		this.Playlist = [];
		console.log('OrganicaAudio: Hello');
		}
	else
		{
		// Trigger error??
		alert('OrganicaAudio: Unable to obtain Web Audio API context.');
		}
	
	this.PauseButtonID = "#PauseButton";
	}

Of course, I don't expect anyone to necessarily think that "PauseButton" is a good ID for a Play button or any other. But now that MyOrganicaAudio has such a property, the web page can specify which button to use.

<!DOCTYPE html>
<html>

<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Organica Audio Test</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src='OrganicaAudio.js'></script>
</head>

<body style='background-color: aquamarine; text-align: center'>
<h1>Let's test the Web Audio API!</h1>

<audio controls id="Song1">
<source src="COBOLin'.mp3">
Your browser does not support the audio element.
</audio>

<input id="Button1" type="button" value="Play, Dammit!"/>

<script>
$(document).ready(function()
	{
	MyOrganicaAudio.AddTrack("audio","#Song1");
	MyOrganicaAudio.AddTrack("Danny_Boy.mp3");

	MyOrganicaAudio.PauseButtonID = "#Button1";
	$("#Button1").click(MyOrganicaAudio.PauseResume);
	});
</script>

</body>
</html>

The handler for the button click event will be another member of MyOrganicaAudio:

OrganicaAudio.prototype.PauseResume = function()
	{
	if (MyOrganicaAudio.Context.state === 'suspended')
		{
		MyOrganicaAudio.Context.resume().then(function() 
			{
			$(MyOrganicaAudio.PauseButtonID).prop('value', 'Pause');
			});
		}
	else if (MyOrganicaAudio.Playlist[MyOrganicaAudio.PlaylistIndex].Playing)
		{
		MyOrganicaAudio.Context.suspend().then(function() 
			{
			$(MyOrganicaAudio.PauseButtonID).prop('value', 'Resume');
			});
		}
	else
		{
		$(MyOrganicaAudio.PauseButtonID).prop('value', 'Pause');
		MyOrganicaAudio.Play();
		}
	}

To keep the code simple, I had to arrange the logic out of the natural order in which the actual events will occur. The logic for the initial user click is the final option in the routine: Actually the initial playing of the track, which also changes the text of the button to "Pause".

In the middle is the logic of the second action, clicking the button (which now reads "Pause"). This invokes the audio context's suspend() method. This is an asynchronous operation but it returns almost instantly, at which point we again change the button text, this time to "Resume".

A side effect of this action is that the audio context state, which is normally running, shifts to suspended; and we can use that to detect the final occurrence to deal with (at the top of the function): The audio context is suspended, the button reads, "Resume", and we want to resume() the context. And, when we have done that, we switch the button text to "Pause" once again.

You can test your version of AudioTest.html now, or simply test the embedded version here: