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.
Showing
5 changed files
with
32 additions
and
19 deletions
... | @@ -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) { |
397 | return videojs.createTimeRange(); | ||
398 | } | ||
399 | media = this.playlists.media(); | ||
400 | if (!media) { | ||
401 | return videojs.createTimeRange(); | ||
402 | } | ||
403 | |||
400 | // report the seekable range relative to the earliest possible | 404 | // report the seekable range relative to the earliest possible |
401 | // position when the stream was first loaded | 405 | // position when the stream was first loaded |
402 | media = this.playlists.media(); | ||
403 | absoluteSeekable = videojs.Hls.Playlist.seekable(media); | 406 | absoluteSeekable = videojs.Hls.Playlist.seekable(media); |
404 | startOffset = this.playlists.expiredPostDiscontinuity_ - this.playlists.expiredPreDiscontinuity_; | 407 | startOffset = this.playlists.expiredPostDiscontinuity_ - this.playlists.expiredPreDiscontinuity_; |
405 | return videojs.createTimeRange(startOffset, | 408 | return videojs.createTimeRange(startOffset, |
406 | startOffset + (absoluteSeekable.end(0) - absoluteSeekable.start(0))); | 409 | startOffset + (absoluteSeekable.end(0) - absoluteSeekable.start(0))); |
407 | } | ||
408 | return videojs.createTimeRange(); | ||
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() { | ... | ... |
-
Please register or sign in to post a comment