8

A notable issue that's appearing as I'm building a simple audio streaming element in HTML5 is that the <audio> tag doesn't behave as one would expect in regards to playing and pausing a live audio stream.

I'm using the most basic HTML5 code for streaming the audio, an <audio> tag with controls, the source of which is a live stream.

Current outcome: When the stream is first played, it plays whatever is streaming as expected. When it's paused and played again, however, the audio resumes exactly where it left off when the stream was previously paused. The user is now listening to a delayed version of the stream. This occurrence isn't browser-specific.

Desired outcome: When the stream is paused, I want the stream to stop. When it is played again, I want it resume where the stream is currently at, not where it was when the user paused the stream.

Does anyone know of a way to make this audio stream resume properly after it's been paused?

Some failed attempts I've made to fix this issue:

  • Altering the currentTime of the audio element does nothing to streaming audio.
  • I've removed the audio element from the DOM when the user stops stream playback and added it back in when user resumes playback. The stream still continues where the user left off and worse yet downloads another copy of the stream behind the scenes.
  • I've added a random GET variable to the end of the stream URL every time the stream is played in an attempt to fool the browser into believing that it's playing a new stream. Playback still resumes where the user paused the stream.
Cole
  • 724
  • 1
  • 8
  • 24

4 Answers4

12

Best way to stop a stream, and then start it again seems to be removing the source and then calling load:

var sourceElement = document.querySelector("source");
var originalSourceUrl = sourceElement.getAttribute("src");
var audioElement = document.querySelector("audio");

function pause() {
    sourceElement.setAttribute("src", "");
    audioElement.pause();
    // settimeout, otherwise pause event is not raised normally
    setTimeout(function () { 
        audioElement.load(); // This stops the stream from downloading
    });
}

function play() {
    if (!sourceElement.getAttribute("src")) {
        sourceElement.setAttribute("src", originalSourceUrl);
        audioElement.load(); // This restarts the stream download
    }
    audioElement.play();
}
Ciantic
  • 6,064
  • 4
  • 54
  • 49
  • I've added this to my code and I can Play and Pause the stream one time. But when I try to start the stream playing a second time again, it will not start. Nothing happens when I click on Play. –  Oct 18 '20 at 15:27
3

Resetting the audio source and calling the load() method seems to be the simplest solution when you want to stop downloading from the stream.

Since it's a stream, the browser will stop downloading only when the user gets offline. Resetting is necessary to protect your users from burning through their cellular data or to avoid serving outdated content that the browser downloaded when they paused the audio.

Keep in mind though that when the source attribute is set to an empty string, like so audio.src = "", the audio source will instead be set to the page's hostname. If you use a random word, that word will be appended as a path.

So as seen below, setting audio.src ="", means that audio.src === "https://stacksnippets.net/js". Setting audio.src="meow" will make the source be audio.src === "https://stacksnippets.net/js/meow" instead. Thus the 3d paragraph is not visible.

const audio1 = document.getElementById('audio1');
const audio2 = document.getElementById('audio2');
document.getElementById('p1').innerHTML = `First audio source: ${audio1.src}`;
document.getElementById('p2').innerHTML = `Second audio source: ${audio2.src}`;

if (audio1.src === "") {
  document.getElementById('p3').innerHTML = "You can see me because the audio source is set to an empty string";
}
<audio id="audio1" src=""></audio>
<audio id="audio2" src="meow"></audio>

<p id="p1"></p>

<p id="p2"></p>

<p id="p3"></p>

Be aware of that behavior if you do rely on the audio's source at a given moment. Using the about URI scheme seems to trick it into behaving in a more reliable way. So using "about:" or "about:about", "about:blank", etc. will work fine.

const resetAudioSource = "about:"

const audio = document.getElementById('audio');
audio.src = resetAudioSource;

document.getElementById('p1').innerHTML = `Audio source: -- "${audio.src}"`;

// Somewhere else in your code...

