Fix race condition in MediaElement.js (#685, #1160)

This commit is hopefully the final fix on Airsonic's side for #685.  It
also fixes #1160, which was caused by temporary workarounds introduced
in #1080 while we were looking for a solution.

The root cause of the issue is the fact that, when we go to the next
track in an Airsonic play queue, we change the media source in the
`ended` event.

In MEJS, this translates as the following two things:

* In Airsonic's 'ended' event, we change the media source (set the `src`
  attribute) and call the `load()` method, followed by the `play()`
  method.

* The 'ended' event was also used internally by the MEJS player, and
  one of these internal uses called the `pause()` method (presumably in
  order to make sure that playback was stopped on some media renderers).

Unfortunately, the order in which these events are called depends (in
all modern browsers) on the order in which they are registered.

In our case, the first one is registered inside the `<body>` tag, but
the second one is registered with `$(document).ready(...)`. This means
that the first event handler is called before the second.

This means that, in some cases (when we're unlucky, hence the seemingly
random nature of the bug), `pause()` is called after `load()`
but before the media has finished loading.

Apparently, this causes the `AbortError: The fetching process for the
media resource was aborted by the user agent at the user's request.`
message to appear (which indicates exactly what's described in the last
paragraph), and the playback of the next song is aborted.
master
François-Xavier Thomas 5 years ago
parent 3d8f3ed21d
commit 40ef5501ea
  1. 37
      airsonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp

@ -197,20 +197,16 @@
} }
function createMediaElementPlayer() { function createMediaElementPlayer() {
// Manually run MediaElement.js initialization.
var player = $('#audioPlayer').get(0); //
// Warning: Bugs will happen if MediaElement.js is not initialized when
// we modify the media elements (e.g. adding event handlers). Running
// MediaElement.js's automatic initialization does not guarantee that
// (it depends on when we call createMediaElementPlayer at load time).
$('#audioPlayer').mediaelementplayer();
// Once playback reaches the end, go to the next song, if any. // Once playback reaches the end, go to the next song, if any.
player.addEventListener("ended", onEnded); $('#audioPlayer').on("ended", onEnded);
// FIXME: Remove once https://github.com/mediaelement/mediaelement/issues/2650 is fixed.
//
// Once a media is loaded, we can start playback, but not before.
//
// Trying to start playback after a song has endred but before the
// 'canplay' event for the next song currently causes a race condition
// in the MediaElement.js player (4.2.10).
player.addEventListener("canplay", function() { player.play(); });
} }
function getPlayQueue() { function getPlayQueue() {
@ -678,19 +674,8 @@
player.currentTime = position || 0; player.currentTime = position || 0;
} }
// FIXME: Uncomment once https://github.com/mediaelement/mediaelement/issues/2650 is fixed. // Start playback immediately.
// player.play();
// Calling 'player.play()' at this point may work by chance, but it's
// not guaranteed in the current MediaElement.js player (4.2.10). See
// the 'createMediaElementPlayer()' function above.
//
// player.play();
// FIXME: Remove once https://github.com/mediaelement/mediaelement/issues/2650 is fixed.
//
// Instead, we're triggering a 'waiting' event so that the player shows
// a progress bar to indicate that it's loading the stream.
player.dispatchEvent(new Event("waiting"));
} }
function skip(index, position) { function skip(index, position) {
@ -851,7 +836,7 @@
<c:if test="${model.player.web}"> <c:if test="${model.player.web}">
<td> <td>
<div id="player" style="width:340px; height:40px"> <div id="player" style="width:340px; height:40px">
<audio id="audioPlayer" class="mejs__player" data-mejsoptions='{"alwaysShowControls": true, "enableKeyboard": false}' width="340px" height"40px" tabindex="-1" /> <audio id="audioPlayer" data-mejsoptions='{"alwaysShowControls": true, "enableKeyboard": false}' width="340px" height"40px" tabindex="-1" />
</div> </div>
<div id="castPlayer" style="display: none"> <div id="castPlayer" style="display: none">
<div style="float:left"> <div style="float:left">

Loading…
Cancel
Save