0227bf64 by David LaPalomento

improved video duration calculation. closes #321

2 parents 675d9423 af75b33b
...@@ -2,7 +2,7 @@ CHANGELOG ...@@ -2,7 +2,7 @@ CHANGELOG
2 ========= 2 =========
3 3
4 ## HEAD (Unreleased) 4 ## HEAD (Unreleased)
5 _(none)_ 5 * @dmlap improved video duration calculation. ([view](https://github.com/videojs/videojs-contrib-hls/pull/321))
6 6
7 -------------------- 7 --------------------
8 8
......
...@@ -363,29 +363,4 @@ hls.FlvTag.frameTime = function(tag) { ...@@ -363,29 +363,4 @@ hls.FlvTag.frameTime = function(tag) {
363 return pts; 363 return pts;
364 }; 364 };
365 365
366 /**
367 * Calculate the media timeline duration represented by an array of
368 * tags. This function assumes the tags are already pre-sorted by
369 * presentation timestamp (PTS), in ascending order. Returns zero if
370 * there are less than two FLV tags to inspect.
371 * @param tags {array} the FlvTag objects to query
372 * @return the number of milliseconds between the display time of the
373 * first tag and the last tag.
374 */
375 hls.FlvTag.durationFromTags = function(tags) {
376 if (tags.length < 2) {
377 return 0;
378 }
379
380 var
381 first = tags[0],
382 last = tags[tags.length - 1],
383 frameDuration;
384
385 // use the interval between the last two tags or assume 24 fps
386 frameDuration = last.pts - tags[tags.length - 2].pts || (1/24);
387
388 return (last.pts - first.pts) + frameDuration;
389 };
390
391 })(this); 366 })(this);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
18 resolveUrl = videojs.Hls.resolveUrl, 18 resolveUrl = videojs.Hls.resolveUrl,
19 xhr = videojs.Hls.xhr, 19 xhr = videojs.Hls.xhr,
20 Playlist = videojs.Hls.Playlist, 20 Playlist = videojs.Hls.Playlist,
21 mergeOptions = videojs.util.mergeOptions,
21 22
22 /** 23 /**
23 * Returns a new master playlist that is the result of merging an 24 * Returns a new master playlist that is the result of merging an
...@@ -33,7 +34,7 @@ ...@@ -33,7 +34,7 @@
33 updateMaster = function(master, media) { 34 updateMaster = function(master, media) {
34 var 35 var
35 changed = false, 36 changed = false,
36 result = videojs.util.mergeOptions(master, {}), 37 result = mergeOptions(master, {}),
37 i, 38 i,
38 playlist; 39 playlist;
39 40
...@@ -50,14 +51,47 @@ ...@@ -50,14 +51,47 @@
50 continue; 51 continue;
51 } 52 }
52 53
53 result.playlists[i] = videojs.util.mergeOptions(playlist, media); 54 result.playlists[i] = mergeOptions(playlist, media);
54 result.playlists[media.uri] = result.playlists[i]; 55 result.playlists[media.uri] = result.playlists[i];
56
57 // if the update could overlap existing segment information,
58 // merge the two lists
59 if (playlist.segments) {
60 result.playlists[i].segments = updateSegments(playlist.segments,
61 media.segments,
62 media.mediaSequence - playlist.mediaSequence);
63 }
55 changed = true; 64 changed = true;
56 } 65 }
57 } 66 }
58 return changed ? result : null; 67 return changed ? result : null;
59 }, 68 },
60 69
70 /**
71 * Returns a new array of segments that is the result of merging
72 * properties from an older list of segments onto an updated
73 * list. No properties on the updated playlist will be overridden.
74 * @param original {array} the outdated list of segments
75 * @param update {array} the updated list of segments
76 * @param offset {number} (optional) the index of the first update
77 * segment in the original segment list. For non-live playlists,
78 * this should always be zero and does not need to be
79 * specified. For live playlists, it should be the difference
80 * between the media sequence numbers in the original and updated
81 * playlists.
82 * @return a list of merged segment objects
83 */
84 updateSegments = function(original, update, offset) {
85 var result = update.slice(), length, i;
86 offset = offset || 0;
87 length = Math.min(original.length, update.length + offset);
88
89 for (i = offset; i < length; i++) {
90 result[i - offset] = mergeOptions(original[i], result[i - offset]);
91 }
92 return result;
93 },
94
61 PlaylistLoader = function(srcUrl, withCredentials) { 95 PlaylistLoader = function(srcUrl, withCredentials) {
62 var 96 var
63 loader = this, 97 loader = this,
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
21 * index. 21 * index.
22 */ 22 */
23 segmentsDuration = function(playlist, startSequence, endSequence) { 23 segmentsDuration = function(playlist, startSequence, endSequence) {
24 var targetDuration, i, segment, expiredSegmentCount, result = 0; 24 var targetDuration, i, j, segment, endSegment, expiredSegmentCount, result = 0;
25 25
26 startSequence = startSequence || 0; 26 startSequence = startSequence || 0;
27 i = startSequence; 27 i = startSequence;
...@@ -36,9 +36,27 @@ ...@@ -36,9 +36,27 @@
36 // accumulate the segment durations into the result 36 // accumulate the segment durations into the result
37 for (; i < endSequence; i++) { 37 for (; i < endSequence; i++) {
38 segment = playlist.segments[i - playlist.mediaSequence]; 38 segment = playlist.segments[i - playlist.mediaSequence];
39 result += segment.preciseDuration || 39
40 segment.duration || 40 // when PTS values aren't available, use information from the playlist
41 targetDuration; 41 if (segment.minVideoPts === undefined) {
42 result += segment.duration ||
43 targetDuration;
44 continue;
45 }
46
47 // find the last segment with PTS info and use that to calculate
48 // the interval duration
49 for(j = i; j < endSequence - 1; j++) {
50 endSegment = playlist.segments[j - playlist.mediaSequence + 1];
51 if (endSegment.maxVideoPts === undefined ||
52 endSegment.discontinuity) {
53 break;
54 }
55 }
56 endSegment = playlist.segments[j - playlist.mediaSequence];
57 result += (Math.max(endSegment.maxVideoPts, endSegment.maxAudioPts) -
58 Math.min(segment.minVideoPts, segment.minAudioPts)) * 0.001;
59 i = j;
42 } 60 }
43 61
44 return result; 62 return result;
......
...@@ -479,8 +479,20 @@ ...@@ -479,8 +479,20 @@
479 h264Tags: function() { 479 h264Tags: function() {
480 return h264Stream.tags.length; 480 return h264Stream.tags.length;
481 }, 481 },
482 minVideoPts: function() {
483 return h264Stream.tags[0].pts;
484 },
485 maxVideoPts: function() {
486 return h264Stream.tags[h264Stream.tags.length - 1].pts;
487 },
482 aacTags: function() { 488 aacTags: function() {
483 return aacStream.tags.length; 489 return aacStream.tags.length;
490 },
491 minAudioPts: function() {
492 return aacStream.tags[0].pts;
493 },
494 maxAudioPts: function() {
495 return aacStream.tags[aacStream.tags.length - 1].pts;
484 } 496 }
485 }; 497 };
486 }; 498 };
......
...@@ -870,15 +870,17 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -870,15 +870,17 @@ videojs.Hls.prototype.drainBuffer = function(event) {
870 870
871 tags = []; 871 tags = [];
872 872
873 while (this.segmentParser_.tagsAvailable()) { 873 if (this.segmentParser_.tagsAvailable()) {
874 tags.push(this.segmentParser_.getNextTag()); 874 // record PTS information for the segment so we can calculate
875 // accurate durations and seek reliably
876 segment.minVideoPts = this.segmentParser_.stats.minVideoPts();
877 segment.maxVideoPts = this.segmentParser_.stats.maxVideoPts();
878 segment.minAudioPts = this.segmentParser_.stats.minAudioPts();
879 segment.maxAudioPts = this.segmentParser_.stats.maxAudioPts();
875 } 880 }
876 881
877 if (tags.length > 0) { 882 while (this.segmentParser_.tagsAvailable()) {
878 // Use the presentation timestamp of the ts segment to calculate its 883 tags.push(this.segmentParser_.getNextTag());
879 // exact duration, since this may differ by fractions of a second
880 // from what is reported in the playlist
881 segment.preciseDuration = videojs.Hls.FlvTag.durationFromTags(tags) * 0.001;
882 } 884 }
883 885
884 this.updateDuration(this.playlists.media()); 886 this.updateDuration(this.playlists.media());
......
...@@ -57,32 +57,4 @@ test('writeBytes grows the internal byte array dynamically', function() { ...@@ -57,32 +57,4 @@ test('writeBytes grows the internal byte array dynamically', function() {
57 } 57 }
58 }); 58 });
59 59
60 test('calculates the duration of a tag array from PTS values', function() {
61 var tags = [], count = 20, i;
62
63 for (i = 0; i < count; i++) {
64 tags[i] = new FlvTag(FlvTag.VIDEO_TAG);
65 tags[i].pts = i * 1000;
66 }
67
68 equal(FlvTag.durationFromTags(tags), count * 1000, 'calculated duration from PTS values');
69 });
70
71 test('durationFromTags() assumes 24fps if the last frame duration cannot be calculated', function() {
72 var tags = [
73 new FlvTag(FlvTag.VIDEO_TAG),
74 new FlvTag(FlvTag.VIDEO_TAG),
75 new FlvTag(FlvTag.VIDEO_TAG)
76 ];
77 tags[0].pts = 0;
78 tags[1].pts = tags[2].pts = 1000;
79
80 equal(FlvTag.durationFromTags(tags), 1000 + (1/24) , 'assumes 24fps video');
81 });
82
83 test('durationFromTags() returns zero if there are less than two frames', function() {
84 equal(FlvTag.durationFromTags([]), 0, 'returns zero for empty input');
85 equal(FlvTag.durationFromTags([new FlvTag(FlvTag.VIDEO_TAG)]), 0, 'returns zero for a singleton input');
86 });
87
88 })(this); 60 })(this);
......
...@@ -331,6 +331,35 @@ ...@@ -331,6 +331,35 @@
331 'requested the media playlist'); 331 'requested the media playlist');
332 }); 332 });
333 333
334 test('preserves segment metadata across playlist refreshes', function() {
335 var loader = new videojs.Hls.PlaylistLoader('live.m3u8'), segment;
336 requests.pop().respond(200, null,
337 '#EXTM3U\n' +
338 '#EXT-X-MEDIA-SEQUENCE:0\n' +
339 '#EXTINF:10,\n' +
340 '0.ts\n' +
341 '#EXTINF:10,\n' +
342 '1.ts\n' +
343 '#EXTINF:10,\n' +
344 '2.ts\n');
345 // add PTS info to 1.ts
346 segment = loader.media().segments[1];
347 segment.minVideoPts = 14;
348 segment.maxAudioPts = 27;
349 segment.preciseDuration = 10.045;
350
351 clock.tick(10 * 1000); // trigger a refresh
352 requests.pop().respond(200, null,
353 '#EXTM3U\n' +
354 '#EXT-X-MEDIA-SEQUENCE:1\n' +
355 '#EXTINF:10,\n' +
356 '1.ts\n' +
357 '#EXTINF:10,\n' +
358 '2.ts\n');
359
360 deepEqual(loader.media().segments[0], segment, 'preserved segment attributes');
361 });
362
334 test('clears the update timeout when switching quality', function() { 363 test('clears the update timeout when switching quality', function() {
335 var loader = new videojs.Hls.PlaylistLoader('live-master.m3u8'), refreshes = 0; 364 var loader = new videojs.Hls.PlaylistLoader('live-master.m3u8'), refreshes = 0;
336 // track the number of playlist refreshes triggered 365 // track the number of playlist refreshes triggered
......
...@@ -38,6 +38,98 @@ ...@@ -38,6 +38,98 @@
38 equal(duration, 14 * 10, 'duration includes dropped segments'); 38 equal(duration, 14 * 10, 'duration includes dropped segments');
39 }); 39 });
40 40
41 test('interval duration uses PTS values when available', function() {
42 var duration = Playlist.duration({
43 mediaSequence: 0,
44 endList: true,
45 segments: [{
46 minVideoPts: 1,
47 minAudioPts: 2,
48 uri: '0.ts'
49 }, {
50 duration: 10,
51 maxVideoPts: 2 * 10 * 1000 + 1,
52 maxAudioPts: 2 * 10 * 1000 + 2,
53 uri: '1.ts'
54 }, {
55 duration: 10,
56 maxVideoPts: 3 * 10 * 1000 + 1,
57 maxAudioPts: 3 * 10 * 1000 + 2,
58 uri: '2.ts'
59 }, {
60 duration: 10,
61 maxVideoPts: 4 * 10 * 1000 + 1,
62 maxAudioPts: 4 * 10 * 1000 + 2,
63 uri: '3.ts'
64 }]
65 }, 0, 4);
66
67 equal(duration, ((4 * 10 * 1000 + 2) - 1) * 0.001, 'used PTS values');
68 });
69
70 test('interval duration works when partial PTS information is available', function() {
71 var firstInterval, secondInterval, duration = Playlist.duration({
72 mediaSequence: 0,
73 endList: true,
74 segments: [{
75 minVideoPts: 1,
76 minAudioPts: 2,
77 maxVideoPts: 1 * 10 * 1000 + 1,
78
79 // intentionally less duration than video
80 // the max stream duration should be used
81 maxAudioPts: 1 * 10 * 1000 + 1,
82 uri: '0.ts'
83 }, {
84 duration: 10,
85 uri: '1.ts'
86 }, {
87 duration: 10,
88 minVideoPts: 2 * 10 * 1000 + 7,
89 minAudioPts: 2 * 10 * 1000 + 10,
90 maxVideoPts: 3 * 10 * 1000 + 1,
91 maxAudioPts: 3 * 10 * 1000 + 2,
92 uri: '2.ts'
93 }, {
94 duration: 10,
95 maxVideoPts: 4 * 10 * 1000 + 1,
96 maxAudioPts: 4 * 10 * 1000 + 2,
97 uri: '3.ts'
98 }]
99 }, 0, 4);
100
101 firstInterval = (1 * 10 * 1000 + 1) - 1;
102 firstInterval *= 0.001;
103 secondInterval = (4 * 10 * 1000 + 2) - (2 * 10 * 1000 + 7);
104 secondInterval *= 0.001;
105
106 equal(duration, firstInterval + 10 + secondInterval, 'calculated with mixed intervals');
107 });
108
109 test('interval duration accounts for discontinuities', function() {
110 var duration = Playlist.duration({
111 mediaSequence: 0,
112 endList: true,
113 segments: [{
114 minVideoPts: 0,
115 minAudioPts: 0,
116 maxVideoPts: 1 * 10 * 1000,
117 maxAudioPts: 1 * 10 * 1000,
118 uri: '0.ts'
119 }, {
120 discontinuity: true,
121 minVideoPts: 2 * 10 * 1000,
122 minAudioPts: 2 * 10 * 1000,
123 maxVideoPts: 3 * 10 * 1000,
124 maxAudioPts: 3 * 10 * 1000,
125 duration: 10,
126 uri: '1.ts'
127 }]
128 }, 0, 2);
129
130 equal(duration, 10 + 10, 'handles discontinuities');
131 });
132
41 test('calculates seekable time ranges from the available segments', function() { 133 test('calculates seekable time ranges from the available segments', function() {
42 var playlist = { 134 var playlist = {
43 mediaSequence: 0, 135 mediaSequence: 0,
......
...@@ -422,10 +422,19 @@ ...@@ -422,10 +422,19 @@
422 byte, 422 byte,
423 tag, 423 tag,
424 type, 424 type,
425 minVideoPts,
426 maxVideoPts,
427 minAudioPts,
428 maxAudioPts,
425 currentPts = 0, 429 currentPts = 0,
426 lastTime = 0; 430 lastTime = 0;
427 parser.parseSegmentBinaryData(window.bcSegment); 431 parser.parseSegmentBinaryData(window.bcSegment);
428 432
433 minVideoPts = parser.stats.minVideoPts();
434 maxVideoPts = parser.stats.maxVideoPts();
435 minAudioPts = parser.stats.minAudioPts();
436 maxAudioPts = parser.stats.maxAudioPts();
437
429 while (parser.tagsAvailable()) { 438 while (parser.tagsAvailable()) {
430 tag = parser.getNextTag(); 439 tag = parser.getNextTag();
431 type = tag.bytes[0]; 440 type = tag.bytes[0];
...@@ -435,11 +444,15 @@ ...@@ -435,11 +444,15 @@
435 444
436 // generic flv headers 445 // generic flv headers
437 switch (type) { 446 switch (type) {
438 case 8: ok(true, 'the type is audio'); 447 case 8: ok(true, 'the type is audio');
448 ok(minAudioPts <= currentPts, 'not less than minimum audio PTS');
449 ok(maxAudioPts >= currentPts, 'not greater than max audio PTS');
439 break; 450 break;
440 case 9: ok(true, 'the type is video'); 451 case 9: ok(true, 'the type is video');
452 ok(minVideoPts <= currentPts, 'not less than minimum video PTS');
453 ok(maxVideoPts >= currentPts, 'not greater than max video PTS');
441 break; 454 break;
442 case 18: ok(true, 'the type is script'); 455 case 18: ok(true, 'the type is script');
443 break; 456 break;
444 default: ok(false, 'the type (' + type + ') is unrecognized'); 457 default: ok(false, 'the type (' + type + ') is unrecognized');
445 } 458 }
......
...@@ -94,14 +94,18 @@ var ...@@ -94,14 +94,18 @@ var
94 }, 94 },
95 95
96 mockSegmentParser = function(tags) { 96 mockSegmentParser = function(tags) {
97 var MockSegmentParser;
98
97 if (tags === undefined) { 99 if (tags === undefined) {
98 tags = []; 100 tags = [];
99 } 101 }
100 return function() { 102 MockSegmentParser = function() {
101 this.getFlvHeader = function() { 103 this.getFlvHeader = function() {
102 return 'flv'; 104 return 'flv';
103 }; 105 };
104 this.parseSegmentBinaryData = function() {}; 106 this.parseSegmentBinaryData = function() {};
107 this.timestampOffset = 0;
108 this.mediaTimelineOffset = 0;
105 this.flushTags = function() {}; 109 this.flushTags = function() {};
106 this.tagsAvailable = function() { 110 this.tagsAvailable = function() {
107 return tags.length; 111 return tags.length;
...@@ -112,10 +116,31 @@ var ...@@ -112,10 +116,31 @@ var
112 this.getNextTag = function() { 116 this.getNextTag = function() {
113 return tags.shift(); 117 return tags.shift();
114 }; 118 };
115 this.metadataStream = { 119 this.metadataStream = new videojs.Hls.Stream();
116 on: Function.prototype 120 this.metadataStream.init();
121 this.metadataStream.descriptor = new Uint8Array([
122 1, 2, 3, 0xbb
123 ]);
124
125 this.stats = {
126 minVideoPts: function() {
127 return tags[0].pts;
128 },
129 maxVideoPts: function() {
130 return tags[tags.length - 1].pts;
131 },
132 minAudioPts: function() {
133 return tags[0].pts;
134 },
135 maxAudioPts: function() {
136 return tags[tags.length - 1].pts;
137 },
117 }; 138 };
118 }; 139 };
140
141 MockSegmentParser.STREAM_TYPES = videojs.Hls.SegmentParser.STREAM_TYPES;
142
143 return MockSegmentParser;
119 }, 144 },
120 145
121 // return an absolute version of a page-relative URL 146 // return an absolute version of a page-relative URL
...@@ -1001,6 +1026,26 @@ test('only appends one segment at a time', function() { ...@@ -1001,6 +1026,26 @@ test('only appends one segment at a time', function() {
1001 equal(appends, 0, 'did not append while updating'); 1026 equal(appends, 0, 'did not append while updating');
1002 }); 1027 });
1003 1028
1029 test('records the min and max PTS values for a segment', function() {
1030 var tags = [];
1031 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1032 player.src({
1033 src: 'manifest/media.m3u8',
1034 type: 'application/vnd.apple.mpegurl'
1035 });
1036 openMediaSource(player);
1037 standardXHRResponse(requests.pop()); // media.m3u8
1038
1039 tags.push({ pts: 0, bytes: new Uint8Array(1) });
1040 tags.push({ pts: 10, bytes: new Uint8Array(1) });
1041 standardXHRResponse(requests.pop()); // segment 0
1042
1043 equal(player.hls.playlists.media().segments[0].minVideoPts, 0, 'recorded min video pts');
1044 equal(player.hls.playlists.media().segments[0].maxVideoPts, 10, 'recorded max video pts');
1045 equal(player.hls.playlists.media().segments[0].minAudioPts, 0, 'recorded min audio pts');
1046 equal(player.hls.playlists.media().segments[0].maxAudioPts, 10, 'recorded max audio pts');
1047 });
1048
1004 test('waits to download new segments until the media playlist is stable', function() { 1049 test('waits to download new segments until the media playlist is stable', function() {
1005 var media; 1050 var media;
1006 player.src({ 1051 player.src({
...@@ -1140,58 +1185,9 @@ test('flushes the parser after each segment', function() { ...@@ -1140,58 +1185,9 @@ test('flushes the parser after each segment', function() {
1140 strictEqual(flushes, 1, 'tags are flushed at the end of a segment'); 1185 strictEqual(flushes, 1, 'tags are flushed at the end of a segment');
1141 }); 1186 });
1142 1187
1143 test('calculates preciseDuration for a new segment', function() {
1144 var tags = [
1145 { pts : 200 * 1000, bytes: new Uint8Array(1) },
1146 { pts : 300 * 1000, bytes: new Uint8Array(1) }
1147 ];
1148 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1149
1150 player.src({
1151 src: 'manifest/media.m3u8',
1152 type: 'application/vnd.apple.mpegurl'
1153 });
1154 openMediaSource(player);
1155
1156 standardXHRResponse(requests[0]);
1157 strictEqual(player.duration(), 40, 'player duration is read from playlist on load');
1158 standardXHRResponse(requests[1]);
1159 strictEqual(player.hls.playlists.media().segments[0].preciseDuration, 200, 'preciseDuration is calculated and stored');
1160 strictEqual(player.duration(), 230, 'player duration is calculated using preciseDuration');
1161 });
1162
1163 test('calculates preciseDuration correctly around discontinuities', function() {
1164 var tags = [];
1165 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1166 player.src({
1167 src: 'manifest/media.m3u8',
1168 type: 'application/vnd.apple.mpegurl'
1169 });
1170 openMediaSource(player);
1171 requests.shift().respond(200, null,
1172 '#EXTM3U\n' +
1173 '#EXTINF:10,\n' +
1174 '0.ts\n' +
1175 '#EXT-X-DISCONTINUITY\n' +
1176 '#EXTINF:10,\n' +
1177 '1.ts\n' +
1178 '#EXT-X-ENDLIST\n');
1179 tags.push({ pts: 10 * 1000, bytes: new Uint8Array(1) });
1180 standardXHRResponse(requests.shift()); // segment 0
1181 player.hls.checkBuffer_();
1182
1183 // the PTS value of the second segment is *earlier* than the first
1184 tags.push({ pts: 0 * 1000, bytes: new Uint8Array(1) });
1185 tags.push({ pts: 5 * 1000, bytes: new Uint8Array(1) });
1186 standardXHRResponse(requests.shift()); // segment 1
1187
1188 equal(player.hls.playlists.media().segments[1].preciseDuration,
1189 5 + 5, // duration includes the time to display the second tag
1190 'duration is independent of previous segments');
1191 });
1192
1193 test('exposes in-band metadata events as cues', function() { 1188 test('exposes in-band metadata events as cues', function() {
1194 var track; 1189 var track;
1190 videojs.Hls.SegmentParser = mockSegmentParser();
1195 player.src({ 1191 player.src({
1196 src: 'manifest/media.m3u8', 1192 src: 'manifest/media.m3u8',
1197 type: 'application/vnd.apple.mpegurl' 1193 type: 'application/vnd.apple.mpegurl'
...@@ -1199,10 +1195,6 @@ test('exposes in-band metadata events as cues', function() { ...@@ -1199,10 +1195,6 @@ test('exposes in-band metadata events as cues', function() {
1199 openMediaSource(player); 1195 openMediaSource(player);
1200 1196
1201 player.hls.segmentParser_.parseSegmentBinaryData = function() { 1197 player.hls.segmentParser_.parseSegmentBinaryData = function() {
1202 // fake out a descriptor
1203 player.hls.segmentParser_.metadataStream.descriptor = new Uint8Array([
1204 1, 2, 3, 0xbb
1205 ]);
1206 // trigger a metadata event 1198 // trigger a metadata event
1207 player.hls.segmentParser_.metadataStream.trigger('data', { 1199 player.hls.segmentParser_.metadataStream.trigger('data', {
1208 pts: 2000, 1200 pts: 2000,
...@@ -1251,23 +1243,14 @@ test('exposes in-band metadata events as cues', function() { ...@@ -1251,23 +1243,14 @@ test('exposes in-band metadata events as cues', function() {
1251 1243
1252 test('only adds in-band cues the first time they are encountered', function() { 1244 test('only adds in-band cues the first time they are encountered', function() {
1253 var tags = [{ pts: 0, bytes: new Uint8Array(1) }], track; 1245 var tags = [{ pts: 0, bytes: new Uint8Array(1) }], track;
1246 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1254 player.src({ 1247 player.src({
1255 src: 'manifest/media.m3u8', 1248 src: 'manifest/media.m3u8',
1256 type: 'application/vnd.apple.mpegurl' 1249 type: 'application/vnd.apple.mpegurl'
1257 }); 1250 });
1258 openMediaSource(player); 1251 openMediaSource(player);
1259 1252
1260 player.hls.segmentParser_.getNextTag = function() {
1261 return tags.shift();
1262 };
1263 player.hls.segmentParser_.tagsAvailable = function() {
1264 return tags.length;
1265 };
1266 player.hls.segmentParser_.parseSegmentBinaryData = function() { 1253 player.hls.segmentParser_.parseSegmentBinaryData = function() {
1267 // fake out a descriptor
1268 player.hls.segmentParser_.metadataStream.descriptor = new Uint8Array([
1269 1, 2, 3, 0xbb
1270 ]);
1271 // trigger a metadata event 1254 // trigger a metadata event
1272 player.hls.segmentParser_.metadataStream.trigger('data', { 1255 player.hls.segmentParser_.metadataStream.trigger('data', {
1273 pts: 2000, 1256 pts: 2000,
...@@ -1295,23 +1278,14 @@ test('clears in-band cues ahead of current time on seek', function() { ...@@ -1295,23 +1278,14 @@ test('clears in-band cues ahead of current time on seek', function() {
1295 tags = [], 1278 tags = [],
1296 events = [], 1279 events = [],
1297 track; 1280 track;
1281 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1298 player.src({ 1282 player.src({
1299 src: 'manifest/media.m3u8', 1283 src: 'manifest/media.m3u8',
1300 type: 'application/vnd.apple.mpegurl' 1284 type: 'application/vnd.apple.mpegurl'
1301 }); 1285 });
1302 openMediaSource(player); 1286 openMediaSource(player);
1303 1287
1304 player.hls.segmentParser_.getNextTag = function() {
1305 return tags.shift();
1306 };
1307 player.hls.segmentParser_.tagsAvailable = function() {
1308 return tags.length;
1309 };
1310 player.hls.segmentParser_.parseSegmentBinaryData = function() { 1288 player.hls.segmentParser_.parseSegmentBinaryData = function() {
1311 // fake out a descriptor
1312 player.hls.segmentParser_.metadataStream.descriptor = new Uint8Array([
1313 1, 2, 3, 0xbb
1314 ]);
1315 // trigger a metadata event 1289 // trigger a metadata event
1316 if (events.length) { 1290 if (events.length) {
1317 player.hls.segmentParser_.metadataStream.trigger('data', events.shift()); 1291 player.hls.segmentParser_.metadataStream.trigger('data', events.shift());
...@@ -1360,26 +1334,17 @@ test('clears in-band cues ahead of current time on seek', function() { ...@@ -1360,26 +1334,17 @@ test('clears in-band cues ahead of current time on seek', function() {
1360 1334
1361 test('translates ID3 PTS values to cue media timeline positions', function() { 1335 test('translates ID3 PTS values to cue media timeline positions', function() {
1362 var tags = [{ pts: 4 * 1000, bytes: new Uint8Array(1) }], track; 1336 var tags = [{ pts: 4 * 1000, bytes: new Uint8Array(1) }], track;
1337 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1363 player.src({ 1338 player.src({
1364 src: 'manifest/media.m3u8', 1339 src: 'manifest/media.m3u8',
1365 type: 'application/vnd.apple.mpegurl' 1340 type: 'application/vnd.apple.mpegurl'
1366 }); 1341 });
1367 openMediaSource(player); 1342 openMediaSource(player);
1368 1343
1369 player.hls.segmentParser_.getNextTag = function() {
1370 return tags.shift();
1371 };
1372 player.hls.segmentParser_.tagsAvailable = function() {
1373 return tags.length;
1374 };
1375 player.hls.segmentParser_.parseSegmentBinaryData = function() { 1344 player.hls.segmentParser_.parseSegmentBinaryData = function() {
1376 // setup the timestamp offset 1345 // setup the timestamp offset
1377 this.timestampOffset = tags[0].pts; 1346 this.timestampOffset = tags[0].pts;
1378 1347
1379 // fake out a descriptor
1380 player.hls.segmentParser_.metadataStream.descriptor = new Uint8Array([
1381 1, 2, 3, 0xbb
1382 ]);
1383 // trigger a metadata event 1348 // trigger a metadata event
1384 player.hls.segmentParser_.metadataStream.trigger('data', { 1349 player.hls.segmentParser_.metadataStream.trigger('data', {
1385 pts: 5 * 1000, 1350 pts: 5 * 1000,
...@@ -1400,26 +1365,17 @@ test('translates ID3 PTS values to cue media timeline positions', function() { ...@@ -1400,26 +1365,17 @@ test('translates ID3 PTS values to cue media timeline positions', function() {
1400 1365
1401 test('translates ID3 PTS values across discontinuities', function() { 1366 test('translates ID3 PTS values across discontinuities', function() {
1402 var tags = [], events = [], track; 1367 var tags = [], events = [], track;
1368 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1403 player.src({ 1369 player.src({
1404 src: 'cues-and-discontinuities.m3u8', 1370 src: 'cues-and-discontinuities.m3u8',
1405 type: 'application/vnd.apple.mpegurl' 1371 type: 'application/vnd.apple.mpegurl'
1406 }); 1372 });
1407 openMediaSource(player); 1373 openMediaSource(player);
1408 1374
1409 player.hls.segmentParser_.getNextTag = function() {
1410 return tags.shift();
1411 };
1412 player.hls.segmentParser_.tagsAvailable = function() {
1413 return tags.length;
1414 };
1415 player.hls.segmentParser_.parseSegmentBinaryData = function() { 1375 player.hls.segmentParser_.parseSegmentBinaryData = function() {
1416 if (this.timestampOffset === null) { 1376 if (this.timestampOffset === null) {
1417 this.timestampOffset = tags[0].pts; 1377 this.timestampOffset = tags[0].pts;
1418 } 1378 }
1419 // fake out a descriptor
1420 player.hls.segmentParser_.metadataStream.descriptor = new Uint8Array([
1421 1, 2, 3, 0xbb
1422 ]);
1423 // trigger a metadata event 1379 // trigger a metadata event
1424 if (events.length) { 1380 if (events.length) {
1425 player.hls.segmentParser_.metadataStream.trigger('data', events.shift()); 1381 player.hls.segmentParser_.metadataStream.trigger('data', events.shift());
...@@ -1437,7 +1393,9 @@ test('translates ID3 PTS values across discontinuities', function() { ...@@ -1437,7 +1393,9 @@ test('translates ID3 PTS values across discontinuities', function() {
1437 '1.ts\n'); 1393 '1.ts\n');
1438 1394
1439 // segment 0 starts at PTS 14000 and has a cue point at 15000 1395 // segment 0 starts at PTS 14000 and has a cue point at 15000
1440 tags.push({ pts: 14 * 1000, bytes: new Uint8Array(1) }); 1396 player.hls.segmentParser_.timestampOffset = 14 * 1000;
1397 tags.push({ pts: 14 * 1000, bytes: new Uint8Array(1) },
1398 { pts: 24 * 1000, bytes: new Uint8Array(1) });
1441 events.push({ 1399 events.push({
1442 pts: 15 * 1000, 1400 pts: 15 * 1000,
1443 data: new Uint8Array([]), 1401 data: new Uint8Array([]),
...@@ -1449,14 +1407,14 @@ test('translates ID3 PTS values across discontinuities', function() { ...@@ -1449,14 +1407,14 @@ test('translates ID3 PTS values across discontinuities', function() {
1449 standardXHRResponse(requests.shift()); // segment 0 1407 standardXHRResponse(requests.shift()); // segment 0
1450 1408
1451 // segment 1 is after a discontinuity, starts at PTS 22000 1409 // segment 1 is after a discontinuity, starts at PTS 22000
1452 // and has a cue point at 15000 1410 // and has a cue point at 23000
1453 tags.push({ pts: 22 * 1000, bytes: new Uint8Array(1) }); 1411 tags.push({ pts: 22 * 1000, bytes: new Uint8Array(1) });
1454 events.push({ 1412 events.push({
1455 pts: 23 * 1000, 1413 pts: 23 * 1000,
1456 data: new Uint8Array([]), 1414 data: new Uint8Array([]),
1457 frames: [{ 1415 frames: [{
1458 id: 'TXXX', 1416 id: 'TXXX',
1459 value: 'cue 0' 1417 value: 'cue 1'
1460 }] 1418 }]
1461 }); 1419 });
1462 player.hls.checkBuffer_(); 1420 player.hls.checkBuffer_();
......