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 @@
// allow in-band metadata to be observed
self.metadataStream = new MetadataStream();
this.mediaTimelineOffset = null;
// The first timestamp value encountered during parsing. This
// value can be used to determine the relative timing between
// frames and the start of the current timestamp sequence. It
......
......@@ -287,7 +287,7 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
// create cue points for all the ID3 frames in this metadata event
for (i = 0; i < metadata.frames.length; i++) {
frame = metadata.frames[i];
time = segmentOffset + ((metadata.pts - tech.segmentParser_.timestampOffset) * 0.001);
time = tech.segmentParser_.mediaTimelineOffset + ((metadata.pts - tech.segmentParser_.timestampOffset) * 0.001);
cue = new window.VTTCue(time, time, frame.value || frame.url || '');
cue.frame = frame;
textTrack.addCue(cue);
......@@ -297,23 +297,20 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
// when seeking, clear out all cues ahead of the earliest position
// in the new segment. keep earlier cues around so they can still be
// programmatically inspected even though they've already fired
tech.on('seeking', function() {
tech.on(tech.player(), 'seeking', function() {
var media, startTime, i;
if (!textTrack) {
return;
}
var media = tech.playlists.media(), i;
var startTime = tech.playlists.expiredPreDiscontinuity_ + tech.playlists.expiredPostDiscontinuity_;
media = tech.playlists.media();
startTime = tech.playlists.expiredPreDiscontinuity_ + tech.playlists.expiredPostDiscontinuity_;
startTime += videojs.Hls.Playlist.duration(media, media.mediaSequence, media.mediaSequence + tech.mediaIndex);
console.trace('seeking');
i = textTrack.cues.length;
while (i--) {
if (textTrack.cues[i].startTime < startTime) {
// cues are sorted by start time, earliest first, so all the
// rest of the cues are from earlier segments
break;
if (textTrack.cues[i].startTime >= startTime) {
textTrack.removeCue(textTrack.cues[i]);
}
textTrack.removeCue(textTrack.cues[i])
}
});
};
......@@ -396,16 +393,20 @@ videojs.Hls.prototype.duration = function() {
videojs.Hls.prototype.seekable = function() {
var absoluteSeekable, startOffset, media;
if (this.playlists) {
// report the seekable range relative to the earliest possible
// position when the stream was first loaded
media = this.playlists.media();
absoluteSeekable = videojs.Hls.Playlist.seekable(media);
startOffset = this.playlists.expiredPostDiscontinuity_ - this.playlists.expiredPreDiscontinuity_;
return videojs.createTimeRange(startOffset,
startOffset + (absoluteSeekable.end(0) - absoluteSeekable.start(0)));
if (!this.playlists) {
return videojs.createTimeRange();
}
return videojs.createTimeRange();
media = this.playlists.media();
if (!media) {
return videojs.createTimeRange();
}
// report the seekable range relative to the earliest possible
// position when the stream was first loaded
absoluteSeekable = videojs.Hls.Playlist.seekable(media);
startOffset = this.playlists.expiredPostDiscontinuity_ - this.playlists.expiredPreDiscontinuity_;
return videojs.createTimeRange(startOffset,
startOffset + (absoluteSeekable.end(0) - absoluteSeekable.start(0)));
};
/**
......@@ -848,7 +849,10 @@ videojs.Hls.prototype.drainBuffer = function(event) {
// sequence, the segment parser's timestamp offset must be
// re-calculated
if (segment.discontinuity) {
this.segmentParser_.mediaTimelineOffset = segmentOffset * 0.001;
this.segmentParser_.timestampOffset = null;
} else if (this.segmentParser_.mediaTimelineOffset === null) {
this.segmentParser_.mediaTimelineOffset = segmentOffset * 0.001;
}
// transmux the segment data from MP2T to FLV
......
......@@ -75,7 +75,7 @@ module.exports = function(config) {
'../node_modules/sinon/lib/sinon/util/fake_xml_http_request.js',
'../node_modules/sinon/lib/sinon/util/xhr_ie.js',
'../node_modules/sinon/lib/sinon/util/fake_timers.js',
'../node_modules/video.js/dist/video-js/video.js',
'../node_modules/video.js/dist/video-js/video.dev.js',
'../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
'../node_modules/pkcs7/dist/pkcs7.unpad.js',
'../test/karma-qunit-shim.js',
......
......@@ -39,7 +39,7 @@ module.exports = function(config) {
'../node_modules/sinon/lib/sinon/util/fake_xml_http_request.js',
'../node_modules/sinon/lib/sinon/util/xhr_ie.js',
'../node_modules/sinon/lib/sinon/util/fake_timers.js',
'../node_modules/video.js/dist/video-js/video.js',
'../node_modules/video.js/dist/video-js/video.dev.js',
'../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
'../node_modules/pkcs7/dist/pkcs7.unpad.js',
'../test/karma-qunit-shim.js',
......
......@@ -1320,6 +1320,14 @@ test('clears in-band cues ahead of current time on seek', function() {
standardXHRResponse(requests.shift()); // media
tags.push({ pts: 10 * 1000, bytes: new Uint8Array(1) });
events.push({
pts: 20 * 1000,
data: new Uint8Array([]),
frames: [{
id: 'TXXX',
value: 'cue 3'
}]
});
events.push({
pts: 9.9 * 1000,
data: new Uint8Array([]),
frames: [{
......@@ -1345,7 +1353,7 @@ test('clears in-band cues ahead of current time on seek', function() {
// seek into segment 1
player.currentTime(11);
player.hls.trigger('seeking');
player.trigger('seeking');
equal(track.cues.length, 1, 'removed a cue');
equal(track.cues[0].startTime, 9.9, 'retained the earlier cue');
});
......@@ -1990,8 +1998,7 @@ test('remove event handlers on dispose', function() {
player.dispose();
ok(offhandlers > onhandlers, 'more handlers were removed than were registered');
equal(offhandlers - onhandlers, 1, 'one handler was registered during init');
ok(offhandlers > onhandlers, 'removed all registered handlers');
});
test('aborts the source buffer on disposal', function() {
......