9ed1b5bb by Jon-Carlos Rivera

Fix time correction.. some more (#712)

* Fudge segments that are reported as having a zero-second duration
  * The fetcher logic basically ignores segments with a duration of zero. Give them a tiny duration so that the fetcher will "see" these segments.
* Added tests for and fixed getSegmentBufferedPercent_ calculations
* Now returns the percent buffered of the entire segment duration instead of the "adjusted" duration
* Handles segments reported as having a zero-duration
* Reduced the number of segments that we will attempt to "timeCorrect" when the segment chosen by `checkBuffer_` is already more than 90% buffered to 1
* No longer trigger errors from `timeCorrection_` handling, returning to the previous behavior
* Use the tech's setCurrentTime function in segment loaders
* Moved getSegmentBufferedPercent to `Ranges` module
* Moved correction for zero-duration segments from parse-stream to parser proper
1 parent 7748d1f1
...@@ -108,10 +108,17 @@ export default class Parser extends Stream { ...@@ -108,10 +108,17 @@ export default class Parser extends Stream {
108 message: 'defaulting discontinuity sequence to zero' 108 message: 'defaulting discontinuity sequence to zero'
109 }); 109 });
110 } 110 }
111 if (entry.duration >= 0) { 111 if (entry.duration > 0) {
112 currentUri.duration = entry.duration; 112 currentUri.duration = entry.duration;
113 } 113 }
114 114
115 if (entry.duration === 0) {
116 currentUri.duration = 0.01;
117 this.trigger('info', {
118 message: 'updating zero segment duration to a small value'
119 });
120 }
121
115 this.manifest.segments = uris; 122 this.manifest.segments = uris;
116 }, 123 },
117 key() { 124 key() {
......
...@@ -72,7 +72,7 @@ export default class MasterPlaylistController extends videojs.EventTarget { ...@@ -72,7 +72,7 @@ export default class MasterPlaylistController extends videojs.EventTarget {
72 withCredentials: this.withCredentials, 72 withCredentials: this.withCredentials,
73 seekable: () => this.seekable(), 73 seekable: () => this.seekable(),
74 seeking: () => this.tech_.seeking(), 74 seeking: () => this.tech_.seeking(),
75 setCurrentTime: (a) => this.setCurrentTime(a), 75 setCurrentTime: (a) => this.tech_.setCurrentTime(a),
76 hasPlayed: () => this.tech_.played().length !== 0, 76 hasPlayed: () => this.tech_.played().length !== 0,
77 bandwidth 77 bandwidth
78 }; 78 };
......
...@@ -10,6 +10,16 @@ import videojs from 'video.js'; ...@@ -10,6 +10,16 @@ import videojs from 'video.js';
10 // Fudge factor to account for TimeRanges rounding 10 // Fudge factor to account for TimeRanges rounding
11 const TIME_FUDGE_FACTOR = 1 / 30; 11 const TIME_FUDGE_FACTOR = 1 / 30;
12 12
13 /**
14 * Clamps a value to within a range
15 * @param {Number} num - the value to clamp
16 * @param {Number} start - the start of the range to clamp within, inclusive
17 * @param {Number} end - the end of the range to clamp within, inclusive
18 * @return {Number}
19 */
20 const clamp = function(num, [start, end]) {
21 return Math.min(Math.max(start, num), end);
22 };
13 const filterRanges = function(timeRanges, predicate) { 23 const filterRanges = function(timeRanges, predicate) {
14 let results = []; 24 let results = [];
15 let i; 25 let i;
...@@ -184,27 +194,81 @@ const bufferIntersection = function(bufferA, bufferB) { ...@@ -184,27 +194,81 @@ const bufferIntersection = function(bufferA, bufferB) {
184 /** 194 /**
185 * Calculates the percentage of `segmentRange` that overlaps the 195 * Calculates the percentage of `segmentRange` that overlaps the
186 * `buffered` time ranges. 196 * `buffered` time ranges.
187 * @param {TimeRanges} segmentRange - the time range that the segment covers 197 * @param {TimeRanges} segmentRange - the time range that the segment
198 * covers adjusted according to currentTime
199 * @param {TimeRanges} referenceRange - the original time range that the
200 * segment covers
188 * @param {TimeRanges} buffered - the currently buffered time ranges 201 * @param {TimeRanges} buffered - the currently buffered time ranges
189 * @returns {Number} percent of the segment currently buffered 202 * @returns {Number} percent of the segment currently buffered
190 */ 203 */
191 const calculateBufferedPercent = function(segmentRange, buffered) { 204 const calculateBufferedPercent = function(segmentRange, referenceRange, buffered) {
205 let referenceDuration = referenceRange.end(0) - referenceRange.start(0);
192 let segmentDuration = segmentRange.end(0) - segmentRange.start(0); 206 let segmentDuration = segmentRange.end(0) - segmentRange.start(0);
193 let intersection = bufferIntersection(segmentRange, buffered); 207 let intersection = bufferIntersection(segmentRange, buffered);
194 let overlapDuration = 0;
195 let count = intersection.length; 208 let count = intersection.length;
196 209
197 while (count--) { 210 while (count--) {
198 overlapDuration += intersection.end(count) - intersection.start(count); 211 segmentDuration -= intersection.end(count) - intersection.start(count);
212 }
213 return (referenceDuration - segmentDuration) / referenceDuration * 100;
214 };
215
216 /**
217 * Return the amount of a segment specified by the mediaIndex overlaps
218 * the current buffered content.
219 *
220 * @param {Number} startOfSegment - the time where the segment begins
221 * @param {Number} segmentDuration - the duration of the segment in seconds
222 * @param {TimeRanges} buffered - the state of the buffer
223 * @returns {Number} percentage of the segment's time range that is
224 * already in `buffered`
225 */
226 const getSegmentBufferedPercent = function(startOfSegment,
227 segmentDuration,
228 currentTime,
229 buffered) {
230 let endOfSegment = startOfSegment + segmentDuration;
231
232 // The entire time range of the segment
233 let originalSegmentRange = videojs.createTimeRanges([[
234 startOfSegment,
235 endOfSegment
236 ]]);
237
238 // The adjusted segment time range that is setup such that it starts
239 // no earlier than currentTime
240 // Flash has no notion of a back-buffer so adjustedSegmentRange adjusts
241 // for that and the function will still return 100% if a only half of a
242 // segment is actually in the buffer as long as the currentTime is also
243 // half-way through the segment
244 let adjustedSegmentRange = videojs.createTimeRanges([[
245 clamp(startOfSegment, [currentTime, endOfSegment]),
246 endOfSegment
247 ]]);
248
249 // This condition happens when the currentTime is beyond the segment's
250 // end time
251 if (adjustedSegmentRange.start(0) === adjustedSegmentRange.end(0)) {
252 return 0;
253 }
254
255 let percent = calculateBufferedPercent(adjustedSegmentRange,
256 originalSegmentRange,
257 buffered);
258
259 // If the segment is reported as having a zero duration, return 0%
260 // since it is likely that we will need to fetch the segment
261 if (isNaN(percent) || percent === Infinity || percent === -Infinity) {
262 return 0;
199 } 263 }
200 264
201 return (overlapDuration / segmentDuration) * 100; 265 return percent;
202 }; 266 };
203 267
204 export default { 268 export default {
205 findRange, 269 findRange,
206 findNextRange, 270 findNextRange,
207 findSoleUncommonTimeRangesEnd, 271 findSoleUncommonTimeRangesEnd,
208 calculateBufferedPercent, 272 getSegmentBufferedPercent,
209 TIME_FUDGE_FACTOR 273 TIME_FUDGE_FACTOR
210 }; 274 };
......
...@@ -313,29 +313,6 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -313,29 +313,6 @@ export default class SegmentLoader extends videojs.EventTarget {
313 } 313 }
314 314
315 /** 315 /**
316 * Return the amount of a segment specified by the mediaIndex overlaps
317 * the current buffered content.
318 *
319 * @param {Object} playlist the playlist object to fetch segments from
320 * @param {Number} mediaIndex the index of the segment in the playlist
321 * @param {TimeRanges} buffered the state of the buffer
322 * @returns {Number} percentage of the segment's time range that is
323 * already in `buffered`
324 */
325 getSegmentBufferedPercent_(playlist, mediaIndex, currentTime, buffered) {
326 let segment = playlist.segments[mediaIndex];
327 let startOfSegment = duration(playlist,
328 playlist.mediaSequence + mediaIndex,
329 this.expired_);
330 let segmentRange = videojs.createTimeRanges([[
331 Math.max(currentTime, startOfSegment),
332 startOfSegment + segment.duration
333 ]]);
334
335 return Ranges.calculateBufferedPercent(segmentRange, buffered);
336 }
337
338 /**
339 * Determines what segment request should be made, given current 316 * Determines what segment request should be made, given current
340 * playback state. 317 * playback state.
341 * 318 *
...@@ -432,7 +409,9 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -432,7 +409,9 @@ export default class SegmentLoader extends videojs.EventTarget {
432 // to the source buffer 409 // to the source buffer
433 timestampOffset, 410 timestampOffset,
434 // The timeline that the segment is in 411 // The timeline that the segment is in
435 timeline: segment.timeline 412 timeline: segment.timeline,
413 // The expected duration of the segment in seconds
414 duration: segment.duration
436 }; 415 };
437 } 416 }
438 417
...@@ -471,12 +450,23 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -471,12 +450,23 @@ export default class SegmentLoader extends videojs.EventTarget {
471 return; 450 return;
472 } 451 }
473 452
453 if (request.mediaIndex === this.playlist_.segments.length - 1 &&
454 this.mediaSource_.readyState === 'ended' &&
455 !this.seeking_()) {
456 return;
457 }
458
459 let segment = this.playlist_.segments[request.mediaIndex];
460 let startOfSegment = duration(this.playlist_,
461 this.playlist_.mediaSequence + request.mediaIndex,
462 this.expired_);
463
474 // Sanity check the segment-index determining logic by calcuating the 464 // Sanity check the segment-index determining logic by calcuating the
475 // percentage of the chosen segment that is buffered. If more than 90% 465 // percentage of the chosen segment that is buffered. If more than 90%
476 // of the segment is buffered then fetching it will likely not help in 466 // of the segment is buffered then fetching it will likely not help in
477 // any way 467 // any way
478 let percentBuffered = this.getSegmentBufferedPercent_(this.playlist_, 468 let percentBuffered = Ranges.getSegmentBufferedPercent(startOfSegment,
479 request.mediaIndex, 469 segment.duration,
480 this.currentTime_(), 470 this.currentTime_(),
481 this.sourceUpdater_.buffered()); 471 this.sourceUpdater_.buffered());
482 472
...@@ -484,9 +474,9 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -484,9 +474,9 @@ export default class SegmentLoader extends videojs.EventTarget {
484 // Increment the timeCorrection_ variable to push the fetcher forward 474 // Increment the timeCorrection_ variable to push the fetcher forward
485 // in time and hopefully skip any gaps or flaws in our understanding 475 // in time and hopefully skip any gaps or flaws in our understanding
486 // of the media 476 // of the media
487 this.incrementTimeCorrection_(this.playlist_.targetDuration); 477 let correctionApplied = this.incrementTimeCorrection_(this.playlist_.targetDuration, 1);
488 478
489 if (!this.paused()) { 479 if (correctionApplied && !this.paused()) {
490 this.fillBuffer_(); 480 this.fillBuffer_();
491 } 481 }
492 482
...@@ -761,7 +751,8 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -761,7 +751,8 @@ export default class SegmentLoader extends videojs.EventTarget {
761 751
762 // add segment metadata if it we have gained information during the 752 // add segment metadata if it we have gained information during the
763 // last append 753 // last append
764 this.updateTimeline_(segmentInfo); 754 let timelineUpdated = this.updateTimeline_(segmentInfo);
755
765 this.trigger('progress'); 756 this.trigger('progress');
766 757
767 let currentMediaIndex = segmentInfo.mediaIndex; 758 let currentMediaIndex = segmentInfo.mediaIndex;
...@@ -805,7 +796,22 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -805,7 +796,22 @@ export default class SegmentLoader extends videojs.EventTarget {
805 796
806 this.state = 'READY'; 797 this.state = 'READY';
807 798
808 if (!this.paused()) { 799 if (timelineUpdated) {
800 this.timeCorrection_ = 0;
801 if (!this.paused()) {
802 this.fillBuffer_();
803 }
804 return;
805 }
806
807 // the last segment append must have been entirely in the
808 // already buffered time ranges. adjust the timeCorrection
809 // offset to fetch forward until we find a segment that adds
810 // to the buffered time ranges and improves subsequent media
811 // index calculations.
812 let correctionApplied = this.incrementTimeCorrection_(segmentInfo.duration, 4);
813
814 if (correctionApplied && !this.paused()) {
809 this.fillBuffer_(); 815 this.fillBuffer_();
810 } 816 }
811 } 817 }
...@@ -820,8 +826,7 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -820,8 +826,7 @@ export default class SegmentLoader extends videojs.EventTarget {
820 updateTimeline_(segmentInfo) { 826 updateTimeline_(segmentInfo) {
821 let segment; 827 let segment;
822 let segmentEnd; 828 let segmentEnd;
823 let timelineUpdated; 829 let timelineUpdated = false;
824 let segmentLength = this.playlist_.targetDuration;
825 let playlist = segmentInfo.playlist; 830 let playlist = segmentInfo.playlist;
826 let currentMediaIndex = segmentInfo.mediaIndex; 831 let currentMediaIndex = segmentInfo.mediaIndex;
827 832
...@@ -837,19 +842,9 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -837,19 +842,9 @@ export default class SegmentLoader extends videojs.EventTarget {
837 timelineUpdated = updateSegmentMetadata(playlist, 842 timelineUpdated = updateSegmentMetadata(playlist,
838 currentMediaIndex, 843 currentMediaIndex,
839 segmentEnd); 844 segmentEnd);
840 segmentLength = segment.duration;
841 } 845 }
842 846
843 // the last segment append must have been entirely in the 847 return timelineUpdated;
844 // already buffered time ranges. adjust the timeCorrection
845 // offset to fetch forward until we find a segment that adds
846 // to the buffered time ranges and improves subsequent media
847 // index calculations.
848 if (!timelineUpdated) {
849 this.incrementTimeCorrection_(segmentLength);
850 } else {
851 this.timeCorrection_ = 0;
852 }
853 } 848 }
854 849
855 /** 850 /**
...@@ -862,17 +857,18 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -862,17 +857,18 @@ export default class SegmentLoader extends videojs.EventTarget {
862 * @private 857 * @private
863 * @param {Number} secondsToIncrement number of seconds to add to the 858 * @param {Number} secondsToIncrement number of seconds to add to the
864 * timeCorrection_ variable 859 * timeCorrection_ variable
860 * @param {Number} maxSegmentsToWalk maximum number of times we allow this
861 * function to walk forward
865 */ 862 */
866 incrementTimeCorrection_(secondsToIncrement) { 863 incrementTimeCorrection_(secondsToIncrement, maxSegmentsToWalk) {
867 // If we have already incremented timeCorrection_ beyond the limit, 864 // If we have already incremented timeCorrection_ beyond the limit,
868 // then stop trying to find a segment, pause fetching, and emit an 865 // stop searching for a segment and reset timeCorrection_
869 // error event 866 if (this.timeCorrection_ >= this.playlist_.targetDuration * maxSegmentsToWalk) {
870 if (this.timeCorrection_ >= this.playlist_.targetDuration * 5) {
871 this.timeCorrection_ = 0; 867 this.timeCorrection_ = 0;
872 this.pause(); 868 return false;
873 return this.trigger('error');
874 } 869 }
875 870
876 this.timeCorrection_ += secondsToIncrement; 871 this.timeCorrection_ += secondsToIncrement;
872 return true;
877 } 873 }
878 } 874 }
......
...@@ -91,3 +91,78 @@ QUnit.test('detects time range end-point changed by updates', function() { ...@@ -91,3 +91,78 @@ QUnit.test('detects time range end-point changed by updates', function() {
91 edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 11]]), null); 91 edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 11]]), null);
92 QUnit.strictEqual(edge, null, 'treat null update buffer as an empty TimeRanges object'); 92 QUnit.strictEqual(edge, null, 'treat null update buffer as an empty TimeRanges object');
93 }); 93 });
94
95 QUnit.module('Segment Percent Buffered Calculations');
96
97 QUnit.test('calculates the percent buffered for segments', function() {
98 let segmentStart = 10;
99 let segmentDuration = 10;
100 let currentTime = 0;
101 let buffered = createTimeRanges([[15, 19]]);
102 let percentBuffered = Ranges.getSegmentBufferedPercent(
103 segmentStart,
104 segmentDuration,
105 currentTime,
106 buffered);
107
108 QUnit.equal(percentBuffered, 40, 'calculated the buffered amount correctly');
109 });
110
111 QUnit.test('calculates the percent buffered for segments taking into account ' +
112 'currentTime', function() {
113 let segmentStart = 10;
114 let segmentDuration = 10;
115 let currentTime = 15;
116 let buffered = createTimeRanges([[15, 19]]);
117 let percentBuffered = Ranges.getSegmentBufferedPercent(
118 segmentStart,
119 segmentDuration,
120 currentTime,
121 buffered);
122
123 QUnit.equal(percentBuffered, 90, 'calculated the buffered amount correctly');
124 });
125
126 QUnit.test('calculates the percent buffered for segments with multiple buffered ' +
127 'regions', function() {
128 let segmentStart = 10;
129 let segmentDuration = 10;
130 let currentTime = 0;
131 let buffered = createTimeRanges([[0, 11], [12, 19]]);
132 let percentBuffered = Ranges.getSegmentBufferedPercent(
133 segmentStart,
134 segmentDuration,
135 currentTime,
136 buffered);
137
138 QUnit.equal(percentBuffered, 80, 'calculated the buffered amount correctly');
139 });
140
141 QUnit.test('calculates the percent buffered for segments with multiple buffered ' +
142 'regions taking into account currentTime', function() {
143 let segmentStart = 10;
144 let segmentDuration = 10;
145 let currentTime = 12;
146 let buffered = createTimeRanges([[0, 11], [12, 19]]);
147 let percentBuffered = Ranges.getSegmentBufferedPercent(
148 segmentStart,
149 segmentDuration,
150 currentTime,
151 buffered);
152
153 QUnit.equal(percentBuffered, 90, 'calculated the buffered amount correctly');
154 });
155
156 QUnit.test('calculates the percent buffered as 0 for zero-length segments', function() {
157 let segmentStart = 10;
158 let segmentDuration = 0;
159 let currentTime = 0;
160 let buffered = createTimeRanges([[0, 19]]);
161 let percentBuffered = Ranges.getSegmentBufferedPercent(
162 segmentStart,
163 segmentDuration,
164 currentTime,
165 buffered);
166
167 QUnit.equal(percentBuffered, 0, 'calculated the buffered amount correctly');
168 });
......
...@@ -2,43 +2,8 @@ import QUnit from 'qunit'; ...@@ -2,43 +2,8 @@ import QUnit from 'qunit';
2 import SegmentLoader from '../src/segment-loader'; 2 import SegmentLoader from '../src/segment-loader';
3 import videojs from 'video.js'; 3 import videojs from 'video.js';
4 import xhrFactory from '../src/xhr'; 4 import xhrFactory from '../src/xhr';
5 import {useFakeEnvironment, useFakeMediaSource} from './test-helpers.js';
6 import Config from '../src/config'; 5 import Config from '../src/config';
7 6 import { playlistWithDuration, useFakeEnvironment, useFakeMediaSource } from './test-helpers.js';
8 const playlistWithDuration = function(time, conf) {
9 let result = {
10 targetDuration: 10,
11 mediaSequence: conf && conf.mediaSequence ? conf.mediaSequence : 0,
12 discontinuityStarts: [],
13 segments: [],
14 endList: true
15 };
16 let count = Math.floor(time / 10);
17 let remainder = time % 10;
18 let i;
19 let isEncrypted = conf && conf.isEncrypted;
20
21 for (i = 0; i < count; i++) {
22 result.segments.push({
23 uri: i + '.ts',
24 resolvedUri: i + '.ts',
25 duration: 10
26 });
27 if (isEncrypted) {
28 result.segments[i].key = {
29 uri: i + '-key.php',
30 resolvedUri: i + '-key.php'
31 };
32 }
33 }
34 if (remainder) {
35 result.segments.push({
36 uri: i + '.ts',
37 duration: remainder
38 });
39 }
40 return result;
41 };
42 7
43 let currentTime; 8 let currentTime;
44 let mediaSource; 9 let mediaSource;
...@@ -428,8 +393,8 @@ QUnit.test('adjusts the playlist offset even when segment.end is set if no' + ...@@ -428,8 +393,8 @@ QUnit.test('adjusts the playlist offset even when segment.end is set if no' +
428 QUnit.equal(this.requests[0].url, '1.ts', 'moved ahead a segment'); 393 QUnit.equal(this.requests[0].url, '1.ts', 'moved ahead a segment');
429 }); 394 });
430 395
431 QUnit.test('adjusts the playlist offset if no buffering progress is made after' + 396 QUnit.test('adjusts the playlist offset if no buffering progress is made after ' +
432 ' five consecutive attempts', function() { 397 'several consecutive attempts', function() {
433 let sourceBuffer; 398 let sourceBuffer;
434 let playlist; 399 let playlist;
435 let errors = 0; 400 let errors = 0;
...@@ -452,7 +417,7 @@ QUnit.test('adjusts the playlist offset if no buffering progress is made after' ...@@ -452,7 +417,7 @@ QUnit.test('adjusts the playlist offset if no buffering progress is made after'
452 sourceBuffer.buffered = videojs.createTimeRanges([[0, 10]]); 417 sourceBuffer.buffered = videojs.createTimeRanges([[0, 10]]);
453 sourceBuffer.trigger('updateend'); 418 sourceBuffer.trigger('updateend');
454 419
455 for (let i = 1; i <= 6; i++) { 420 for (let i = 1; i <= 5; i++) {
456 // the next segment doesn't increase the buffer at all 421 // the next segment doesn't increase the buffer at all
457 QUnit.equal(this.requests[0].url, (i + '.ts'), 'requested the next segment'); 422 QUnit.equal(this.requests[0].url, (i + '.ts'), 'requested the next segment');
458 this.clock.tick(1); 423 this.clock.tick(1);
...@@ -460,10 +425,8 @@ QUnit.test('adjusts the playlist offset if no buffering progress is made after' ...@@ -460,10 +425,8 @@ QUnit.test('adjusts the playlist offset if no buffering progress is made after'
460 this.requests.shift().respond(200, null, ''); 425 this.requests.shift().respond(200, null, '');
461 sourceBuffer.trigger('updateend'); 426 sourceBuffer.trigger('updateend');
462 } 427 }
463 428 this.clock.tick(1);
464 // so the loader should try the next segment 429 QUnit.equal(this.requests.length, 0, 'no more requests are made');
465 QUnit.equal(errors, 1, 'emitted error');
466 QUnit.ok(loader.paused(), 'loader is paused');
467 }); 430 });
468 431
469 QUnit.test('cancels outstanding requests on abort', function() { 432 QUnit.test('cancels outstanding requests on abort', function() {
......
...@@ -292,3 +292,38 @@ export const absoluteUrl = function(relativeUrl) { ...@@ -292,3 +292,38 @@ export const absoluteUrl = function(relativeUrl) {
292 .join('/') 292 .join('/')
293 ); 293 );
294 }; 294 };
295
296 export const playlistWithDuration = function(time, conf) {
297 let result = {
298 targetDuration: 10,
299 mediaSequence: conf && conf.mediaSequence ? conf.mediaSequence : 0,
300 discontinuityStarts: [],
301 segments: [],
302 endList: true
303 };
304 let count = Math.floor(time / 10);
305 let remainder = time % 10;
306 let i;
307 let isEncrypted = conf && conf.isEncrypted;
308
309 for (i = 0; i < count; i++) {
310 result.segments.push({
311 uri: i + '.ts',
312 resolvedUri: i + '.ts',
313 duration: 10
314 });
315 if (isEncrypted) {
316 result.segments[i].key = {
317 uri: i + '-key.php',
318 resolvedUri: i + '-key.php'
319 };
320 }
321 }
322 if (remainder) {
323 result.segments.push({
324 uri: i + '.ts',
325 duration: remainder
326 });
327 }
328 return result;
329 };
......
1 {
2 "allowCache": true,
3 "mediaSequence": 0,
4 "playlistType": "VOD",
5 "segments": [
6 {
7 "duration": 0.01,
8 "timeline": 0,
9 "uri": "http://example.com/00001.ts"
10 }
11 ],
12 "targetDuration": 10,
13 "endList": true,
14 "discontinuitySequence": 0,
15 "discontinuityStarts": []
16 }
1 #EXTM3U
2 #EXT-X-PLAYLIST-TYPE:VOD
3 #EXT-X-TARGETDURATION:10
4
5 #EXTINF:0,
6 http://example.com/00001.ts
7 #ZEN-TOTAL-DURATION:57.9911
8 #EXT-X-ENDLIST