New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 728294 link

Starred by 3 users

Issue metadata

Status: Archived
Owner:
Closed: Apr 2018
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: ----
Pri: 3
Type: Feature

Blocked on:
issue 731667



Sign in to add a comment

Please silence play() Promise errors

Project Member Reported by joeyparrish@chromium.org, May 31 2017

Issue description

Chrome Version: 59.0.3071.71 (Offizieller Build) beta (64-Bit)
Flash or HTML5?  HTML5
What steps will reproduce the problem?

I don't have solid repro steps, but I do occasionally see the problem detailed in this closed issue: https://bugs.chromium.org/p/chromium/issues/detail?id=593273

In particular, the error "Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause()."

As Mounir said in [1]: "If you are seeing an error but that your website otherwise behave exactly the same, this is not a bug in Chrome but in your website."

I understand the perspective that play() returns a Promise now, and all unhandled Promise rejections result in an error log.  However, I don't believe it's a bug for a website not to care about the results of play().  I also think it's pretty reasonable for websites that must work cross-browser to continue to treat play() as returning nothing.  Currently, only Firefox and Chrome return a Promise from play() [2].

This uncaught Promise error happens much less than it used to, but it does still happen sometimes.  Users of Shaka Player sometimes focus on that in bug reports [3], which is understandable, because the error message is scary.  Given that the unhandled Promise rejection is actually completely harmless, I think this error message is doing more harm than good.

I opened an issue on Shaka Player [4] to polyfill play() to handle all rejections with a no-op, effectively silencing the error and creating backward compatibility.

It would be ideal if the browser would do this for us, in the name of backward compatibility.  Even when Shaka is "handling" these "errors", the misleading and unhelpful nature of the errors affects all developers of media applications, so I believe this should be fixed at a platform level.

This is what a JS polyfill for this would look like, for reference:

var originalPlay = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function() {
  var p = originalPlay.apply(this, arguments);
  p.catch(function(){});
  return p;
};

This would not prevent an application from handling the rejection, since it returns the original Promise.  But by attaching a no-op handler, there will never be a misleading error message delivered to apps who shouldn't have to care about this enhancement to the web platform.

I think the ideal solution would be to have the platform do something equivalent to the above catch statement in native code.  In the mean time, we'll do it in JavaScript.  Thanks for listening, and please let me know what you think.

[1]: https://bugs.chromium.org/p/chromium/issues/detail?id=593273#c100 
[2]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play#Browser_compatibility
[3]: https://github.com/google/shaka-player/issues/807#issue-229051849
[4]: https://github.com/google/shaka-player/issues/836
 
Cc: fbeaufort@chromium.org
+fbeaufort@

Would it help if we https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play#Browser_compatibility had a section about how to handle the promise rejection and the error message would link to it?
It might, but I still think it would be best not to issue messages that tell developers "you should care about this thing you don't care about".  Especially when the severity is "error".
Cc: domenic@chromium.org
+domenic@

To be clear, it's not on purpose that `play()` rejection ends up in an error. Unhandled promises will log errors and in this case, if `play()` is called and playback could not occur, it will reject the promise. As far as API design goes, I think that makes sense as it would be confusing it the promise was fulfilled without the playback happening but maybe `play()` rejection should have been more conservative.
Indeed. You asked it to play, by calling play(), and it failed to do so---that's an error. In synchronous code, that would be a thrown exception; in the web platform, it's a rejected promise.

It sounds like the real pain here is that not all browsers have implemented promise-returning play(). I'd suggest working around that by monkey-patching play() in such browsers to always return a promise; a version of that would be 

var originalPlay = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function() {
  var p = originalPlay.apply(this, arguments);
  return Promise.resolve(p);
};

This will not silence the errors for browsers (like Chrome) that are actually able to notify you about a failure to play, but will allow all client code to easily state whether it wants to catch such failures to play, or ignore them:

el.play().catch(e => {
  // Could not play. Maybe log an error, alert the user.
  // Or just ignore it, like a try { } catch (e) { /* do nothing */ }
});
While in general rejecting a promise should throw an exception, calling pause() after play() semantically cancels the play request from the pages point of view. I agree that these messages are confusing while happen often as Chrome got lots of bugs filed by developers after the promise play() launched. I'm not sure if they learned how to use play() or just learned that it's WAI and stopped filing bugs.
I agree that the Promise should be rejected if it fails to play.  I do not agree with the unhandled rejection resulting in an error message.

