1 | import QUnit from 'qunit';
|
2 | import {
|
3 | default as SyncController,
|
4 | syncPointStrategies as strategies } from '../src/sync-controller.js';
|
5 | import { playlistWithDuration } from './test-helpers.js';
|
6 |
|
7 | function getStrategy(name) {
|
8 | for (let i = 0; i < strategies.length; i++) {
|
9 | if (strategies[i].name === name) {
|
10 | return strategies[i];
|
11 | }
|
12 | }
|
13 | throw new Error('No sync-strategy named "${name}" was found!');
|
14 | }
|
15 |
|
16 | QUnit.module('SyncController', {
|
17 | beforeEach() {
|
18 | this.syncController = new SyncController();
|
19 | }
|
20 | });
|
21 |
|
22 | QUnit.test('returns correct sync point for VOD strategy', function(assert) {
|
23 | let playlist = playlistWithDuration(40);
|
24 | let duration = 40;
|
25 | let timeline = 0;
|
26 | let vodStrategy = getStrategy('VOD');
|
27 | let syncPoint = vodStrategy.run(this.syncController, playlist, duration, timeline);
|
28 |
|
29 | assert.deepEqual(syncPoint, { time: 0, segmentIndex: 0 }, 'sync point found for vod');
|
30 |
|
31 | duration = Infinity;
|
32 | syncPoint = vodStrategy.run(this.syncController, playlist, duration, timeline);
|
33 |
|
34 | assert.equal(syncPoint, null, 'no syncpoint found for non vod ');
|
35 | });
|
36 |
|
37 | QUnit.test('returns correct sync point for ProgramDateTime strategy', function(assert) {
|
38 | let strategy = getStrategy('ProgramDateTime');
|
39 | let datetime = new Date(2012, 11, 12, 12, 12, 12);
|
40 | let playlist = playlistWithDuration(40);
|
41 | let timeline = 0;
|
42 | let duration = Infinity;
|
43 | let syncPoint;
|
44 |
|
45 | syncPoint = strategy.run(this.syncController, playlist, duration, timeline);
|
46 |
|
47 | assert.equal(syncPoint, null, 'no syncpoint when datetimeToDisplayTime not set');
|
48 |
|
49 | playlist.dateTimeObject = datetime;
|
50 |
|
51 | this.syncController.setDateTimeMapping(playlist);
|
52 |
|
53 | let newPlaylist = playlistWithDuration(40);
|
54 |
|
55 | syncPoint = strategy.run(this.syncController, newPlaylist, duration, timeline);
|
56 |
|
57 | assert.equal(syncPoint, null, 'no syncpoint when datetimeObject not set on playlist');
|
58 |
|
59 | newPlaylist.dateTimeObject = new Date(2012, 11, 12, 12, 12, 22);
|
60 |
|
61 | syncPoint = strategy.run(this.syncController, newPlaylist, duration, timeline);
|
62 |
|
63 | assert.deepEqual(syncPoint, {
|
64 | time: 10,
|
65 | segmentIndex: 0
|
66 | }, 'syncpoint found for ProgramDateTime set');
|
67 | });
|
68 |
|
69 | QUnit.test('returns correct sync point for Segment strategy', function(assert) {
|
70 | let strategy = getStrategy('Segment');
|
71 | let playlist = {
|
72 | segments: [
|
73 | { timeline: 0 },
|
74 | { timeline: 0 },
|
75 | { timeline: 1, start: 10 },
|
76 | { timeline: 1, start: 20 },
|
77 | { timeline: 1 },
|
78 | { timeline: 1 },
|
79 | { timeline: 1, start: 50 },
|
80 | { timeline: 1, start: 60 }
|
81 | ]
|
82 | };
|
83 | let currentTimeline;
|
84 | let syncPoint;
|
85 |
|
86 | currentTimeline = 0;
|
87 | syncPoint = strategy.run(this.syncController, playlist, 80, currentTimeline, 0);
|
88 | assert.equal(syncPoint, null, 'no syncpoint for timeline 0');
|
89 |
|
90 | currentTimeline = 1;
|
91 | syncPoint = strategy.run(this.syncController, playlist, 80, currentTimeline, 30);
|
92 | assert.deepEqual(syncPoint, { time: 20, segmentIndex: 3 },
|
93 | 'closest sync point found');
|
94 |
|
95 | syncPoint = strategy.run(this.syncController, playlist, 80, currentTimeline, 40);
|
96 | assert.deepEqual(syncPoint, { time: 50, segmentIndex: 6 },
|
97 | 'closest sync point found');
|
98 |
|
99 | syncPoint = strategy.run(this.syncController, playlist, 80, currentTimeline, 50);
|
100 | assert.deepEqual(syncPoint, { time: 50, segmentIndex: 6 },
|
101 | 'exact sync point found');
|
102 | });
|
103 |
|
104 | QUnit.test('returns correct sync point for Discontinuity strategy', function(assert) {
|
105 | let strategy = getStrategy('Discontinuity');
|
106 | let playlist = {
|
107 | targetDuration: 10,
|
108 | discontinuitySequence: 2,
|
109 | discontinuityStarts: [2, 5],
|
110 | segments: [
|
111 | { timeline: 2, start: 20, end: 30, duration: 10 },
|
112 | { timeline: 2, start: 30, end: 40, duration: 10 },
|
113 | { timeline: 3, start: 40, end: 50, duration: 10, discontinuity: true },
|
114 | { timeline: 3, start: 50, end: 60, duration: 10 },
|
115 | { timeline: 3, start: 60, end: 70, duration: 10 },
|
116 | { timeline: 4, start: 70, end: 80, duration: 10, discontinuity: true },
|
117 | { timeline: 4, start: 80, end: 90, duration: 10 },
|
118 | { timeline: 4, start: 90, end: 100, duration: 10 }
|
119 | ]
|
120 | };
|
121 | let segmentInfo = {
|
122 | playlist,
|
123 | segment: playlist.segments[2],
|
124 | mediaIndex: 2
|
125 | };
|
126 | let currentTimeline = 3;
|
127 | let syncPoint;
|
128 |
|
129 | syncPoint = strategy.run(this.syncController, playlist, 100, currentTimeline, 0);
|
130 | assert.equal(syncPoint, null, 'no sync point when no discontinuities saved');
|
131 |
|
132 | this.syncController.saveDiscontinuitySyncInfo_(segmentInfo);
|
133 |
|
134 | syncPoint = strategy.run(this.syncController, playlist, 100, currentTimeline, 55);
|
135 | assert.deepEqual(syncPoint, { time: 40, segmentIndex: 2 },
|
136 | 'found sync point for timeline 3');
|
137 |
|
138 | segmentInfo.mediaIndex = 6;
|
139 | segmentInfo.segment = playlist.segments[6];
|
140 | currentTimeline = 4;
|
141 |
|
142 | this.syncController.saveDiscontinuitySyncInfo_(segmentInfo);
|
143 |
|
144 | syncPoint = strategy.run(this.syncController, playlist, 100, currentTimeline, 90);
|
145 | assert.deepEqual(syncPoint, { time: 70, segmentIndex: 5 },
|
146 | 'found sync point for timeline 4');
|
147 | });
|
148 |
|
149 | QUnit.test('returns correct sync point for Playlist strategy', function(assert) {
|
150 | let strategy = getStrategy('Playlist');
|
151 | let playlist = { mediaSequence: 100 };
|
152 | let syncPoint;
|
153 |
|
154 | syncPoint = strategy.run(this.syncController, playlist, 40, 0);
|
155 | assert.equal(syncPoint, null, 'no sync point if no sync info');
|
156 |
|
157 | playlist.mediaSequence = 102;
|
158 | playlist.syncInfo = { time: 10, mediaSequence: 100};
|
159 |
|
160 | syncPoint = strategy.run(this.syncController, playlist, 40, 0);
|
161 | assert.deepEqual(syncPoint, { time: 10, segmentIndex: -2 },
|
162 | 'found sync point in playlist');
|
163 | });
|
164 |
|
165 | QUnit.test('saves expired info onto new playlist for sync point', function(assert) {
|
166 | let oldPlaylist = playlistWithDuration(50);
|
167 | let newPlaylist = playlistWithDuration(50);
|
168 |
|
169 | oldPlaylist.mediaSequence = 100;
|
170 | newPlaylist.mediaSequence = 103;
|
171 |
|
172 | oldPlaylist.segments[0].start = 390;
|
173 | oldPlaylist.segments[1].start = 400;
|
174 |
|
175 | this.syncController.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
|
176 |
|
177 | assert.deepEqual(newPlaylist.syncInfo, { mediaSequence: 101, time: 400 },
|
178 | 'saved correct info for expired segment onto new playlist');
|
179 | });
|
180 |
|
181 | QUnit.test('Correctly updates time mapping and discontinuity info when probing segments',
|
182 | function(assert) {
|
183 | let syncCon = this.syncController;
|
184 | let playlist = playlistWithDuration(60);
|
185 |
|
186 | playlist.discontinuityStarts = [3];
|
187 | playlist.discontinuitySequence = 0;
|
188 | playlist.segments[3].discontinuity = true;
|
189 | playlist.segments.forEach((segment, i) => {
|
190 | if (i >= playlist.discontinuityStarts[0]) {
|
191 | segment.timeline = 1;
|
192 | } else {
|
193 | segment.timeline = 0;
|
194 | }
|
195 | });
|
196 |
|
197 | syncCon.probeTsSegment_ = function(segmentInfo) {
|
198 | return {
|
199 |
|
200 | start: segmentInfo.mediaIndex * 10 + 5 + (6 * segmentInfo.timeline),
|
201 | end: segmentInfo.mediaIndex * 10 + 10 + 5 + (6 * segmentInfo.timeline)
|
202 | };
|
203 | };
|
204 |
|
205 | let segment = playlist.segments[0];
|
206 | let segmentInfo = {
|
207 | mediaIndex: 0,
|
208 | playlist,
|
209 | timeline: 0,
|
210 | timestampOffset: 0,
|
211 | startOfSegment: 0,
|
212 | segment
|
213 | };
|
214 |
|
215 | syncCon.probeSegmentInfo(segmentInfo);
|
216 | assert.ok(syncCon.timelines[0], 'created mapping object for timeline 0');
|
217 | assert.deepEqual(syncCon.timelines[0], { time: 0, mapping: -5 },
|
218 | 'mapping object correct');
|
219 | assert.equal(segment.start, 0, 'correctly calculated segment start');
|
220 | assert.equal(segment.end, 10, 'correctly calculated segment end');
|
221 | assert.ok(syncCon.discontinuities[1], 'created discontinuity info for timeline 1');
|
222 | assert.deepEqual(syncCon.discontinuities[1], { time: 30, accuracy: 3 },
|
223 | 'discontinuity sync info correct');
|
224 |
|
225 | segmentInfo.timestampOffset = null;
|
226 | segmentInfo.startOfSegment = 10;
|
227 | segmentInfo.mediaIndex = 1;
|
228 | segment = playlist.segments[1];
|
229 | segmentInfo.segment = segment;
|
230 |
|
231 | syncCon.probeSegmentInfo(segmentInfo);
|
232 | assert.equal(segment.start, 10, 'correctly calculated segment start');
|
233 | assert.equal(segment.end, 20, 'correctly calculated segment end');
|
234 | assert.deepEqual(syncCon.discontinuities[1], { time: 30, accuracy: 2 },
|
235 | 'discontinuity sync info correctly updated with new accuracy');
|
236 |
|
237 | segmentInfo.timestampOffset = 30;
|
238 | segmentInfo.startOfSegment = 30;
|
239 | segmentInfo.mediaIndex = 3;
|
240 | segmentInfo.timeline = 1;
|
241 | segment = playlist.segments[3];
|
242 | segmentInfo.segment = segment;
|
243 |
|
244 | syncCon.probeSegmentInfo(segmentInfo);
|
245 | assert.ok(syncCon.timelines[1], 'created mapping object for timeline 1');
|
246 | assert.deepEqual(syncCon.timelines[1], { time: 30, mapping: -11 },
|
247 | 'mapping object correct');
|
248 | assert.equal(segment.start, 30, 'correctly calculated segment start');
|
249 | assert.equal(segment.end, 40, 'correctly calculated segment end');
|
250 | assert.deepEqual(syncCon.discontinuities[1], { time: 30, accuracy: 0 },
|
251 | 'discontinuity sync info correctly updated with new accuracy');
|
252 | });
|
253 |
|
254 | QUnit.test('Correctly calculates expired time', function(assert) {
|
255 | let playlist = {
|
256 | targetDuration: 10,
|
257 | mediaSequence: 100,
|
258 | discontinuityStarts: [],
|
259 | syncInfo: {
|
260 | time: 50,
|
261 | mediaSequence: 95
|
262 | },
|
263 | segments: [
|
264 | {
|
265 | duration: 10,
|
266 | uri: '0.ts'
|
267 | },
|
268 | {
|
269 | duration: 10,
|
270 | uri: '1.ts'
|
271 | },
|
272 | {
|
273 | duration: 10,
|
274 | uri: '2.ts'
|
275 | },
|
276 | {
|
277 | duration: 10,
|
278 | uri: '3.ts'
|
279 | },
|
280 | {
|
281 | duration: 10,
|
282 | uri: '4.ts'
|
283 | }
|
284 | ]
|
285 | };
|
286 |
|
287 | let expired = this.syncController.getExpiredTime(playlist, Infinity);
|
288 |
|
289 | assert.equal(expired, 100, 'estimated expired time using segmentSync');
|
290 |
|
291 | playlist = {
|
292 | targetDuration: 10,
|
293 | discontinuityStarts: [],
|
294 | mediaSequence: 100,
|
295 | segments: [
|
296 | {
|
297 | duration: 10,
|
298 | uri: '0.ts'
|
299 | },
|
300 | {
|
301 | duration: 10,
|
302 | uri: '1.ts',
|
303 | start: 108.5,
|
304 | end: 118.4
|
305 | },
|
306 | {
|
307 | duration: 10,
|
308 | uri: '2.ts'
|
309 | },
|
310 | {
|
311 | duration: 10,
|
312 | uri: '3.ts'
|
313 | },
|
314 | {
|
315 | duration: 10,
|
316 | uri: '4.ts'
|
317 | }
|
318 | ]
|
319 | };
|
320 |
|
321 | expired = this.syncController.getExpiredTime(playlist, Infinity);
|
322 |
|
323 | assert.equal(expired, 98.5, 'estimated expired time using segmentSync');
|
324 |
|
325 | playlist = {
|
326 | discontinuityStarts: [],
|
327 | targetDuration: 10,
|
328 | mediaSequence: 100,
|
329 | syncInfo: {
|
330 | time: 50,
|
331 | mediaSequence: 95
|
332 | },
|
333 | segments: [
|
334 | {
|
335 | duration: 10,
|
336 | uri: '0.ts'
|
337 | },
|
338 | {
|
339 | duration: 10,
|
340 | uri: '1.ts',
|
341 | start: 108.5,
|
342 | end: 118.5
|
343 | },
|
344 | {
|
345 | duration: 10,
|
346 | uri: '2.ts'
|
347 | },
|
348 | {
|
349 | duration: 10,
|
350 | uri: '3.ts'
|
351 | },
|
352 | {
|
353 | duration: 10,
|
354 | uri: '4.ts'
|
355 | }
|
356 | ]
|
357 | };
|
358 |
|
359 | expired = this.syncController.getExpiredTime(playlist, Infinity);
|
360 |
|
361 | assert.equal(expired, 98.5, 'estimated expired time using segmentSync');
|
362 |
|
363 | playlist = {
|
364 | targetDuration: 10,
|
365 | discontinuityStarts: [],
|
366 | mediaSequence: 100,
|
367 | syncInfo: {
|
368 | time: 90.8,
|
369 | mediaSequence: 99
|
370 | },
|
371 | segments: [
|
372 | {
|
373 | duration: 10,
|
374 | uri: '0.ts'
|
375 | },
|
376 | {
|
377 | duration: 10,
|
378 | uri: '1.ts'
|
379 | },
|
380 | {
|
381 | duration: 10,
|
382 | uri: '2.ts',
|
383 | start: 118.5,
|
384 | end: 128.5
|
385 | },
|
386 | {
|
387 | duration: 10,
|
388 | uri: '3.ts'
|
389 | },
|
390 | {
|
391 | duration: 10,
|
392 | uri: '4.ts'
|
393 | }
|
394 | ]
|
395 | };
|
396 |
|
397 | expired = this.syncController.getExpiredTime(playlist, Infinity);
|
398 |
|
399 | assert.equal(expired, 100.8, 'estimated expired time using segmentSync');
|
400 |
|
401 | playlist = {
|
402 | targetDuration: 10,
|
403 | discontinuityStarts: [],
|
404 | mediaSequence: 100,
|
405 | endList: true,
|
406 | segments: [
|
407 | {
|
408 | duration: 10,
|
409 | uri: '0.ts'
|
410 | },
|
411 | {
|
412 | duration: 10,
|
413 | uri: '1.ts'
|
414 | },
|
415 | {
|
416 | duration: 10,
|
417 | uri: '2.ts'
|
418 | },
|
419 | {
|
420 | duration: 10,
|
421 | uri: '3.ts'
|
422 | },
|
423 | {
|
424 | duration: 10,
|
425 | uri: '4.ts'
|
426 | }
|
427 | ]
|
428 | };
|
429 |
|
430 | expired = this.syncController.getExpiredTime(playlist, 50);
|
431 |
|
432 | assert.equal(expired, 0, 'estimated expired time using segmentSync');
|
433 |
|
434 | playlist = {
|
435 | targetDuration: 10,
|
436 | discontinuityStarts: [],
|
437 | mediaSequence: 100,
|
438 | endList: true,
|
439 | segments: [
|
440 | {
|
441 | start: 0.006,
|
442 | duration: 10,
|
443 | uri: '0.ts',
|
444 | end: 9.982
|
445 | },
|
446 | {
|
447 | duration: 10,
|
448 | uri: '1.ts'
|
449 | },
|
450 | {
|
451 | duration: 10,
|
452 | uri: '2.ts'
|
453 | },
|
454 | {
|
455 | duration: 10,
|
456 | uri: '3.ts'
|
457 | },
|
458 | {
|
459 | duration: 10,
|
460 | uri: '4.ts'
|
461 | }
|
462 | ]
|
463 | };
|
464 |
|
465 | expired = this.syncController.getExpiredTime(playlist, 50);
|
466 |
|
467 | assert.equal(expired, 0, 'estimated expired time using segmentSync');
|
468 | });
|