Skip to content
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

Always emulated text track list #2391

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
"license": "Apache-2.0",
"keywords": [
"videojs",
"html5",
"flash",
"html5",
"player",
"video",
"player"
"videojs"
],
"homepage": "http://videojs.com",
"author": "Steve Heffernan",
Expand Down Expand Up @@ -79,21 +79,21 @@
"karma-sauce-launcher": "^0.2.8",
"karma-sinon": "^1.0.3",
"load-grunt-tasks": "^3.1.0",
"qunitjs": "~1.14.0",
"qunitjs": "^1.18.0",
"sinon": "~1.9.1",
"time-grunt": "^1.1.1",
"uglify-js": "~2.3.6",
"videojs-doc-generator": "0.0.1"
},
"standard": {
"ignore": [
"**/Gruntfile.js",
"**/build/**",
"**/dist/**",
"**/docs/**",
"**/lang/**",
"**/sandbox/**",
"**/test/**",
"**/Gruntfile.js"
"**/test/**"
]
}
}
4 changes: 4 additions & 0 deletions src/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import globalOptions from './global-options.js';
import safeParseTuple from 'safe-json-parse/tuple';
import assign from 'object.assign';
import mergeOptions from './utils/merge-options.js';
import textTrackConverter from './tracks/text-track-list-converter.js';

// Include required child components (importing also registers them)
import MediaLoader from './tech/loader.js';
Expand Down Expand Up @@ -522,6 +523,8 @@ class Player extends Component {
let techComponent = Component.getComponent(techName);
this.tech = new techComponent(techOptions);

textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech);

this.on(this.tech, 'ready', this.handleTechReady);
this.on(this.tech, 'usenativecontrols', this.handleTechUseNativeControls);

Expand Down Expand Up @@ -580,6 +583,7 @@ class Player extends Component {
unloadTech() {
// Save the current text tracks so that we can reuse the same text tracks with the next tech
this.textTracks_ = this.textTracks();
this.textTracksJson_ = textTrackConverter.textTracksToJson(this);

this.isReady_ = false;

Expand Down
63 changes: 53 additions & 10 deletions src/js/tech/html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ class Html5 extends Tech {

if (this.featuresNativeTextTracks) {
this.on('loadstart', Fn.bind(this, this.hideCaptions));

this.handleTextTrackChange_ = Fn.bind(this, this.handleTextTrackChange);
this.handleTextTrackAdd_ = Fn.bind(this, this.handleTextTrackAdd);
this.handleTextTrackRemove_ = Fn.bind(this, this.handleTextTrackRemove);
this.proxyNativeTextTracks_();
}

// Determine if native controls should be used
Expand All @@ -86,6 +91,22 @@ class Html5 extends Tech {
* @method dispose
*/
dispose() {
let tt = this.el().textTracks;
let emulatedTt = this.textTracks();

// remove native event listeners
tt.removeEventListener('change', this.handleTextTrackChange_);
tt.removeEventListener('addtrack', this.handleTextTrackAdd_);
tt.removeEventListener('removetrack', this.handleTextTrackRemove_);

// clearout the emulated text track list.
let i = emulatedTt.length;

while (i--) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to not do any async shenanigans. Even though, those async shenanigans might be more technically correct, this one is good enough and much simpler.

emulatedTt.removeTrack_(emulatedTt[i]);
}


Html5.disposeMediaElement(this.el_);
super.dispose();
}
Expand Down Expand Up @@ -182,6 +203,32 @@ class Html5 extends Tech {
}
}

proxyNativeTextTracks_() {
let tt = this.el().textTracks;

tt.addEventListener('change', this.handleTextTrackChange_);
tt.addEventListener('addtrack', this.handleTextTrackAdd_);
tt.addEventListener('removetrack', this.handleTextTrackRemove_);
}

handleTextTrackChange(e) {
let tt = this.textTracks();
this.textTracks().trigger({
type: 'change',
target: tt,
currentTarget: tt,
srcElement: tt
});
}

handleTextTrackAdd(e) {
this.textTracks().addTrack_(e.track);
}

handleTextTrackRemove(e) {
this.textTracks().removeTrack_(e.track);
}

/**
* Play for html5 tech
*
Expand Down Expand Up @@ -584,11 +631,7 @@ class Html5 extends Tech {
* @method textTracks
*/
textTracks() {
if (!this['featuresNativeTextTracks']) {
return super.textTracks();
}

return this.el_.textTracks;
return super.textTracks();
}

/**
Expand Down Expand Up @@ -683,12 +726,12 @@ class Html5 extends Tech {

this.remoteTextTracks().removeTrack_(track);

tracks = this.el()['querySelectorAll']('track');
tracks = this.el().querySelectorAll('track');

for (i = 0; i < tracks.length; i++) {
if (tracks[i] === track || tracks[i]['track'] === track) {
tracks[i]['parentNode']['removeChild'](tracks[i]);
break;
i = tracks.length;
while (i--) {
if (track === tracks[i] || track === tracks[i].track) {
this.el().removeChild(tracks[i]);
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/js/tech/tech.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,14 @@ class Tech extends Component {
* @method dispose
*/
dispose() {
// clear out text tracks because we can't reuse them between techs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we're not maintaining tracks between tech/source changes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we cannot maintain them. If we're switching from Html to Flash, we're moving from track elements to emulated text tracks. And then if if we're going from Flash back to Html, we would want to clear out our emulated text tracks and use the native track elements.

If you're just switching sources but staying in the same tech, you should keep the tracks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so help me understand what this means for users. What are caveats of using text tracks that we need to tell them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would mean that users would need to re-add cuechange events to the text tracks each time the tech changes. Also, events that are added to the cues themselves would need to be re-added, though, we don't currently support that for the emulated cues.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the text track list (player.textTracks) will survive. We'll remove the old text tracks (which will trigger change events) and add new text tracks (which will also trigger change events). Users should listen for those events to handle re-adding listeners to the new tracks. Is that right?

That doesn't sound too bad.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, sounds right.

let tt = this.textTracks();
let i = tt.length;
while(i--) {
this.removeRemoteTextTrack(tt[i]);
}


// Turn off any manual progress or timeupdate tracking
if (this.manualProgress) { this.manualProgressOff(); }

Expand Down
47 changes: 47 additions & 0 deletions src/js/tracks/text-track-list-converter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
let trackToJson = function(track) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function and file could use an explanatory comment.

return {
kind: track.kind,
label: track.label,
language: track.language,
id: track.id,
inBandMetadataTrackDispatchType: track.inBandMetadataTrackDispatchType,
mode: track.mode,
cues: track.cues && Array.prototype.map.call(track.cues, function(cue) {
return {
startTime: cue.startTime,
endTime: cue.endTime,
text: cue.text,
id: cue.id
};
}),
src: track.src
};
};

let textTracksToJson = function(tech) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of textTracksToJson compared to ttToJson could be a little more different.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Also, writing tests for this.

let trackEls = tech.el().querySelectorAll('track');

let trackObjs = Array.prototype.map.call(trackEls, (t) => t.track);
let tracks = Array.prototype.map.call(trackEls, function(trackEl) {
let json = trackToJson(trackEl.track);
json.src = trackEl.src;
return json;
});

return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function(track) {
return trackObjs.indexOf(track) === -1;
}).map(trackToJson));
};

