HTML5 video.play returning Pending promises

2019-06-21 19:36发布

问题:

I'm trying to determine whether the browser supports autoplay on load.

I'm using the following code, and it works fine on Android chrome, but for Desktop Chrome none of the lines in .catch or .then get executed. The promise seems to just return Pending promises ad infinitum.

Is this an actual Chrome bug or am I not understanding how Promises work here?

const promise = document.createElement('video').play();

if (promise instanceof Promise) {
	promise.catch((error) => {
		// Check if it is the right error
		if (error.name === 'NotAllowedError') {
			autoPlayAllowed = false;
		} else {
			throw error;
		}
	}).then(() => {
		if (autoPlayAllowed) {
			// Autoplay is allowed - continue with initialization
			console.log('autoplay allowed')
		} else {
			// Autoplay is not allowed - wait for the user to trigger the play button manually
			console.log('autoplay NOT allowed')
		}
	});

} else {
	// Unknown if allowed
	console.log('autoplay unknown')
}

Thanks!

回答1:

Which browsers Promisify MediaElement.play()?

The WHATWG spec has only recommended that MediaElement.play() be Promisified since a Feb 2016 pull request following on from an issue submitted on Jan 2016. It is connected to the recent decision to prevent MediaElements from autoplaying (except under special conditions, such as these, for iOS Safari).

The W3C spec still (as of Nov 2017) does not appear to mention that MediaElement.play() should return a Promise.

MediaElement.play() returns a Promise as of version:

  • Firefox: 53 – [Mozilla docs]; [Mozilla Bugzilla]

  • 'Chrome for Desktop', 'Chrome for Android', 'Android WebView': 50 – [Mozilla docs]; [Chrome platform status]

  • 'Opera', 'Opera for Android': 37 – [Chrome platform status]

  • iOS Safari: iOS 10 – [WebKit's new video policies for iOS]

  • Desktop Safari: Jun 2017, so maybe v10.1.1 – [Autoplay policy changes for macOS]

The status of this change is unknown for Edge (feel free to comment).

How can I test whether my browser supports it?

The Chrome team have released a test page to check whether your own browser supports Promisified MediaElement.play().

If supported, the logging box will say:

Attempting to play automatically...
The play() Promise fulfilled! Rock on!

Otherwise, it will only print the first line of those two.

How should I universally support MediaElement.play()?

Here are the best practices recommended by Jeff Posnick in a Google Developer blog post. Similar code is recommended in a WebKit blog post and by BitMovin, and they all derive from the original issue filing.

var playPromise = document.querySelector('video').play();

// In browsers that don’t yet support this functionality,
// playPromise won’t be defined.
if (playPromise !== undefined) {
  playPromise.then(function() {
    // Automatic playback started!
  }).catch(function(error) {
    // Automatic playback failed.
    // Show a UI element to let the user manually start playback.
  });
}

Basically: store the result of invoking MediaElement.play(), and only act on it as a Promise chain if the result turns out to be non-undefined (eg. of type object).



回答2:

I am not sure if this is a bug or not, but you need to set an src to your video before being able to fulfill or reject the play Promise.

Setting it to an empty string "" will throw an "no supported source was found" error, but since in your original code you thrown unrecognized errors, the then never fired. So you could either rely on this "not found error" to tell that the autoplay works, or don't throw the error to go to the then.

let autoPlayAllowed = true;
let v = document.createElement('video');
v.src = ""; // we need this

const promise = v.play();

if (promise instanceof Promise) {
  promise.catch(function(error) {
    console.log(error.message);
    // Check if it is the right error
    if (error.name === 'NotAllowedError') {
      autoPlayAllowed = false;
    } else {
      // Don't throw the error so that we get to the then
      // or throw it but set the autoPlayAllowed to true in here
    }
  }).then(function() {
    if (autoPlayAllowed) {
      // Autoplay is allowed - continue with initialization
      console.log('autoplay allowed')
    } else {
      // Autoplay is not allowed - wait for the user to trigger the play button manually
      console.log('autoplay NOT allowed')
    }
  });

} else {
  // Unknown if allowed
  // Note: you could fallback to simple event listeners in this case
  console.log('autoplay unknown')
}

Note that only WHATWG specs have included the promises in MediaElement.play(), W3C didn't. So you may want to fallback to simple event listeners, at least for the time being.