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

setSrc clears currentSource_ after loadstart #3285

Closed
wants to merge 8 commits into
from

Conversation

Projects
None yet
4 participants
@incompl
Contributor

incompl commented Apr 26, 2016

Description

currentSrc will now report correct source after the video source was changed by a third party.

This PR replaces #2957

Specific Changes proposed

  • setSrc now clears currentSource_ after loadstart
  • EventTarget.prototype.one removes the addEventListener alias before calling Events.on so we don't get into an infinite type loop

Requirements Checklist

  • Feature implemented / Bug fixed
  • If necessary, more likely in a feature request than a bug fix
  • Reviewed by Two Core Contributors
@gkatsev

This comment has been minimized.

Show comment
Hide comment
@gkatsev

gkatsev Apr 26, 2016

Member

Fixes #1951. This still needs disposing the source handler, right?

Member

gkatsev commented Apr 26, 2016

Fixes #1951. This still needs disposing the source handler, right?

@incompl

This comment has been minimized.

Show comment
Hide comment
@incompl

incompl Apr 26, 2016

Contributor

@gkatsev It has this.disposeSourceHandler(); in setSrc

Contributor

incompl commented Apr 26, 2016

@gkatsev It has this.disposeSourceHandler(); in setSrc

@incompl

This comment has been minimized.

Show comment
Hide comment
@incompl

incompl Apr 26, 2016

Contributor

Moved disposeSourceHandler to the right place

Contributor

incompl commented Apr 26, 2016

Moved disposeSourceHandler to the right place

Show outdated Hide outdated src/js/event-target.js
// Remove the addEventListener alias before calling Events.on
// so we don't get into an infinite type loop
let ael = this.addEventListener;
this.addEventListener = Function.prototype;

This comment has been minimized.

@misteroneill

misteroneill Apr 27, 2016

Member

We have encountered issues with using Function.prototype as a no-op due to the way video.js tracks functions by a GUID property. I don't think it would be an issue here, but I think it's safer to never use it. Unfortunately. 😞

@misteroneill

misteroneill Apr 27, 2016

Member

We have encountered issues with using Function.prototype as a no-op due to the way video.js tracks functions by a GUID property. I don't think it would be an issue here, but I think it's safer to never use it. Unfortunately. 😞

This comment has been minimized.

@incompl

incompl Apr 27, 2016

Contributor

What do you propose? () => {}?

@incompl

incompl Apr 27, 2016

Contributor

What do you propose? () => {}?

