95aa0508 by David LaPalomento

Track the media time associated with the segment parser PTS offset

If the segment parser timestamp offset isn't at the start of the last encountered discontinuity, it was impossible to recover the associated media time. Track the media time alongside the timestamp offset so that when a stream is first loaded and the last discontinuity segment start is unavailable, it's still possible to properly translate timestamps. Also, make sure that cue points ahead of current time are cleaned out of the in-band metadata track on seeking. We previously assumed they were ordered by start time but that doesn't seem to be the case for Chrome on OS X. Use the dev version of video.js because of Google Closure compiler gobbling some part of the requiremed machinery for removing cues.
1 parent 237d9b4e
...@@ -31,6 +31,8 @@ ...@@ -31,6 +31,8 @@
31 // allow in-band metadata to be observed 31 // allow in-band metadata to be observed
32 self.metadataStream = new MetadataStream(); 32 self.metadataStream = new MetadataStream();
33 33
34 this.mediaTimelineOffset = null;
35
34 // The first timestamp value encountered during parsing. This 36 // The first timestamp value encountered during parsing. This
35 // value can be used to determine the relative timing between 37 // value can be used to determine the relative timing between
36 // frames and the start of the current timestamp sequence. It 38 // frames and the start of the current timestamp sequence. It
......
...@@ -287,7 +287,7 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { ...@@ -287,7 +287,7 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
287 // create cue points for all the ID3 frames in this metadata event 287 // create cue points for all the ID3 frames in this metadata event
288 for (i = 0; i < metadata.frames.length; i++) { 288 for (i = 0; i < metadata.frames.length; i++) {
289 frame = metadata.frames[i]; 289 frame = metadata.frames[i];
290 time = segmentOffset + ((metadata.pts - tech.segmentParser_.timestampOffset) * 0.001); 290 time = tech.segmentParser_.mediaTimelineOffset + ((metadata.pts - tech.segmentParser_.timestampOffset) * 0.001);
291 cue = new window.VTTCue(time, time, frame.value || frame.url || ''); 291 cue = new window.VTTCue(time, time, frame.value || frame.url || '');
292 cue.frame = frame; 292 cue.frame = frame;
293 textTrack.addCue(cue); 293 textTrack.addCue(cue);
...@@ -297,23 +297,20 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { ...@@ -297,23 +297,20 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
297 // when seeking, clear out all cues ahead of the earliest position 297 // when seeking, clear out all cues ahead of the earliest position
298 // in the new segment. keep earlier cues around so they can still be 298 // in the new segment. keep earlier cues around so they can still be
299 // programmatically inspected even though they've already fired 299 // programmatically inspected even though they've already fired
300 tech.on('seeking', function() { 300 tech.on(tech.player(), 'seeking', function() {
301 var media, startTime, i;
301 if (!textTrack) { 302 if (!textTrack) {
302 return; 303 return;
303 } 304 }
304 var media = tech.playlists.media(), i; 305 media = tech.playlists.media();
305 var startTime = tech.playlists.expiredPreDiscontinuity_ + tech.playlists.expiredPostDiscontinuity_; 306 startTime = tech.playlists.expiredPreDiscontinuity_ + tech.playlists.expiredPostDiscontinuity_;
306 startTime += videojs.Hls.Playlist.duration(media, media.mediaSequence, media.mediaSequence + tech.mediaIndex); 307 startTime += videojs.Hls.Playlist.duration(media, media.mediaSequence, media.mediaSequence + tech.mediaIndex);
307 console.trace('seeking');
308 308
309 i = textTrack.cues.length; 309 i = textTrack.cues.length;
310 while (i--) { 310 while (i--) {
311 if (textTrack.cues[i].startTime < startTime) { 311 if (textTrack.cues[i].startTime >= startTime) {
312 // cues are sorted by start time, earliest first, so all the 312 textTrack.removeCue(textTrack.cues[i]);
313 // rest of the cues are from earlier segments
314 break;
315 } 313 }
316 textTrack.removeCue(textTrack.cues[i])
317 } 314 }
318 }); 315 });
319 }; 316 };
...@@ -396,16 +393,20 @@ videojs.Hls.prototype.duration = function() { ...@@ -396,16 +393,20 @@ videojs.Hls.prototype.duration = function() {
396 videojs.Hls.prototype.seekable = function() { 393 videojs.Hls.prototype.seekable = function() {
397 var absoluteSeekable, startOffset, media; 394 var absoluteSeekable, startOffset, media;
398 395
399 if (this.playlists) { 396 if (!this.playlists) {
400 // report the seekable range relative to the earliest possible 397 return videojs.createTimeRange();
401 // position when the stream was first loaded
402 media = this.playlists.media();
403 absoluteSeekable = videojs.Hls.Playlist.seekable(media);
404 startOffset = this.playlists.expiredPostDiscontinuity_ - this.playlists.expiredPreDiscontinuity_;
405 return videojs.createTimeRange(startOffset,
406 startOffset + (absoluteSeekable.end(0) - absoluteSeekable.start(0)));
407 } 398 }
408 return videojs.createTimeRange(); 399 media = this.playlists.media();
400 if (!media) {
401 return videojs.createTimeRange();
402 }
403
404 // report the seekable range relative to the earliest possible
405 // position when the stream was first loaded
406 absoluteSeekable = videojs.Hls.Playlist.seekable(media);
407 startOffset = this.playlists.expiredPostDiscontinuity_ - this.playlists.expiredPreDiscontinuity_;
408 return videojs.createTimeRange(startOffset,
409 startOffset + (absoluteSeekable.end(0) - absoluteSeekable.start(0)));
409 }; 410 };
410 411
411 /** 412 /**
...@@ -848,7 +849,10 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -848,7 +849,10 @@ videojs.Hls.prototype.drainBuffer = function(event) {
848 // sequence, the segment parser's timestamp offset must be 849 // sequence, the segment parser's timestamp offset must be
849 // re-calculated 850 // re-calculated
850 if (segment.discontinuity) { 851 if (segment.discontinuity) {
852 this.segmentParser_.mediaTimelineOffset = segmentOffset * 0.001;
851 this.segmentParser_.timestampOffset = null; 853 this.segmentParser_.timestampOffset = null;
854 } else if (this.segmentParser_.mediaTimelineOffset === null) {
855 this.segmentParser_.mediaTimelineOffset = segmentOffset * 0.001;
852 } 856 }
853 857
854 // transmux the segment data from MP2T to FLV 858 // transmux the segment data from MP2T to FLV
......
...@@ -75,7 +75,7 @@ module.exports = function(config) { ...@@ -75,7 +75,7 @@ module.exports = function(config) {
75 '../node_modules/sinon/lib/sinon/util/fake_xml_http_request.js', 75 '../node_modules/sinon/lib/sinon/util/fake_xml_http_request.js',
76 '../node_modules/sinon/lib/sinon/util/xhr_ie.js', 76 '../node_modules/sinon/lib/sinon/util/xhr_ie.js',
77 '../node_modules/sinon/lib/sinon/util/fake_timers.js', 77 '../node_modules/sinon/lib/sinon/util/fake_timers.js',
78 '../node_modules/video.js/dist/video-js/video.js', 78 '../node_modules/video.js/dist/video-js/video.dev.js',
79 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', 79 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
80 '../node_modules/pkcs7/dist/pkcs7.unpad.js', 80 '../node_modules/pkcs7/dist/pkcs7.unpad.js',
81 '../test/karma-qunit-shim.js', 81 '../test/karma-qunit-shim.js',
......
...@@ -39,7 +39,7 @@ module.exports = function(config) { ...@@ -39,7 +39,7 @@ module.exports = function(config) {
39 '../node_modules/sinon/lib/sinon/util/fake_xml_http_request.js', 39 '../node_modules/sinon/lib/sinon/util/fake_xml_http_request.js',
40 '../node_modules/sinon/lib/sinon/util/xhr_ie.js', 40 '../node_modules/sinon/lib/sinon/util/xhr_ie.js',
41 '../node_modules/sinon/lib/sinon/util/fake_timers.js', 41 '../node_modules/sinon/lib/sinon/util/fake_timers.js',
42 '../node_modules/video.js/dist/video-js/video.js', 42 '../node_modules/video.js/dist/video-js/video.dev.js',
43 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', 43 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
44 '../node_modules/pkcs7/dist/pkcs7.unpad.js', 44 '../node_modules/pkcs7/dist/pkcs7.unpad.js',
45 '../test/karma-qunit-shim.js', 45 '../test/karma-qunit-shim.js',
......
...@@ -1320,6 +1320,14 @@ test('clears in-band cues ahead of current time on seek', function() { ...@@ -1320,6 +1320,14 @@ test('clears in-band cues ahead of current time on seek', function() {
1320 standardXHRResponse(requests.shift()); // media 1320 standardXHRResponse(requests.shift()); // media
1321 tags.push({ pts: 10 * 1000, bytes: new Uint8Array(1) }); 1321 tags.push({ pts: 10 * 1000, bytes: new Uint8Array(1) });
1322 events.push({ 1322 events.push({
1323 pts: 20 * 1000,
1324 data: new Uint8Array([]),
1325 frames: [{
1326 id: 'TXXX',
1327 value: 'cue 3'
1328 }]
1329 });
1330 events.push({
1323 pts: 9.9 * 1000, 1331 pts: 9.9 * 1000,
1324 data: new Uint8Array([]), 1332 data: new Uint8Array([]),
1325 frames: [{ 1333 frames: [{
...@@ -1345,7 +1353,7 @@ test('clears in-band cues ahead of current time on seek', function() { ...@@ -1345,7 +1353,7 @@ test('clears in-band cues ahead of current time on seek', function() {
1345 1353
1346 // seek into segment 1 1354 // seek into segment 1
1347 player.currentTime(11); 1355 player.currentTime(11);
1348 player.hls.trigger('seeking'); 1356 player.trigger('seeking');
1349 equal(track.cues.length, 1, 'removed a cue'); 1357 equal(track.cues.length, 1, 'removed a cue');
1350 equal(track.cues[0].startTime, 9.9, 'retained the earlier cue'); 1358 equal(track.cues[0].startTime, 9.9, 'retained the earlier cue');
1351 }); 1359 });
...@@ -1990,8 +1998,7 @@ test('remove event handlers on dispose', function() { ...@@ -1990,8 +1998,7 @@ test('remove event handlers on dispose', function() {
1990 1998
1991 player.dispose(); 1999 player.dispose();
1992 2000
1993 ok(offhandlers > onhandlers, 'more handlers were removed than were registered'); 2001 ok(offhandlers > onhandlers, 'removed all registered handlers');
1994 equal(offhandlers - onhandlers, 1, 'one handler was registered during init');
1995 }); 2002 });
1996 2003
1997 test('aborts the source buffer on disposal', function() { 2004 test('aborts the source buffer on disposal', function() {
......