-
Notifications
You must be signed in to change notification settings - Fork 7.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Fix cases where the player reports that it is ready during source selection, but before tech selection. #4665
Conversation
src/js/player.js
Outdated
}); | ||
// Given a return value for a tech's `play()` method, silences "unhandled | ||
// promise from play" errors. | ||
const silencePromise = (value) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we probably want a promise helper file that contains isPromise
and silencePromise
, since we silence promises in a few places.
src/js/player.js
Outdated
// Finally, if the player is ready, but we don't have a source, wait for | ||
// one to be set. | ||
} else { | ||
this.ready(() => this.tech_.one('loadstart', () => silencePromise(this.play()))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will fail because the function gets called with the context of the player and not the tech as relied on previously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
D'oh.
src/js/player.js
Outdated
// we could not find an appropriate tech, but let's still notify the delegate that this is it | ||
// this needs a better comment about why this is needed | ||
// we could not find an appropriate tech, but let's still notify the | ||
// delegate that this is it this needs a better comment about why this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you revert this change or put a new line in? Otherwise the two statements run in together.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, it is hard to distinguish sentences without capitals and punctuation. ;)
f319644
to
c3c35a7
Compare
@@ -574,7 +574,7 @@ if (Html5.isSupported()) { | |||
player.addRemoteTextTrack(track, false); | |||
player.src({src: 'example.mp4', type: 'video/mp4'}); | |||
|
|||
this.clock.tick(1); | |||
this.clock.tick(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling src()
now results in two async functions being queued up in sequence. First, middleware.setSource
runs async. Then, its callback invokes this.triggerReady()
, which queues up a flush of the ready queue.
That's why this changed from 1
to 2
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im just wondering if this is going to break plugin tests, I don't really see an issue other that that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might.
loadTech_() { | ||
loadTechCalled++; | ||
} | ||
}; | ||
|
||
Player.prototype.src_.call(playerProxy); | ||
Player.prototype.src_.call(playerProxy, {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes to this test were necessary because src_()
no longer calls ready()
. Instead, it immediately loads the source into the tech because the only cases where this is possible is if the tech did not change; so, it should already be ready.
// middlewareSource is the source after it has been changed by middleware | ||
// Set the player to a non-ready state while middleware asynchronously | ||
// determines the final source. | ||
this.isReady_ = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the key fix: the player should not be "ready" while middleware is resolving a source and deciding if it needs to load a new tech or set the resolved source on the existing tech.
@@ -574,7 +574,7 @@ if (Html5.isSupported()) { | |||
player.addRemoteTextTrack(track, false); | |||
player.src({src: 'example.mp4', type: 'video/mp4'}); | |||
|
|||
this.clock.tick(1); | |||
this.clock.tick(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im just wondering if this is going to break plugin tests, I don't really see an issue other that that.
*/ | ||
play() { | ||
if (this.changingSrc_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having this function sometimes return a promise just sucks, not really something we can/should do about it in this PR though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dunno. I mean, it does model the native play
method. The alternative would be to include a Promise
polyfill.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think making a Promise is what we'd want to do eventually. Though, we'd probably ask users to provide a polyfill rather than include it in video.js. Then if Promises exists or was passed in we'll make one.
Either way, outside the scope of this PR :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be great to make a test for the originally breaking condition.
@videojs/core-committers any thoughts or comments regarding this change? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes seem good to go. Should be tested first, though.
51b57d4
to
2203c40
Compare
removing confirmed until this is tested so we don't merge it in early. |
I just tested this with videojs-playlist and autoadvance is broken with this but works with the released version 6.3.3. We might want to re think about this taking the |
@gkatsev I saw the same playlist behavior when re-testing to see if #4639 is still necessary. Autoadvance didnt work unless I wrapped silencePromise(this.techGet_('play')) in a timeout of some arbitrary amount (can't recall exact wait time), so perhaps the player is still reporting it is ready too early. |
2203c40
to
3997cec
Compare
@alex-barstow is correct. What's happening is that we're calling |
I'm not sure this is going to work, but I'm going to gather some thoughts here, partly to get my head on straight. The current and previous assumption within Video.js has been that the player's ready state is identical to the tech's ready state. This PR aims to adjust that assumption such that the tech could be ready, but the player may still be resolving a source asynchronously via middleware; so, the player should not be ready. Why? Because, I think it's fair to assume as a user of Video.js that if I queue a ready callback on a player, I should be able to interact with it once that callback fires (e.g., calling However, the I'm going to spend some time today tinkering with this, but it may not be possible to work around without breaking something. |
b9f0acf
to
3fddd8a
Compare
Alright, turns out most of the issues with changing |
// At this point, we've found a compatible tech and source and set the | ||
// source both on the tech and in the internal cache. It's safe to tell | ||
// any subscribers that the player is ready again. | ||
this.triggerReady(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we even need to do this? Should we be getting ready from the tech for the source change in the first place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the whole idea here is that tech ready != player ready - this is the latter. The tech is ready before the source change is complete. Maybe I'm not following the line of questions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, when the player gets the tech's ready
event, it already triggers ready itself:
Lines 907 to 908 in c7ad7b3
// player.triggerReady is always async, so don't need this to be async | |
this.tech_.ready(Fn.bind(this, this.handleTechReady_), true); |
Lines 1055 to 1061 in c7ad7b3
/** | |
* Player waits for the tech to be ready | |
* | |
* @private | |
*/ | |
handleTechReady_() { | |
this.triggerReady(); |
So, we would end up getting two
ready
events for the same source unless we change the way that handleTechReady
functions.
src/js/player.js
Outdated
@@ -2324,10 +2330,10 @@ class Player extends Component { | |||
this.middleware_ = mws; | |||
|
|||
// Attempt to set the source on a tech. | |||
const foundTech = this.src_(middlewareSource); | |||
const didNotFindTech = this.src_(middlewareSource); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that is a better name
@dmlap brought up an interesting question related to this. If you are using I wondered more about this possibly being a subtle breaking change and whether the alternative approach should be to suggest waiting for player.src({src: 'foo.mp4', type: 'video/mp4'});
player.ready(() => {
player.one('canplay', () => player.play());
}); |
@misteroneill sounds like @dmlap and I had similar questions though worded differently. |
I wonder if a better fix is just to have |
I don't think this is the right path forward. I'm going back to the drawing board. |
…instead of just ready. (#4743) The core goal here is to make sure the following works in light of some middleware process that makes setting the source more async than next tick: ```js player.src('...'); player.ready(() => player.play()); ``` In fact, given this change, we should even be able to do: ```js player.src('...'); player.play(); ``` Unlike #4665, which would have clarified/changed the meaning of "ready", it remains a reflection of the tech's state and we make better use of the ability to queue things on that state and on the middleware `setSource` process.
…#4665 (#136) (#141) This reverts commit 87f0d08119a825098e944e37a20d95596306eae2.
In working on some issues with asynchronous middleware and playlists, I realized that there is a period of time during the source and tech setting process where a new source and tech have not been selected, but the player still considers itself "ready".
The effect of this is to cause any callbacks to
ready()
during source selection to be called immediately instead of waiting for the source/tech to be selected:I think most people would expect, in that case,
foo.mp4
to begin playback. However, if there is asynchronous middleware happening, what you may get is some playback of the current source and then a source change tofoo.mp4
(which will pause the player).The essence of this change is to separate the player's readiness from the tech's readiness. The tech can be ready when the player is not because the player is still resolving middleware, etc.
This should be backward-compatible and make
ready()
more reliable and better match user expectations.Requirements Checklist