Show outdated Hide outdated src/js/tech/html5.js
@@ -99,7 +99,7 @@ class Html5 extends Tech {
if (this.featuresNativeTextTracks) {
if (crossoriginTracks) {
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.

This comment has been minimized.

@misteroneill

misteroneill Apr 27, 2016

Member

tsml will strip spaces between lines so this will end up with "isn't used.This may", which isn't the end of the world, but we could use my alternative package tsmlj which will join each line with a space.

@misteroneill

misteroneill Apr 27, 2016

Member

tsml will strip spaces between lines so this will end up with "isn't used.This may", which isn't the end of the world, but we could use my alternative package tsmlj which will join each line with a space.

This comment has been minimized.

@incompl

incompl Apr 27, 2016

Contributor

Will tell me editor to cut it out with the whitespace trimming.

@incompl

incompl Apr 27, 2016

Contributor

Will tell me editor to cut it out with the whitespace trimming.

Show outdated Hide outdated test/unit/tech/html5.test.js
one: (el, type, fun) => {
el.one(type, Fn.bind(thing, fun));
},
disposeSourceHandler: () => {},

This comment has been minimized.

@gkatsev

gkatsev Apr 27, 2016

Member

we should verify that this has been called.

@gkatsev

gkatsev Apr 27, 2016

Member

we should verify that this has been called.

This comment has been minimized.

@incompl

incompl Apr 27, 2016

Contributor

Good idea

@incompl

incompl Apr 27, 2016

Contributor

Good idea

@gkatsev

This comment has been minimized.

Show comment
Hide comment
@gkatsev

gkatsev Apr 27, 2016

Member

@dmlap or @imbcmdth can you check this out as well and let us know if you think there could be any issues with it for contrib-hls.

Member

gkatsev commented Apr 27, 2016

@dmlap or @imbcmdth can you check this out as well and let us know if you think there could be any issues with it for contrib-hls.

incompl added some commits Apr 27, 2016

@@ -11,7 +11,7 @@ EventTarget.prototype.on = function(type, fn) {
// Remove the addEventListener alias before calling Events.on
// so we don't get into an infinite type loop
let ael = this.addEventListener;
this.addEventListener = Function.prototype;
this.addEventListener = () => {};

This comment has been minimized.

@dmlap

dmlap Apr 27, 2016

Member

Point for discussion: should we be careful about using fat-arrows all over the place? I know calling addEventListener() with a non-this receiver is a weird thing to do but we could use a plain function here and avoid possibly subverting people's expectations.

@dmlap

dmlap Apr 27, 2016

Member

Point for discussion: should we be careful about using fat-arrows all over the place? I know calling addEventListener() with a non-this receiver is a weird thing to do but we could use a plain function here and avoid possibly subverting people's expectations.

This comment has been minimized.

@dmlap

dmlap Apr 27, 2016

Member

We're gonna change this to function() {}

@dmlap

dmlap Apr 27, 2016

Member

We're gonna change this to function() {}

This comment has been minimized.

@gkatsev

gkatsev Apr 27, 2016

Member

I don't think it matters here at all since we just want an empty function while Events.on runs so we don't get into an infinite loop, so, there's no expectations being subverted here.
As for elsewhere, it really depends on what you want the context of your function to be since in arrow functions this and arguments are never bound.

@gkatsev

gkatsev Apr 27, 2016

Member

I don't think it matters here at all since we just want an empty function while Events.on runs so we don't get into an infinite loop, so, there's no expectations being subverted here.
As for elsewhere, it really depends on what you want the context of your function to be since in arrow functions this and arguments are never bound.

This comment has been minimized.

@dmlap

dmlap May 2, 2016

Member

That's the crux of the discussion I think we should have. Using fat-arrows removes the ability of the caller to rebind this. I've felt it necessary to rebind this more than once in my life. Are we intentionally removing that ability or just doing it to save some keystrokes?

@dmlap

dmlap May 2, 2016

Member

That's the crux of the discussion I think we should have. Using fat-arrows removes the ability of the caller to rebind this. I've felt it necessary to rebind this more than once in my life. Are we intentionally removing that ability or just doing it to save some keystrokes?

// Remove the addEventListener alias before calling Events.on
// so we don't get into an infinite type loop
let ael = this.addEventListener;
this.addEventListener = () => {};

This comment has been minimized.

@dmlap

dmlap Apr 27, 2016

Member

What would happen if one of the listeners registers an event listener? Something like this:

function maybeReRegister() {
  if (someCondition()) {
    player.one('play', maybeReRegister);
    return;
  }
  alert('done!');
};
player.one('play', maybeReRegister);
@dmlap

dmlap Apr 27, 2016

Member

What would happen if one of the listeners registers an event listener? Something like this:

function maybeReRegister() {
  if (someCondition()) {
    player.one('play', maybeReRegister);
    return;
  }
  alert('done!');
};
player.one('play', maybeReRegister);

This comment has been minimized.

@dmlap

dmlap Apr 27, 2016

Member

After reviewing, I don't think this is a problem right now but the event stuff sure is confusing.

@dmlap

dmlap Apr 27, 2016

Member

After reviewing, I don't think this is a problem right now but the event stuff sure is confusing.

This comment has been minimized.

@gkatsev

gkatsev Apr 27, 2016

Member

I think it works fine because the event system creates a clone of the handlers before it starts triggering events:

if (handlers) {
// Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
var handlersCopy = handlers.slice(0);
for (var m = 0, n = handlersCopy.length; m < n; m++) {
if (event.isImmediatePropagationStopped()) {
break;
} else {
handlersCopy[m].call(elem, event, hash);
}
}
}

@gkatsev

gkatsev Apr 27, 2016

Member

I think it works fine because the event system creates a clone of the handlers before it starts triggering events:

if (handlers) {
// Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
var handlersCopy = handlers.slice(0);
for (var m = 0, n = handlersCopy.length; m < n; m++) {
if (event.isImmediatePropagationStopped()) {
break;
} else {
handlersCopy[m].call(elem, event, hash);
}
}
}

Show outdated Hide outdated src/js/tech/html5.js
let loadstartlistener = Html5.prototype.loadStartListener_;
this.off(this.el_, 'loadstart', loadstartlistener);
this.one(this.el_, 'loadstart', () => this.one(this.el_, 'loadstart', loadstartlistener));

This comment has been minimized.

@dmlap

dmlap Apr 27, 2016

Member

Does this assume setSrc() is the only valid way to modify the video element's source? What about setSource()?

Is there a way this can be written that doesn't require adding and removing listeners after the tech's creation? You might be able to use the readyState of the HTML element, for instance.

@dmlap

dmlap Apr 27, 2016

Member

Does this assume setSrc() is the only valid way to modify the video element's source? What about setSource()?

Is there a way this can be written that doesn't require adding and removing listeners after the tech's creation? You might be able to use the readyState of the HTML element, for instance.

This comment has been minimized.

@dmlap

dmlap Apr 27, 2016

Member

Also, this function literal wouldn't get cleared properly on subsequent calls to setSrc() which would cause the loadstartlistener_ to get invoked on the wrong loadstart

@dmlap

dmlap Apr 27, 2016

Member

Also, this function literal wouldn't get cleared properly on subsequent calls to setSrc() which would cause the loadstartlistener_ to get invoked on the wrong loadstart

This comment has been minimized.

@gkatsev

gkatsev Apr 27, 2016

Member

Yeah, this doesn't account for setSource which is what ends up being used if a source handler is in effect.

video.js/src/js/player.js

Lines 1916 to 1919 in 2e2dbde

if (currentTech.prototype.hasOwnProperty('setSource')) {
this.techCall_('setSource', source);
} else {
this.techCall_('src', source.src);

@gkatsev

gkatsev Apr 27, 2016

Member

Yeah, this doesn't account for setSource which is what ends up being used if a source handler is in effect.

video.js/src/js/player.js

Lines 1916 to 1919 in 2e2dbde

if (currentTech.prototype.hasOwnProperty('setSource')) {
this.techCall_('setSource', source);
} else {
this.techCall_('src', source.src);

This comment has been minimized.

@gkatsev

gkatsev Apr 27, 2016

Member

why would loadstartlistener get called on the wrong loadstart?

@gkatsev

gkatsev Apr 27, 2016

Member

why would loadstartlistener get called on the wrong loadstart?

This comment has been minimized.

@dmlap

dmlap Apr 28, 2016

Member

Here's the scenario:

  1. Call setSrc()
  2. Video element fires loadstart
  3. Call setSrc()

loadStartListener_() would be removed on the second call to setSrc() but the inline listener that triggers the loadstartlistener on the next loadstart would not. So, the very next loadstart would run loadstartlistener.

@dmlap

dmlap Apr 28, 2016

Member

Here's the scenario:

  1. Call setSrc()
  2. Video element fires loadstart
  3. Call setSrc()

loadStartListener_() would be removed on the second call to setSrc() but the inline listener that triggers the loadstartlistener on the next loadstart would not. So, the very next loadstart would run loadstartlistener.

This comment has been minimized.

@gkatsev

gkatsev Apr 28, 2016

Member

Oh, right, the first loadstart listener would not be removed and would trigger again.

@gkatsev

gkatsev Apr 28, 2016

Member

Oh, right, the first loadstart listener would not be removed and would trigger again.

Show outdated Hide outdated test/unit/tech/html5.test.js
off: () => {},
one: (el, type, fun) => {
el.one(type, Fn.bind(thing, fun));
},

This comment has been minimized.

@dmlap

dmlap Apr 27, 2016

Member

Mocking one() and off() may make this test a little too artificial.

@dmlap

dmlap Apr 27, 2016

Member

Mocking one() and off() may make this test a little too artificial.

This comment has been minimized.

@incompl

incompl Apr 27, 2016

Contributor

It does successfully test that setSrc clears contentSource_ after loadstart. Do you have any suggestions on how to make this more real?

@incompl

incompl Apr 27, 2016

Contributor

It does successfully test that setSrc clears contentSource_ after loadstart. Do you have any suggestions on how to make this more real?

This comment has been minimized.

@dmlap

dmlap Apr 28, 2016

Member

I'd create an object that inherited from EventTarget right in the test here and use that.

@dmlap

dmlap Apr 28, 2016

Member

I'd create an object that inherited from EventTarget right in the test here and use that.

This comment has been minimized.

@gkatsev

gkatsev Apr 28, 2016

Member

We tried that. thing is a a tech mock who's one method signature is different. We could make it be a standalone Component instance, possibly.

@gkatsev

gkatsev Apr 28, 2016

Member

We tried that. thing is a a tech mock who's one method signature is different. We could make it be a standalone Component instance, possibly.

Show outdated Hide outdated src/js/tech/html5.js
@@ -549,9 +549,19 @@ class Html5 extends Tech {
* @method setSrc
*/
setSrc(src) {
let loadstartlistener = Html5.prototype.loadStartListener_;

This comment has been minimized.

@misteroneill

misteroneill Apr 28, 2016

Member

Are we explicitly using the Html5#loadStartListener_ function instead of this.loadStartListener_ because we want to be sure that property hasn't been added to an instance of the tech as something else? Maybe this is a candidate for being module-level private instead of class-level pseudo-private?

@misteroneill

misteroneill Apr 28, 2016

Member

Are we explicitly using the Html5#loadStartListener_ function instead of this.loadStartListener_ because we want to be sure that property hasn't been added to an instance of the tech as something else? Maybe this is a candidate for being module-level private instead of class-level pseudo-private?

This comment has been minimized.

@incompl

incompl Apr 28, 2016

Contributor

No, it's just arbitrary.

@incompl

incompl Apr 28, 2016

Contributor

No, it's just arbitrary.

@gkatsev

This comment has been minimized.

Show comment
Hide comment
@gkatsev

gkatsev Apr 29, 2016

Member

That looks pretty good to me. @dmlap can you re-review?

Member

gkatsev commented Apr 29, 2016

That looks pretty good to me. @dmlap can you re-review?

@gkatsev

This comment has been minimized.

Show comment
Hide comment
@gkatsev

gkatsev May 2, 2016

Member

Oh neat, github picked up that it's the same code (I pushed this branch to the repo so that it can run in browserstack).

Member

gkatsev commented May 2, 2016

Oh neat, github picked up that it's the same code (I pushed this branch to the repo so that it can run in browserstack).

_Tech.prototype.disposeSourceHandler = function(){
// On the first loadstart after setSource
_Tech.prototype.firstLoadStartListener_ = function() {
this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);

This comment has been minimized.

@dmlap

dmlap May 2, 2016

Member

Why use one() if you want the successiveLoadStartListener_ to run on every subsequent loadstart?

@dmlap

dmlap May 2, 2016

Member

Why use one() if you want the successiveLoadStartListener_ to run on every subsequent loadstart?

This comment has been minimized.

@incompl

incompl May 2, 2016

Contributor

It's because disposeSourceHandler removes it. We work around this by re-adding it in successiveLoadStartListener_ after disposeSourceHandler has been called.

@incompl

incompl May 2, 2016

Contributor

It's because disposeSourceHandler removes it. We work around this by re-adding it in successiveLoadStartListener_ after disposeSourceHandler has been called.

// If they do, set currentSource_ to null and dispose our source handler.
this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
this.one(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);

This comment has been minimized.

@dmlap

dmlap May 2, 2016

Member

I'm not a fan of using event handler registration as instance-level state. The current set of registered event handlers are difficult to introspect and control flow is harder to trace. I don't see any logical issues with this code so we can move ahead with this but in general, I think adding an instance variable is preferable to this kind of solution.

@dmlap

dmlap May 2, 2016

Member

I'm not a fan of using event handler registration as instance-level state. The current set of registered event handlers are difficult to introspect and control flow is harder to trace. I don't see any logical issues with this code so we can move ahead with this but in general, I think adding an instance variable is preferable to this kind of solution.

@dmlap

This comment has been minimized.

Show comment
Hide comment
@dmlap

dmlap May 2, 2016

Member

I'm not in love with the solution but I can live with it. LGTM

Member

dmlap commented May 2, 2016

I'm not in love with the solution but I can live with it. LGTM

@dmlap dmlap closed this in 249532a May 2, 2016

wmfgerrit pushed a commit to wikimedia/mediawiki-extensions-TimedMediaHandler that referenced this pull request May 8, 2016

Update videojs to 5.10.1
Changelog is at

5.9.0
* https://github.com/videojs/video.js/releases/tag/v5.9.0

5.9.1
* https://github.com/videojs/video.js/releases/tag/v5.9.1

5.9.2
* https://github.com/videojs/video.js/releases/tag/v5.9.2

Microsoft Edge change
* updated IS_CHROME to not be true on MS Edge

5.10.1
*https://github.com/videojs/video.js/releases/tag/v5.10.1

Important: Changing resolution now works as of this change
videojs/video.js#3285

Which is in 5.10.1.

Ive tested and changing resolution now works.

Bug: T131544
Change-Id: I191d92f24609b22424c402d0ef7f4b44cf1e6b20

wmfgerrit pushed a commit to wikimedia/mediawiki-extensions that referenced this pull request May 8, 2016

Updated mediawiki/extensions
Project: mediawiki/extensions/TimedMediaHandler  2074f78f19c3b4c028c4566f2a416b44ac61b140

Update videojs to 5.10.1

Changelog is at

5.9.0
* https://github.com/videojs/video.js/releases/tag/v5.9.0

5.9.1
* https://github.com/videojs/video.js/releases/tag/v5.9.1

5.9.2
* https://github.com/videojs/video.js/releases/tag/v5.9.2

Microsoft Edge change
* updated IS_CHROME to not be true on MS Edge

5.10.1
*https://github.com/videojs/video.js/releases/tag/v5.10.1

Important: Changing resolution now works as of this change
videojs/video.js#3285

Which is in 5.10.1.

Ive tested and changing resolution now works.

Bug: T131544
Change-Id: I191d92f24609b22424c402d0ef7f4b44cf1e6b20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment