UNPKG

32.2 kBJavaScriptView Raw
1import QUnit from 'qunit';
2import {
3 default as SegmentLoader,
4 illegalMediaSwitch,
5 safeBackBufferTrimTime
6} from '../src/segment-loader';
7import videojs from 'video.js';
8import mp4probe from 'mux.js/lib/mp4/probe';
9import {
10 playlistWithDuration,
11 MockTextTrack
12} from './test-helpers.js';
13import {
14 LoaderCommonHooks,
15 LoaderCommonSettings,
16 LoaderCommonFactory
17} from './loader-common.js';
18import sinon from 'sinon';
19
20// noop addSegmentMetadataCue_ since most test segments dont have real timing information
21// save the original function to a variable to patch it back in for the metadata cue
22// specific tests
23const ogAddSegmentMetadataCue_ = SegmentLoader.prototype.addSegmentMetadataCue_;
24
25SegmentLoader.prototype.addSegmentMetadataCue_ = function() {};
26
27QUnit.module('SegmentLoader Isolated Functions');
28
29QUnit.test('illegalMediaSwitch detects illegal media switches', function(assert) {
30 let startingMedia = { containsAudio: true, containsVideo: true };
31 let newSegmentMedia = { containsAudio: true, containsVideo: true };
32
33 assert.notOk(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
34 'no error when muxed to muxed');
35
36 startingMedia = { containsAudio: true, containsVideo: true };
37 newSegmentMedia = { containsAudio: false, containsVideo: false };
38 assert.notOk(illegalMediaSwitch('audio', startingMedia, newSegmentMedia),
39 'no error when not main loader type');
40
41 startingMedia = { containsAudio: true, containsVideo: false };
42 newSegmentMedia = { containsAudio: true, containsVideo: false };
43 assert.notOk(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
44 'no error when audio only to audio only');
45
46 startingMedia = { containsAudio: false, containsVideo: true };
47 newSegmentMedia = { containsAudio: false, containsVideo: true };
48 assert.notOk(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
49 'no error when video only to video only');
50
51 startingMedia = { containsAudio: false, containsVideo: true };
52 newSegmentMedia = { containsAudio: true, containsVideo: true };
53 assert.notOk(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
54 'no error when video only to muxed');
55
56 startingMedia = { containsAudio: true, containsVideo: true };
57 newSegmentMedia = { containsAudio: false, containsVideo: false };
58 assert.equal(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
59 'Neither audio nor video found in segment.',
60 'error when neither audio nor video');
61
62 startingMedia = { containsAudio: true, containsVideo: false };
63 newSegmentMedia = { containsAudio: false, containsVideo: false };
64 assert.equal(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
65 'Neither audio nor video found in segment.',
66 'error when audio only to neither audio nor video');
67
68 startingMedia = { containsAudio: false, containsVideo: true };
69 newSegmentMedia = { containsAudio: false, containsVideo: false };
70 assert.equal(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
71 'Neither audio nor video found in segment.',
72 'error when video only to neither audio nor video');
73
74 startingMedia = { containsAudio: true, containsVideo: false };
75 newSegmentMedia = { containsAudio: true, containsVideo: true };
76 assert.equal(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
77 'Video found in segment when we expected only audio.' +
78 ' We can\'t switch to a stream with video from an audio only stream.' +
79 ' To get rid of this message, please add codec information to the' +
80 ' manifest.',
81 'error when audio only to muxed');
82
83 startingMedia = { containsAudio: true, containsVideo: true };
84 newSegmentMedia = { containsAudio: true, containsVideo: false };
85 assert.equal(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
86 'Only audio found in segment when we expected video.' +
87 ' We can\'t switch to audio only from a stream that had video.' +
88 ' To get rid of this message, please add codec information to the' +
89 ' manifest.',
90 'error when muxed to audio only');
91
92 startingMedia = { containsAudio: true, containsVideo: false };
93 newSegmentMedia = { containsAudio: false, containsVideo: true };
94 assert.equal(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
95 'Video found in segment when we expected only audio.' +
96 ' We can\'t switch to a stream with video from an audio only stream.' +
97 ' To get rid of this message, please add codec information to the' +
98 ' manifest.',
99 'error when audio only to video only');
100
101 startingMedia = { containsAudio: false, containsVideo: true };
102 newSegmentMedia = { containsAudio: true, containsVideo: false };
103 assert.equal(illegalMediaSwitch('main', startingMedia, newSegmentMedia),
104 'Only audio found in segment when we expected video.' +
105 ' We can\'t switch to audio only from a stream that had video.' +
106 ' To get rid of this message, please add codec information to the' +
107 ' manifest.',
108 'error when video only to audio only');
109});
110
111QUnit.test('safeBackBufferTrimTime determines correct safe removeToTime',
112function(assert) {
113 let seekable = videojs.createTimeRanges([[75, 120]]);
114 let targetDuration = 10;
115 let currentTime = 70;
116
117 assert.equal(safeBackBufferTrimTime(seekable, currentTime, targetDuration), 40,
118 'uses 30s before current time if currentTime is before seekable start');
119
120 currentTime = 110;
121
122 assert.equal(safeBackBufferTrimTime(seekable, currentTime, targetDuration), 75,
123 'uses seekable start if currentTime is after seekable start');
124
125 currentTime = 80;
126
127 assert.equal(safeBackBufferTrimTime(seekable, currentTime, targetDuration), 70,
128 'uses target duration before currentTime if currentTime is after seekable but' +
129 'within target duration');
130});
131
132QUnit.module('SegmentLoader', function(hooks) {
133 hooks.beforeEach(LoaderCommonHooks.beforeEach);
134 hooks.afterEach(LoaderCommonHooks.afterEach);
135
136 LoaderCommonFactory(SegmentLoader,
137 { loaderType: 'main' },
138 (loader) => loader.mimeType('video/mp2t'));
139
140 // Tests specific to the main segment loader go in this module
141 QUnit.module('Loader Main', function(nestedHooks) {
142 let loader;
143
144 nestedHooks.beforeEach(function(assert) {
145 this.segmentMetadataTrack = new MockTextTrack();
146 this.startTime = sinon.stub(mp4probe, 'startTime');
147 this.mimeType = 'video/mp2t';
148
149 loader = new SegmentLoader(LoaderCommonSettings.call(this, {
150 loaderType: 'main',
151 segmentMetadataTrack: this.segmentMetadataTrack
152 }), {});
153
154 // shim updateend trigger to be a noop if the loader has no media source
155 this.updateend = function() {
156 if (loader.mediaSource_) {
157 loader.mediaSource_.sourceBuffers[0].trigger('updateend');
158 }
159 };
160 });
161
162 nestedHooks.afterEach(function(assert) {
163 this.startTime.restore();
164 });
165
166 QUnit.test(`load waits until a playlist and mime type are specified to proceed`,
167 function(assert) {
168 loader.load();
169
170 assert.equal(loader.state, 'INIT', 'waiting in init');
171 assert.equal(loader.paused(), false, 'not paused');
172
173 loader.playlist(playlistWithDuration(10));
174 assert.equal(this.requests.length, 0, 'have not made a request yet');
175 loader.mimeType(this.mimeType);
176 this.clock.tick(1);
177
178 assert.equal(this.requests.length, 1, 'made a request');
179 assert.equal(loader.state, 'WAITING', 'transitioned states');
180 });
181
182 QUnit.test(`calling mime type and load begins buffering`, function(assert) {
183 assert.equal(loader.state, 'INIT', 'starts in the init state');
184 loader.playlist(playlistWithDuration(10));
185 assert.equal(loader.state, 'INIT', 'starts in the init state');
186 assert.ok(loader.paused(), 'starts paused');
187
188 loader.mimeType(this.mimeType);
189 assert.equal(loader.state, 'INIT', 'still in the init state');
190 loader.load();
191 this.clock.tick(1);
192
193 assert.equal(loader.state, 'WAITING', 'moves to the ready state');
194 assert.ok(!loader.paused(), 'loading is not paused');
195 assert.equal(this.requests.length, 1, 'requested a segment');
196 });
197
198 QUnit.test('only appends one segment at a time', function(assert) {
199 loader.playlist(playlistWithDuration(10));
200 loader.mimeType(this.mimeType);
201 loader.load();
202 this.clock.tick(1);
203
204 // some time passes and a segment is received
205 this.clock.tick(100);
206 this.requests[0].response = new Uint8Array(10).buffer;
207 this.requests.shift().respond(200, null, '');
208
209 // a lot of time goes by without "updateend"
210 this.clock.tick(20 * 1000);
211
212 assert.equal(this.mediaSource.sourceBuffers[0].updates_.filter(
213 update => update.append).length, 1, 'only one append');
214 assert.equal(this.requests.length, 0, 'only made one request');
215
216 // verify stats
217 assert.equal(loader.mediaBytesTransferred, 10, '10 bytes');
218 assert.equal(loader.mediaTransferDuration, 100, '100 ms (clock above)');
219 assert.equal(loader.mediaRequests, 1, '1 request');
220 });
221
222 QUnit.test('updates timestamps when segments do not start at zero', function(assert) {
223 let playlist = playlistWithDuration(10);
224
225 playlist.segments.forEach((segment) => {
226 segment.map = {
227 resolvedUri: 'init.mp4',
228 byterange: { length: Infinity, offset: 0 }
229 };
230 });
231 loader.playlist(playlist);
232 loader.mimeType(this.mimeType);
233 loader.load();
234
235 this.startTime.returns(11);
236
237 this.clock.tick(100);
238 // init
239 this.requests[0].response = new Uint8Array(10).buffer;
240 this.requests.shift().respond(200, null, '');
241 // segment
242 this.requests[0].response = new Uint8Array(10).buffer;
243 this.requests.shift().respond(200, null, '');
244
245 assert.equal(loader.sourceUpdater_.timestampOffset(), -11, 'set timestampOffset');
246 assert.equal(playlist.segments[0].start,
247 0,
248 'segment start time not shifted by mp4 start time');
249 assert.equal(playlist.segments[0].end,
250 10,
251 'segment end time not shifted by mp4 start time');
252 });
253
254 QUnit.test('triggers syncinfoupdate before attempting a resync', function(assert) {
255 let syncInfoUpdates = 0;
256
257 loader.playlist(playlistWithDuration(20));
258 loader.mimeType(this.mimeType);
259 loader.load();
260 this.clock.tick(1);
261
262 this.seekable = videojs.createTimeRanges([[0, 10]]);
263 this.syncController.probeSegmentInfo = (segmentInfo) => {
264 let segment = segmentInfo.segment;
265
266 segment.end = 10;
267 };
268 loader.on('syncinfoupdate', () => {
269 syncInfoUpdates++;
270 // Simulate the seekable window updating
271 this.seekable = videojs.createTimeRanges([[200, 210]]);
272 // Simulate the seek to live that should happen in playback-watcher
273 this.currentTime = 210;
274 });
275
276 this.requests[0].response = new Uint8Array(10).buffer;
277 this.requests.shift().respond(200, null, '');
278 this.updateend();
279 this.clock.tick(1);
280
281 assert.equal(loader.mediaIndex, null, 'mediaIndex reset by seek to seekable');
282 assert.equal(syncInfoUpdates, 1, 'syncinfoupdate was triggered');
283 });
284
285 QUnit.test('abort does not cancel segment processing in progress', function(assert) {
286 loader.playlist(playlistWithDuration(20));
287 loader.mimeType(this.mimeType);
288 loader.load();
289 this.clock.tick(1);
290
291 this.requests[0].response = new Uint8Array(10).buffer;
292 this.requests.shift().respond(200, null, '');
293
294 loader.abort();
295 this.clock.tick(1);
296
297 assert.equal(loader.state, 'APPENDING', 'still appending');
298
299 // verify stats
300 assert.equal(loader.mediaBytesTransferred, 10, '10 bytes');
301 assert.equal(loader.mediaRequests, 1, '1 request');
302 });
303
304 QUnit.test('sets the timestampOffset on timeline change', function(assert) {
305 let playlist = playlistWithDuration(40);
306 let buffered = videojs.createTimeRanges();
307 let hlsTimestampOffsetEvents = 0;
308
309 loader.on('timestampoffset', () => {
310 hlsTimestampOffsetEvents++;
311 });
312
313 loader.buffered_ = () => buffered;
314
315 playlist.discontinuityStarts = [1];
316 playlist.segments[1].timeline = 1;
317 loader.playlist(playlist);
318 loader.mimeType(this.mimeType);
319 loader.load();
320 this.clock.tick(1);
321
322 // segment 0
323 this.requests[0].response = new Uint8Array(10).buffer;
324 this.requests.shift().respond(200, null, '');
325 buffered = videojs.createTimeRanges([[0, 10]]);
326 this.updateend();
327 this.clock.tick(1);
328
329 assert.equal(hlsTimestampOffsetEvents, 0,
330 'no hls-timestamp-offset event was fired');
331 // segment 1, discontinuity
332 this.requests[0].response = new Uint8Array(10).buffer;
333 this.requests.shift().respond(200, null, '');
334 assert.equal(loader.mediaSource_.sourceBuffers[0].timestampOffset,
335 10,
336 'set timestampOffset');
337
338 // verify stats
339 assert.equal(loader.mediaBytesTransferred, 20, '20 bytes');
340 assert.equal(loader.mediaRequests, 2, '2 requests');
341 assert.equal(hlsTimestampOffsetEvents, 1,
342 'an hls-timestamp-offset event was fired');
343 });
344
345 QUnit.test('tracks segment end times as they are buffered', function(assert) {
346 let playlist = playlistWithDuration(20);
347
348 loader.syncController_.probeTsSegment_ = function(segmentInfo) {
349 return { start: 0, end: 9.5 };
350 };
351
352 loader.playlist(playlist);
353 loader.mimeType(this.mimeType);
354 loader.load();
355 this.clock.tick(1);
356
357 this.requests[0].response = new Uint8Array(10).buffer;
358 this.requests.shift().respond(200, null, '');
359
360 this.updateend();
361 this.clock.tick(1);
362
363 assert.equal(playlist.segments[0].end, 9.5, 'updated duration');
364
365 // verify stats
366 assert.equal(loader.mediaBytesTransferred, 10, '10 bytes');
367 assert.equal(loader.mediaRequests, 1, '1 request');
368 });
369
370 QUnit.test('loader triggers segmenttimemapping before appending segment',
371 function(assert) {
372 let playlist = playlistWithDuration(20);
373 let segmenttimemappings = 0;
374 let timingInfo = { hasMapping: false };
375
376 this.syncController.probeSegmentInfo = () => timingInfo;
377
378 loader.on('segmenttimemapping', function() {
379 segmenttimemappings++;
380 });
381
382 loader.playlist(playlist);
383 loader.mimeType(this.mimeType);
384 loader.load();
385 this.clock.tick(1);
386
387 assert.equal(segmenttimemappings, 0, 'no events before segment downloaded');
388
389 // some time passes and a response is received
390 this.requests[0].response = new Uint8Array(10).buffer;
391 this.requests.shift().respond(200, null, '');
392
393 assert.equal(segmenttimemappings, 0,
394 'did not trigger segmenttimemappings with unsuccessful probe');
395
396 this.updateend();
397 this.clock.tick(1);
398
399 assert.equal(segmenttimemappings, 0, 'no events before segment downloaded');
400
401 timingInfo.hasMapping = true;
402 this.syncController.timelines[0] = { mapping: 0 };
403
404 // some time passes and a response is received
405 this.requests[0].response = new Uint8Array(10).buffer;
406 this.requests.shift().respond(200, null, '');
407
408 assert.equal(segmenttimemappings, 1,
409 'triggered segmenttimemappings with successful probe');
410 });
411
412 QUnit.test('adds cues with segment information to the segment-metadata track ' +
413 'as they are buffered',
414 function(assert) {
415 const track = loader.segmentMetadataTrack_;
416 let playlist = playlistWithDuration(50);
417 let probeResponse;
418 let expectedCue;
419
420 loader.addSegmentMetadataCue_ = ogAddSegmentMetadataCue_;
421 loader.syncController_.probeTsSegment_ = function(segmentInfo) {
422 return probeResponse;
423 };
424
425 loader.playlist(playlist);
426 loader.mimeType(this.mimeType);
427 loader.load();
428 this.clock.tick(1);
429
430 assert.ok(!track.cues.length,
431 'segment-metadata track empty when no segments appended');
432
433 // Start appending some segments
434 probeResponse = { start: 0, end: 9.5 };
435 this.requests[0].response = new Uint8Array(10).buffer;
436 this.requests.shift().respond(200, null, '');
437 this.updateend();
438 this.clock.tick(1);
439 expectedCue = {
440 uri: '0.ts',
441 timeline: 0,
442 playlist: 'playlist.m3u8',
443 start: 0,
444 end: 9.5
445 };
446
447 assert.equal(track.cues.length, 1, 'one cue added for segment');
448 assert.deepEqual(track.cues[0].value, expectedCue,
449 'added correct segment info to cue');
450
451 probeResponse = { start: 9.56, end: 19.2 };
452 this.requests[0].response = new Uint8Array(10).buffer;
453 this.requests.shift().respond(200, null, '');
454 this.updateend();
455 this.clock.tick(1);
456 expectedCue = {
457 uri: '1.ts',
458 timeline: 0,
459 playlist: 'playlist.m3u8',
460 start: 9.56,
461 end: 19.2
462 };
463
464 assert.equal(track.cues.length, 2, 'one cue added for segment');
465 assert.deepEqual(track.cues[1].value, expectedCue,
466 'added correct segment info to cue');
467
468 probeResponse = { start: 19.24, end: 28.99 };
469 this.requests[0].response = new Uint8Array(10).buffer;
470 this.requests.shift().respond(200, null, '');
471 this.updateend();
472 this.clock.tick(1);
473 expectedCue = {
474 uri: '2.ts',
475 timeline: 0,
476 playlist: 'playlist.m3u8',
477 start: 19.24,
478 end: 28.99
479 };
480
481 assert.equal(track.cues.length, 3, 'one cue added for segment');
482 assert.deepEqual(track.cues[2].value, expectedCue,
483 'added correct segment info to cue');
484
485 // append overlapping segment, emmulating segment-loader fetching behavior on
486 // rendtion switch
487 probeResponse = { start: 19.21, end: 28.98 };
488 this.requests[0].response = new Uint8Array(10).buffer;
489 this.requests.shift().respond(200, null, '');
490 this.updateend();
491 this.clock.tick(1);
492 expectedCue = {
493 uri: '3.ts',
494 timeline: 0,
495 playlist: 'playlist.m3u8',
496 start: 19.21,
497 end: 28.98
498 };
499
500 assert.equal(track.cues.length, 3, 'overlapped cue removed, new one added');
501 assert.deepEqual(track.cues[2].value, expectedCue,
502 'added correct segment info to cue');
503
504 // does not add cue for invalid segment timing info
505 probeResponse = { start: 30, end: void 0 };
506 this.requests[0].response = new Uint8Array(10).buffer;
507 this.requests.shift().respond(200, null, '');
508 this.updateend();
509 this.clock.tick(1);
510
511 assert.equal(track.cues.length, 3, 'no cue added');
512
513 // verify stats
514 assert.equal(loader.mediaBytesTransferred, 50, '50 bytes');
515 assert.equal(loader.mediaRequests, 5, '5 requests');
516 });
517
518 QUnit.test('fires ended at the end of a playlist', function(assert) {
519 let endOfStreams = 0;
520 let buffered = videojs.createTimeRanges();
521
522 loader.buffered_ = () => buffered;
523
524 loader.playlist(playlistWithDuration(10));
525 loader.mimeType(this.mimeType);
526 loader.load();
527 this.clock.tick(1);
528
529 loader.mediaSource_ = {
530 readyState: 'open',
531 sourceBuffers: this.mediaSource.sourceBuffers
532 };
533
534 loader.on('ended', () => endOfStreams++);
535
536 this.requests[0].response = new Uint8Array(10).buffer;
537 this.requests.shift().respond(200, null, '');
538 buffered = videojs.createTimeRanges([[0, 10]]);
539 this.updateend();
540 this.clock.tick(1);
541
542 assert.equal(endOfStreams, 1, 'triggered ended');
543
544 // verify stats
545 assert.equal(loader.mediaBytesTransferred, 10, '10 bytes');
546 assert.equal(loader.mediaRequests, 1, '1 request');
547 });
548
549 QUnit.test('endOfStream happens even after a rendition switch', function(assert) {
550 let endOfStreams = 0;
551 let bandwidthupdates = 0;
552 let buffered = videojs.createTimeRanges();
553
554 loader.buffered_ = () => buffered;
555
556 loader.playlist(playlistWithDuration(20));
557 loader.mimeType(this.mimeType);
558 loader.load();
559 this.clock.tick(1);
560
561 loader.mediaSource_ = {
562 readyState: 'open',
563 sourceBuffers: this.mediaSource.sourceBuffers
564 };
565
566 loader.on('ended', () => endOfStreams++);
567
568 loader.on('bandwidthupdate', () => {
569 bandwidthupdates++;
570 // Simulate a rendition switch
571 loader.resetEverything();
572 });
573
574 this.requests[0].response = new Uint8Array(10).buffer;
575 this.requests.shift().respond(200, null, '');
576 buffered = videojs.createTimeRanges([[0, 10]]);
577 this.updateend();
578 this.clock.tick(10);
579
580 this.requests[0].response = new Uint8Array(10).buffer;
581 this.requests.shift().respond(200, null, '');
582 buffered = videojs.createTimeRanges([[0, 10]]);
583 this.updateend();
584
585 assert.equal(bandwidthupdates, 1, 'triggered bandwidthupdate');
586 assert.equal(endOfStreams, 1, 'triggered ended');
587 });
588
589 QUnit.test('live playlists do not trigger ended', function(assert) {
590 let endOfStreams = 0;
591 let playlist;
592 let buffered = videojs.createTimeRanges();
593
594 loader.buffered_ = () => buffered;
595
596 playlist = playlistWithDuration(10);
597 playlist.endList = false;
598 loader.playlist(playlist);
599 loader.mimeType(this.mimeType);
600 loader.load();
601 this.clock.tick(1);
602
603 loader.mediaSource_ = {
604 readyState: 'open',
605 sourceBuffers: this.mediaSource.sourceBuffers
606 };
607
608 loader.on('ended', () => endOfStreams++);
609
610 this.requests[0].response = new Uint8Array(10).buffer;
611 this.requests.shift().respond(200, null, '');
612 buffered = videojs.createTimeRanges([[0, 10]]);
613 this.updateend();
614 this.clock.tick(1);
615
616 assert.equal(endOfStreams, 0, 'did not trigger ended');
617
618 // verify stats
619 assert.equal(loader.mediaBytesTransferred, 10, '10 bytes');
620 assert.equal(loader.mediaRequests, 1, '1 request');
621 });
622
623 QUnit.test('saves segment info to new segment after playlist refresh',
624 function(assert) {
625 let playlist = playlistWithDuration(40);
626 let buffered = videojs.createTimeRanges();
627
628 loader.buffered_ = () => buffered;
629
630 playlist.endList = false;
631
632 loader.playlist(playlist);
633 loader.mimeType(this.mimeType);
634 loader.load();
635 this.clock.tick(1);
636
637 assert.equal(loader.state, 'WAITING', 'in waiting state');
638 assert.equal(loader.pendingSegment_.uri, '0.ts', 'first segment pending');
639 assert.equal(loader.pendingSegment_.segment.uri,
640 '0.ts',
641 'correct segment reference');
642
643 // wrap up the first request to set mediaIndex and start normal live streaming
644 this.requests[0].response = new Uint8Array(10).buffer;
645 this.requests.shift().respond(200, null, '');
646 buffered = videojs.createTimeRanges([[0, 10]]);
647 this.updateend();
648 this.clock.tick(1);
649
650 assert.equal(loader.state, 'WAITING', 'in waiting state');
651 assert.equal(loader.pendingSegment_.uri, '1.ts', 'second segment pending');
652 assert.equal(loader.pendingSegment_.segment.uri,
653 '1.ts',
654 'correct segment reference');
655
656 // playlist updated during waiting
657 let playlistUpdated = playlistWithDuration(40);
658
659 playlistUpdated.segments.shift();
660 playlistUpdated.mediaSequence++;
661 loader.playlist(playlistUpdated);
662
663 assert.equal(loader.pendingSegment_.uri, '1.ts', 'second segment still pending');
664 assert.equal(loader.pendingSegment_.segment.uri,
665 '1.ts',
666 'correct segment reference');
667
668 // mock probeSegmentInfo as the response bytes aren't parsable (and won't provide
669 // time info)
670 loader.syncController_.probeSegmentInfo = (segmentInfo) => {
671 segmentInfo.segment.start = 10;
672 segmentInfo.segment.end = 20;
673 };
674
675 this.requests[0].response = new Uint8Array(10).buffer;
676 this.requests.shift().respond(200, null, '');
677
678 assert.equal(playlistUpdated.segments[0].start,
679 10,
680 'set start on segment of new playlist');
681 assert.equal(playlistUpdated.segments[0].end,
682 20,
683 'set end on segment of new playlist');
684 assert.ok(!playlist.segments[1].start,
685 'did not set start on segment of old playlist');
686 assert.ok(!playlist.segments[1].end, 'did not set end on segment of old playlist');
687 });
688
689 QUnit.test(
690 'saves segment info to old segment after playlist refresh if segment fell off',
691 function(assert) {
692 let playlist = playlistWithDuration(40);
693 let buffered = videojs.createTimeRanges();
694
695 loader.buffered_ = () => buffered;
696
697 playlist.endList = false;
698
699 loader.playlist(playlist);
700 loader.mimeType(this.mimeType);
701 loader.load();
702 this.clock.tick(1);
703
704 assert.equal(loader.state, 'WAITING', 'in waiting state');
705 assert.equal(loader.pendingSegment_.uri, '0.ts', 'first segment pending');
706 assert.equal(loader.pendingSegment_.segment.uri,
707 '0.ts',
708 'correct segment reference');
709
710 // wrap up the first request to set mediaIndex and start normal live streaming
711 this.requests[0].response = new Uint8Array(10).buffer;
712 this.requests.shift().respond(200, null, '');
713 buffered = videojs.createTimeRanges([[0, 10]]);
714 this.updateend();
715 this.clock.tick(1);
716
717 assert.equal(loader.state, 'WAITING', 'in waiting state');
718 assert.equal(loader.pendingSegment_.uri, '1.ts', 'second segment pending');
719 assert.equal(loader.pendingSegment_.segment.uri,
720 '1.ts',
721 'correct segment reference');
722
723 // playlist updated during waiting
724 let playlistUpdated = playlistWithDuration(40);
725
726 playlistUpdated.segments.shift();
727 playlistUpdated.segments.shift();
728 playlistUpdated.mediaSequence += 2;
729 loader.playlist(playlistUpdated);
730
731 assert.equal(loader.pendingSegment_.uri, '1.ts', 'second segment still pending');
732 assert.equal(loader.pendingSegment_.segment.uri,
733 '1.ts',
734 'correct segment reference');
735
736 // mock probeSegmentInfo as the response bytes aren't parsable (and won't provide
737 // time info)
738 loader.syncController_.probeSegmentInfo = (segmentInfo) => {
739 segmentInfo.segment.start = 10;
740 segmentInfo.segment.end = 20;
741 };
742
743 this.requests[0].response = new Uint8Array(10).buffer;
744 this.requests.shift().respond(200, null, '');
745
746 assert.equal(playlist.segments[1].start,
747 10,
748 'set start on segment of old playlist');
749 assert.equal(playlist.segments[1].end,
750 20,
751 'set end on segment of old playlist');
752 assert.ok(!playlistUpdated.segments[0].start,
753 'no start info for first segment of new playlist');
754 assert.ok(!playlistUpdated.segments[0].end,
755 'no end info for first segment of new playlist');
756 });
757
758 QUnit.test('errors when trying to switch from audio and video to audio only',
759 function(assert) {
760 const playlist = playlistWithDuration(40);
761 const errors = [];
762
763 loader.on('error', () => errors.push(loader.error()));
764
765 loader.playlist(playlist);
766 loader.mimeType(this.mimeType);
767 loader.load();
768 this.clock.tick(1);
769 loader.syncController_.probeSegmentInfo = () => {
770 return {
771 start: 0,
772 end: 10,
773 containsAudio: true,
774 containsVideo: true
775 };
776 };
777 this.requests[0].response = new Uint8Array(10).buffer;
778 this.requests.shift().respond(200, null, '');
779 loader.buffered_ = () => videojs.createTimeRanges([[0, 10]]);
780 this.updateend();
781 this.clock.tick(1);
782
783 assert.equal(errors.length, 0, 'no errors');
784
785 loader.syncController_.probeSegmentInfo = () => {
786 return {
787 start: 10,
788 end: 20,
789 containsAudio: true,
790 containsVideo: false
791 };
792 };
793 this.requests[0].response = new Uint8Array(10).buffer;
794 this.requests.shift().respond(200, null, '');
795
796 assert.equal(errors.length, 1, 'one error');
797 assert.equal(errors[0].message,
798 'Only audio found in segment when we expected video.' +
799 ' We can\'t switch to audio only from a stream that had video.' +
800 ' To get rid of this message, please add codec information to the' +
801 ' manifest.',
802 'correct error message');
803 });
804
805 QUnit.test('errors when trying to switch from audio only to audio and video',
806 function(assert) {
807 const playlist = playlistWithDuration(40);
808 const errors = [];
809
810 loader.on('error', () => errors.push(loader.error()));
811
812 loader.playlist(playlist);
813 loader.mimeType(this.mimeType);
814 loader.load();
815 this.clock.tick(1);
816 loader.syncController_.probeSegmentInfo = () => {
817 return {
818 start: 0,
819 end: 10,
820 containsAudio: true,
821 containsVideo: false
822 };
823 };
824 this.requests[0].response = new Uint8Array(10).buffer;
825 this.requests.shift().respond(200, null, '');
826 loader.buffered_ = () => videojs.createTimeRanges([[0, 10]]);
827 this.updateend();
828 this.clock.tick(1);
829
830 assert.equal(errors.length, 0, 'no errors');
831
832 loader.syncController_.probeSegmentInfo = () => {
833 return {
834 start: 10,
835 end: 20,
836 containsAudio: true,
837 containsVideo: true
838 };
839 };
840 this.requests[0].response = new Uint8Array(10).buffer;
841 this.requests.shift().respond(200, null, '');
842
843 assert.equal(errors.length, 1, 'one error');
844 assert.equal(errors[0].message,
845 'Video found in segment when we expected only audio.' +
846 ' We can\'t switch to a stream with video from an audio only stream.' +
847 ' To get rid of this message, please add codec information to the' +
848 ' manifest.',
849 'correct error message');
850 });
851
852 QUnit.test('no error when not switching from audio and video', function(assert) {
853 const playlist = playlistWithDuration(40);
854 const errors = [];
855
856 loader.on('error', () => errors.push(loader.error()));
857
858 loader.playlist(playlist);
859 loader.mimeType(this.mimeType);
860 loader.load();
861 this.clock.tick(1);
862 loader.syncController_.probeSegmentInfo = () => {
863 return {
864 start: 0,
865 end: 10,
866 containsAudio: true,
867 containsVideo: true
868 };
869 };
870 this.requests[0].response = new Uint8Array(10).buffer;
871 this.requests.shift().respond(200, null, '');
872 loader.buffered_ = () => videojs.createTimeRanges([[0, 10]]);
873 this.updateend();
874 this.clock.tick(1);
875
876 assert.equal(errors.length, 0, 'no errors');
877
878 loader.syncController_.probeSegmentInfo = () => {
879 return {
880 start: 10,
881 end: 20,
882 containsAudio: true,
883 containsVideo: true
884 };
885 };
886 this.requests[0].response = new Uint8Array(10).buffer;
887 this.requests.shift().respond(200, null, '');
888
889 assert.equal(errors.length, 0, 'no errors');
890 });
891 });
892});