Please silence play() Promise errors |
||||||
Issue descriptionChrome 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
,
May 31 2017
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".
,
May 31 2017
+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.
,
May 31 2017
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 */ }
});
,
May 31 2017
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.
,
May 31 2017
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?
,
May 31 2017
> 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.
,
Jun 5 2017
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.
,
Jun 5 2017
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
,
Jun 5 2017
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.
,
Jun 5 2017
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!
,
Jun 5 2017
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.
,
Jun 5 2017
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
,
Jun 5 2017
Your JS-fu is superior to mine; nice :) Does look like the js is calling play again on the old element then hitting load.
,
Jun 5 2017
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?
,
Jun 5 2017
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.
,
Jun 5 2017
> 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.
,
Jun 5 2017
If we wait until load() is fulfilled, we are no longer in a user interaction context. The play() call would be ignored on Android.
,
Jun 5 2017
What about my suggestion in comment 17?
,
Jun 5 2017
Perhaps I misunderstood you. Are you saying call video_.load() instead of video_.play()?
,
Jun 5 2017
Yes, video_.load() will unlock the media element if called in the user interaction context.
,
Jun 8 2017
,
Jun 9 2017
,
Jun 9 2017
I've open bug 731667 and will soon land a CL that will allow use to have numbers regarding the impact of this.
,
Jun 9 2017
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 :)
,
Jun 13 2017
Will video_.load() unlock the media element if called in the user interaction context in all browsers or is it a Chrome-only thing?
,
Jun 13 2017
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!
,
Jun 13 2017
And here's the PR for the article explaining what is happening: https://github.com/google/WebFundamentals/pull/4688 Feel free to give feedback.
,
Jun 13 2017
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.
,
Jun 13 2017
Also, might want to include an example of what to do for non-promise returning browsers.
,
Jun 15 2017
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
,
Jun 15 2017
lgtm, thanks!
,
Jun 16 2017
,
Jun 16 2017
https://codereview.chromium.org/2942833004 will add article link to play promises rejection error message.
,
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
,
Jun 16 2017
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.
,
Feb 8 2018
,
Apr 19 2018
|
||||||
►
Sign in to add a comment |
||||||
Comment 1 by mlamouri@chromium.org
, May 31 2017