I understand that this error message is generically issued for all unhandled rejections in all Promises.  I think it would be helpful if this Promise had an error handler attached at the platform level so that the confusing and unhelpful message is silenced.

My suggestion would not stop play-Promise-aware applications from handling the Promise rejection.  It would only silence the console error message from unhandled rejections for other apps.

Does that make sense?
> I think it would be helpful if this Promise had an error handler attached at the platform level so that the confusing and unhelpful message is silenced.

Sorry, no, that does not make sense. We're not going to decide for the developer that they don't care about errors.
Quickest repro in Shaka Player:

1. Open this page: https://v2-1-2-dot-shaka-player-demo.appspot.com/demo/#asset=//storage.googleapis.com/shaka-demo-assets/heliocentrism/heliocentrism.mpd
2. Open the JS console
3. Click "Load"
4. Play this clip to the end (takes about 6 seconds)
5. Select any other clip from the assets list
6. Click "Load"

You will see in the console:  "Uncaught (in promise) DOMException: The play() request was interrupted by a new load request."

This is really unhelpful, and the app is still working correctly.  I believe these kinds of errors should be silenced.  Applications who care can still catch the Promise rejection and react appropriately.  For everyone else, this is noise.
We have now landed a polyfill for Shaka Player [1] to silence these errors in JavaScript.  I still believe that the browser should silence these errors for us, for the benefit of all developers of media-related web apps.  Thank you for your consideration.

[1] https://github.com/google/shaka-player/commit/53220b5acb6192f783a4a5e1b860393c45adba6a
Joey, do you know which call to play() was interrupted? In the example page, their should have been only one call to play(), that's after "LOAD" was pressed. Maybe a second one after LOAD was pressed the second time. Neither of those should have been interrupted.
Seems no third play call fired:

v=document.querySelector('video')
v.onplay = function() { console.log('PLAYING!'); }

<click load>

VM331:1 PLAYING!

<wait for clip to end, click load again>

player.js:1606 Uncaught (in promise) DOMException: The play() request was interrupted by a new load request.
shaka.Player.destroyStreaming_ @ player.js:1606
shaka.Player.resetStreaming_ @ player.js:1650
(anonymous) @ player.js:983
VM331:1 PLAYING!
Seems like promise wasn't checked even though play succeeded, then the next load cancels it regardless of whether playback finished? That seems like a bug.
I see 'load' called when the second video is selected and I press LOAD - which happens after the second play() command:

var oldPlay = HTMLMediaElement.prototype.play;
undefined
var oldLoad = HTMLMediaElement.prototype.load;
undefined
HTMLMediaElement.prototype.play = function() { console.log('PLAY'); var p = oldPlay.apply(this, arguments); p.then(function() { console.log('RESOLVED'); }, function() { console.log('REJECTED'); }); return p };
function () { console.log('PLAY'); var p = oldPlay.apply(this, arguments); p.then(function() { console.log('RESOLVED'); }, function() { console.log('REJECTED'); }); return p }
HTMLMediaElement.prototype.load = function() { console.log('LOAD'); oldLoad.apply(this, arguments); };
function () { console.log('LOAD'); oldLoad.apply(this, arguments); }
VM1060:1 PLAY
VM1060:1 RESOLVED
segment_index.js:231 The last segment should not end before the end of the Period. shaka.media.SegmentReference {position: 14, startTime: 56.014, endTime: 59.22, startByte: 746159, getUris: function…}
shaka.media.SegmentIndex.fit @ segment_index.js:231
(anonymous) @ segment_base.js:166
VM1060:1 PLAY
VM1240:1 LOAD
VM1060:1 REJECTED
Your JS-fu is superior to mine; nice :) Does look like the js is calling play again on the old element then hitting load.
Indeed, there is no third call to play(). This is my current understanding of the situation:
- New source is picked, shakaDemo.load() is called
- it calls `player.load()`
-   when the player isn't destroyed, it asynchronously calls resetStreaming()
-   which calls destroyStreaming() and finally calls load() on the video element
- after player.load() is called, video.play() is called

Because of the asynchronous steps that only happen when there was a player, the play() will be immediately cancelled by the call to load().