if (audio.src === resetAudioSource){
  document.getElementById('p2').innerHTML = "You can see me because you reset the audio source."
}
<audio id="audio"></audio>
<p id="p1"></p>
<p id="p2"></p>

Resetting the audio.src and calling the .load() method will make the audio to try to load the new source. The above comes in handy if you want to show a spinner component while the audio is loading, but don't want to also show that component when you reset your audio source.

A working example can be found here: https://jsfiddle.net/v2xuczrq/

If the source is reset using a random word, then you might end up with the loader showing up when you also pause the audio, or until the onError event handler catches it. https://jsfiddle.net/jcwvue0s/

UPDATE: The strings "javascript:;" and "javascript:void(0)" can be used instead of the "about:" URI and this seems to work even better as it will also stop the console warnings caused by "about:".

0

Note: I'm leaving this answer for the sake of posterity, since it was the best solution I or anyone could come up with at the time for my issue. But I've since marked Ciantic's later idea as the best solution because it actually stops the stream downloading and playback like I originally wanted. Consider that solution instead of this one.


One solution I came up with while troubleshooting this issue was to ignore the play and pause functions on the audio element entirely and just set the volume property of the audio element to 0 when user wishes to stop playback and then set the volume property back to 1 when the user wishes to resume playback.

The JavaScript code for such a function would look much like this if you're using jQuery (also demonstrated in this fiddle):

/*
 * Play/Stop Live Audio Streams
 * "audioElement" should be a jQuery object
 */
function streamPlayStop(audioElement) {
  if (audioElement[0].paused) {
    audioElement[0].play();
  } else if (!audioElement[0].volume) {
    audioElement[0].volume = 1;
  } else {
    audioElement[0].volume = 0;
  }
}

I should caution that even though this achieves the desired functionality for stopping and resuming live audio streams, it isn't ideal because the stream, when stopped, is actually still playing and being downloaded in the background, using up bandwidth in the process.

However, this solution doesn't necessarily take up more bandwidth than just using .play() and .pause() on a streaming audio element. Simply using the audio tag with streaming audio uses up a great deal of bandwidth anyway, because once streaming audio is played, it continues to download the contents of the stream in the background when it is paused.


It should be noted that this method won't work in iOS because of purposefully built-in limitations for iPhones and iPads:

On iOS devices, the audio level is always under the user’s physical control. The volume property is not settable in JavaScript. Reading the volume property always returns 1.

If you choose to use the workaround in this answer, you'll need to create a fallback for iOS devices that uses the play() and pause() functions normally, or your interface will be unable to pause the stream.

Cole
  • 724
  • 1
  • 8
  • 24
  • See the other answer - it's a very valid concern that consuming a feed without the user wanting to listen to it is a waste of resources - both from a server's perspective, as from a user's perspective if they're mobile OR on a limited connection speed. – Mike Crantea Sep 12 '19 at 08:39
0

Tested @Ciantics code and it worked with some modifications, if you want to use multiple sources.

As the source is getting removed, the HTML audio player becomes inactive, so the source (URL) needs to be added directly after again to become active.

Also added an event listener at the end to connect the function when pausing:

var audioElement = document.querySelector("audio");
var sources = document.querySelector("audio").children;
var sourceList = [];
    
for(i=0;i<sources.length;i++){
    sourceList[i] = sources[i].getAttribute("src");
}
    
function pause() {
    for(i=0;i<sources.length;i++){
        sources[i].setAttribute("src", "");
    }
    audioElement.pause();
    // settimeout, otherwise pause event is not raised normally
    setTimeout(function () { 
        audioElement.load(); // This stops the stream from downloading
    });
    for(i=0;i<sources.length;i++){
        if (!sources[i].getAttribute("src")) {
            sources[i].setAttribute("src", sourceList[i]);
            audioElement.load(); // This restarts the stream download
        }
    }
}
    
audioElement.addEventListener("pause", pause); 
ahuemmer
  • 1,653
  • 9
  • 22
  • 29
Ahmadgbg
  • 13
  • 4