let jsonToTextTracks = function(json, tech) {
json.forEach(function(track) {
let addedTrack = tech.addRemoteTextTrack(track).track;
if (!track.src && track.cues) {
track.cues.forEach((cue) => addedTrack.addCue(cue));
}
});

return tech.textTracks();
};

export default {textTracksToJson, jsonToTextTracks, trackToJson};
1 change: 1 addition & 0 deletions src/js/tracks/text-track.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ let TextTrack = function(options={}) {
});

if (options.src) {
tt.src = options.src;
loadTrack(options.src, tt);
} else {
tt.loaded_ = true;
Expand Down
1 change: 1 addition & 0 deletions test/unit/tech/flash.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ test('dispose removes the object element even before ready fires', function() {
mockFlash.off = noop;
mockFlash.trigger = noop;
mockFlash.el_ = {};
mockFlash.textTracks = () => ([]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is what was causing the issues in safari that we're seeing in master and elsewhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can we fix this? How do we fix this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you did. Sweet. :)


dispose.call(mockFlash);
strictEqual(mockFlash.el_, null, 'swf el is nulled');
Expand Down
45 changes: 45 additions & 0 deletions test/unit/tech/html5.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,48 @@ test('native source handler canHandleSource', function(){
// Reset test video canPlayType
Html5.TEST_VID.canPlayType = origCPT;
});

if (Html5.supportsNativeTextTracks()) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these only run if we are using and support native text tracks, so, I'm only running the tests in those browsers as well because it'll fail otherwise.

test('add native textTrack listeners on startup', function() {
let adds = [];
let rems = [];
let tt = {
length: 0,
addEventListener: (type, fn) => adds.push([type, fn]),
removeEventListener: (type, fn) => rems.push([type, fn]),
};
let el = document.createElement('div');
el.textTracks = tt;

let htmlTech = new Html5({el});

equal(adds[0][0], 'change', 'change event handler added');
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
});

test('remove all tracks from emulated list on dispose', function() {
let adds = [];
let rems = [];
let tt = {
length: 0,
addEventListener: (type, fn) => adds.push([type, fn]),
removeEventListener: (type, fn) => rems.push([type, fn]),
};
let el = document.createElement('div');
el.textTracks = tt;

let htmlTech = new Html5({el});
htmlTech.dispose();

equal(adds[0][0], 'change', 'change event handler added');
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
equal(rems[0][0], 'change', 'change event handler removed');
equal(rems[1][0], 'addtrack', 'addtrack event handler removed');
equal(rems[2][0], 'removetrack', 'removetrack event handler removed');
equal(adds[0][0], rems[0][0], 'change event handler removed');
equal(adds[1][0], rems[1][0], 'addtrack event handler removed');
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
});
}
Loading