Calling `load()` on a media element that was asked to play will reset the element entirely. In my opinion, Shaka shouldn't be doing this as it is wasting resources. Joey, WDYT?
I added breakpoints to the overridden play() and load() in the demo:

/** Load the selected asset. */
shakaDemo.load = function() {
  ...
  // Load the manifest.
  player.load(asset.manifestUri).then(function() {  // <-- this calls resetStreaming_ which does video_.removeAttribute(src); video_.load();
    ...
  }, function(reason) {
    ...
  });

  // While the manifest is being loaded in parallel, go ahead and ask the video
  // to play.  This can help with autoplay on Android, since Android requires
  // user interaction to play a video and this function is called from a click
  // event.  This seems to work only because Shaka Player has already created a
  // MediaSource object and set video.src.
  shakaDemo.video_.play(); // <-- this calls play() before the manifest is loaded and the src is removed; hence
}

Joey, any reason why play() is not called only after load() promise is fulfilled? IMO, calling it before load() resolves is too soon.
> Joey, any reason why play() is not called only after load() promise is 
> fulfilled? IMO, calling it before load() resolves is too soon.

I'm not Joey, but the comments you pasted explains it well :)

FWIW, calling `shaakeDemo.video_.load()` here would provide the same result but that's something for Shaka to decide.
If we wait until load() is fulfilled, we are no longer in a user interaction context.  The play() call would be ignored on Android.
What about my suggestion in comment 17?
Perhaps I misunderstood you.  Are you saying call video_.load() instead of video_.play()?
Yes, video_.load() will unlock the media element if called in the user interaction context.
Status: Assigned (was: Unconfirmed)
Blockedon: 731667
I've open  bug 731667  and will soon land a CL that will allow use to have numbers regarding the impact of this.
Also, I talked to Francois who will look into this and write something explaining why it happens. We will link it from the error message and see if that helps :)
Will video_.load() unlock the media element if called in the user interaction context in all browsers or is it a Chrome-only thing?

Following mlamouri@ advice, I've submitted a PR to Shaka Player demo at https://github.com/google/shaka-player/pull/874

I've tested it and it works great!
And here's the PR for the article explaining what is happening: https://github.com/google/WebFundamentals/pull/4688
Feel free to give feedback.
Can't login to my GitHub right now, but the play() has never been synchronous, it's just now there's a promise rejection that makes it clear that it's not.

You've always had to wait for played to be sure play() finishes.
Also, might want to include an example of what to do for non-promise returning browsers.
Here's the live preview of the article that is going to ship: https://pr-4688-dot-web-central.appspot.com/web/updates/2017/06/play-request-was-interrupted
lgtm, thanks!
https://codereview.chromium.org/2942833004 will add article link to play promises rejection error message.

Project Member

Comment 35 by bugdroid1@chromium.org, Jun 16 2017

The following revision refers to this bug:
  https://chromium.googlesource.com/chromium/src.git/+/804cd4b03e108b85d1cc71d251608b11b6f033f9

commit 804cd4b03e108b85d1cc71d251608b11b6f033f9
Author: beaufort.francois <beaufort.francois@gmail.com>
Date: Fri Jun 16 12:58:27 2017

Add article link to play promises rejection error message

BUG= 728294 

Review-Url: https://codereview.chromium.org/2942833004
Cr-Commit-Position: refs/heads/master@{#480025}

[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-networkState-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-duration-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/media/W3C/audio/events/event_pause_manual-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/media/W3C/audio/events/event_play_manual-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/media/W3C/audio/paused/paused_true_during_pause-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/media/W3C/video/events/event_pause_manual-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/media/W3C/video/events/event_play_manual-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/media/W3C/video/paused/paused_true_during_pause-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/media/media-play-promise.html
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/media/video-display-none-crash-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/LayoutTests/media/video-play-pause-events-expected.txt
[modify] https://crrev.com/804cd4b03e108b85d1cc71d251608b11b6f033f9/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp

The Shaka Player PR at https://github.com/google/shaka-player/pull/874 creates some kind of race condition that causes MSE failures.

In any case, Shaka Player aside, my original assertion was that this error log was unhelpful and caused confusion.  Since no one here supports my suggestion of silencing the message, and since an explanatory link has been added to the message, perhaps this is a good compromise.

If you feel that there is no more to be done, feel free to close the issue, and thank you for listening.
Components: Blink>Media>Autoplay
Status: Archived (was: Assigned)

Sign in to add a comment