1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | import videojs from 'video.js';
|
7 | import window from 'global/window';
|
8 | import {isAudioCodec} from '@videojs/vhs-utils/es/codecs.js';
|
9 | import {TIME_FUDGE_FACTOR} from './ranges.js';
|
10 |
|
11 | const {createTimeRange} = videojs;
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | export const getPartsAndSegments = (playlist) => (playlist.segments || []).reduce((acc, segment, si) => {
|
22 | if (segment.parts) {
|
23 | segment.parts.forEach(function(part, pi) {
|
24 | acc.push({duration: part.duration, segmentIndex: si, partIndex: pi, part, segment});
|
25 | });
|
26 | } else {
|
27 | acc.push({duration: segment.duration, segmentIndex: si, partIndex: null, segment, part: null});
|
28 | }
|
29 | return acc;
|
30 | }, []);
|
31 |
|
32 | export const getLastParts = (media) => {
|
33 | const lastSegment = media.segments && media.segments.length && media.segments[media.segments.length - 1];
|
34 |
|
35 | return lastSegment && lastSegment.parts || [];
|
36 | };
|
37 |
|
38 | export const getKnownPartCount = ({preloadSegment}) => {
|
39 | if (!preloadSegment) {
|
40 | return;
|
41 | }
|
42 | const {parts, preloadHints} = preloadSegment;
|
43 | let partCount = (preloadHints || [])
|
44 | .reduce((count, hint) => count + (hint.type === 'PART' ? 1 : 0), 0);
|
45 |
|
46 | partCount += (parts && parts.length) ? parts.length : 0;
|
47 |
|
48 | return partCount;
|
49 | };
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | export const liveEdgeDelay = (master, media) => {
|
59 | if (media.endList) {
|
60 | return 0;
|
61 | }
|
62 |
|
63 |
|
64 | if (master && master.suggestedPresentationDelay) {
|
65 | return master.suggestedPresentationDelay;
|
66 | }
|
67 |
|
68 | const hasParts = getLastParts(media).length > 0;
|
69 |
|
70 |
|
71 | if (hasParts && media.serverControl && media.serverControl.partHoldBack) {
|
72 | return media.serverControl.partHoldBack;
|
73 | } else if (hasParts && media.partTargetDuration) {
|
74 | return media.partTargetDuration * 3;
|
75 |
|
76 |
|
77 | } else if (media.serverControl && media.serverControl.holdBack) {
|
78 | return media.serverControl.holdBack;
|
79 | } else if (media.targetDuration) {
|
80 | return media.targetDuration * 3;
|
81 | }
|
82 |
|
83 | return 0;
|
84 | };
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | const backwardDuration = function(playlist, endSequence) {
|
95 | let result = 0;
|
96 | let i = endSequence - playlist.mediaSequence;
|
97 |
|
98 |
|
99 | let segment = playlist.segments[i];
|
100 |
|
101 |
|
102 |
|
103 | if (segment) {
|
104 | if (typeof segment.start !== 'undefined') {
|
105 | return { result: segment.start, precise: true };
|
106 | }
|
107 | if (typeof segment.end !== 'undefined') {
|
108 | return {
|
109 | result: segment.end - segment.duration,
|
110 | precise: true
|
111 | };
|
112 | }
|
113 | }
|
114 | while (i--) {
|
115 | segment = playlist.segments[i];
|
116 | if (typeof segment.end !== 'undefined') {
|
117 | return { result: result + segment.end, precise: true };
|
118 | }
|
119 |
|
120 | result += segment.duration;
|
121 |
|
122 | if (typeof segment.start !== 'undefined') {
|
123 | return { result: result + segment.start, precise: true };
|
124 | }
|
125 | }
|
126 | return { result, precise: false };
|
127 | };
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 | const forwardDuration = function(playlist, endSequence) {
|
137 | let result = 0;
|
138 | let segment;
|
139 | let i = endSequence - playlist.mediaSequence;
|
140 |
|
141 |
|
142 |
|
143 | for (; i < playlist.segments.length; i++) {
|
144 | segment = playlist.segments[i];
|
145 | if (typeof segment.start !== 'undefined') {
|
146 | return {
|
147 | result: segment.start - result,
|
148 | precise: true
|
149 | };
|
150 | }
|
151 |
|
152 | result += segment.duration;
|
153 |
|
154 | if (typeof segment.end !== 'undefined') {
|
155 | return {
|
156 | result: segment.end - result,
|
157 | precise: true
|
158 | };
|
159 | }
|
160 |
|
161 | }
|
162 |
|
163 | return { result: -1, precise: false };
|
164 | };
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | const intervalDuration = function(playlist, endSequence, expired) {
|
180 | if (typeof endSequence === 'undefined') {
|
181 | endSequence = playlist.mediaSequence + playlist.segments.length;
|
182 | }
|
183 |
|
184 | if (endSequence < playlist.mediaSequence) {
|
185 | return 0;
|
186 | }
|
187 |
|
188 |
|
189 | const backward = backwardDuration(playlist, endSequence);
|
190 |
|
191 | if (backward.precise) {
|
192 |
|
193 |
|
194 |
|
195 | return backward.result;
|
196 | }
|
197 |
|
198 |
|
199 |
|
200 | const forward = forwardDuration(playlist, endSequence);
|
201 |
|
202 | if (forward.precise) {
|
203 |
|
204 |
|
205 | return forward.result;
|
206 | }
|
207 |
|
208 |
|
209 | return backward.result + expired;
|
210 | };
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | export const duration = function(playlist, endSequence, expired) {
|
228 | if (!playlist) {
|
229 | return 0;
|
230 | }
|
231 |
|
232 | if (typeof expired !== 'number') {
|
233 | expired = 0;
|
234 | }
|
235 |
|
236 |
|
237 |
|
238 | if (typeof endSequence === 'undefined') {
|
239 |
|
240 | if (playlist.totalDuration) {
|
241 | return playlist.totalDuration;
|
242 | }
|
243 |
|
244 |
|
245 | if (!playlist.endList) {
|
246 | return window.Infinity;
|
247 | }
|
248 | }
|
249 |
|
250 |
|
251 | return intervalDuration(
|
252 | playlist,
|
253 | endSequence,
|
254 | expired
|
255 | );
|
256 | };
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | export const sumDurations = function({defaultDuration, durationList, startIndex, endIndex}) {
|
271 | let durations = 0;
|
272 |
|
273 | if (startIndex > endIndex) {
|
274 | [startIndex, endIndex] = [endIndex, startIndex];
|
275 | }
|
276 |
|
277 | if (startIndex < 0) {
|
278 | for (let i = startIndex; i < Math.min(0, endIndex); i++) {
|
279 | durations += defaultDuration;
|
280 | }
|
281 | startIndex = 0;
|
282 | }
|
283 |
|
284 | for (let i = startIndex; i < endIndex; i++) {
|
285 | durations += durationList[i].duration;
|
286 | }
|
287 |
|
288 | return durations;
|
289 | };
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 | export const playlistEnd = function(playlist, expired, useSafeLiveEnd, liveEdgePadding) {
|
311 | if (!playlist || !playlist.segments) {
|
312 | return null;
|
313 | }
|
314 | if (playlist.endList) {
|
315 | return duration(playlist);
|
316 | }
|
317 |
|
318 | if (expired === null) {
|
319 | return null;
|
320 | }
|
321 |
|
322 | expired = expired || 0;
|
323 |
|
324 | let lastSegmentTime = intervalDuration(
|
325 | playlist,
|
326 | playlist.mediaSequence + playlist.segments.length,
|
327 | expired
|
328 | );
|
329 |
|
330 | if (useSafeLiveEnd) {
|
331 | liveEdgePadding = typeof liveEdgePadding === 'number' ? liveEdgePadding : liveEdgeDelay(null, playlist);
|
332 | lastSegmentTime -= liveEdgePadding;
|
333 | }
|
334 |
|
335 |
|
336 | return Math.max(0, lastSegmentTime);
|
337 | };
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 | export const seekable = function(playlist, expired, liveEdgePadding) {
|
357 | const useSafeLiveEnd = true;
|
358 | const seekableStart = expired || 0;
|
359 | const seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
|
360 |
|
361 | if (seekableEnd === null) {
|
362 | return createTimeRange();
|
363 | }
|
364 | return createTimeRange(seekableStart, seekableEnd);
|
365 | };
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 | export const getMediaInfoForTime = function({
|
381 | playlist,
|
382 | currentTime,
|
383 | startingSegmentIndex,
|
384 | startingPartIndex,
|
385 | startTime,
|
386 | experimentalExactManifestTimings
|
387 | }) {
|
388 |
|
389 | let time = currentTime - startTime;
|
390 | const partsAndSegments = getPartsAndSegments(playlist);
|
391 |
|
392 | let startIndex = 0;
|
393 |
|
394 | for (let i = 0; i < partsAndSegments.length; i++) {
|
395 | const partAndSegment = partsAndSegments[i];
|
396 |
|
397 | if (startingSegmentIndex !== partAndSegment.segmentIndex) {
|
398 | continue;
|
399 | }
|
400 |
|
401 |
|
402 | if (typeof startingPartIndex === 'number' && typeof partAndSegment.partIndex === 'number' && startingPartIndex !== partAndSegment.partIndex) {
|
403 | continue;
|
404 | }
|
405 |
|
406 | startIndex = i;
|
407 | break;
|
408 | }
|
409 |
|
410 | if (time < 0) {
|
411 |
|
412 |
|
413 | if (startIndex > 0) {
|
414 | for (let i = startIndex - 1; i >= 0; i--) {
|
415 | const partAndSegment = partsAndSegments[i];
|
416 |
|
417 | time += partAndSegment.duration;
|
418 |
|
419 | if (experimentalExactManifestTimings) {
|
420 | if (time < 0) {
|
421 | continue;
|
422 | }
|
423 | } else if ((time + TIME_FUDGE_FACTOR) <= 0) {
|
424 | continue;
|
425 | }
|
426 | return {
|
427 | partIndex: partAndSegment.partIndex,
|
428 | segmentIndex: partAndSegment.segmentIndex,
|
429 | startTime: startTime - sumDurations({
|
430 | defaultDuration: playlist.targetDuration,
|
431 | durationList: partsAndSegments,
|
432 | startIndex,
|
433 | endIndex: i
|
434 | })
|
435 | };
|
436 | }
|
437 | }
|
438 |
|
439 |
|
440 |
|
441 | return {
|
442 | partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
|
443 | segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
|
444 | startTime: currentTime
|
445 | };
|
446 | }
|
447 |
|
448 |
|
449 |
|
450 |
|
451 | if (startIndex < 0) {
|
452 | for (let i = startIndex; i < 0; i++) {
|
453 | time -= playlist.targetDuration;
|
454 |
|
455 | if (time < 0) {
|
456 | return {
|
457 | partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
|
458 | segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
|
459 | startTime: currentTime
|
460 | };
|
461 | }
|
462 | }
|
463 | startIndex = 0;
|
464 | }
|
465 |
|
466 |
|
467 |
|
468 | for (let i = startIndex; i < partsAndSegments.length; i++) {
|
469 | const partAndSegment = partsAndSegments[i];
|
470 |
|
471 | time -= partAndSegment.duration;
|
472 |
|
473 | if (experimentalExactManifestTimings) {
|
474 | if (time > 0) {
|
475 | continue;
|
476 | }
|
477 | } else if ((time - TIME_FUDGE_FACTOR) >= 0) {
|
478 | continue;
|
479 | }
|
480 |
|
481 | return {
|
482 | partIndex: partAndSegment.partIndex,
|
483 | segmentIndex: partAndSegment.segmentIndex,
|
484 | startTime: startTime + sumDurations({
|
485 | defaultDuration: playlist.targetDuration,
|
486 | durationList: partsAndSegments,
|
487 | startIndex,
|
488 | endIndex: i
|
489 | })
|
490 | };
|
491 | }
|
492 |
|
493 |
|
494 | return {
|
495 | segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
|
496 | partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex,
|
497 | startTime: currentTime
|
498 | };
|
499 | };
|
500 |
|
501 |
|
502 |
|
503 |
|
504 |
|
505 |
|
506 |
|
507 |
|
508 | export const isBlacklisted = function(playlist) {
|
509 | return playlist.excludeUntil && playlist.excludeUntil > Date.now();
|
510 | };
|
511 |
|
512 |
|
513 |
|
514 |
|
515 |
|
516 |
|
517 |
|
518 |
|
519 |
|
520 | export const isIncompatible = function(playlist) {
|
521 | return playlist.excludeUntil && playlist.excludeUntil === Infinity;
|
522 | };
|
523 |
|
524 |
|
525 |
|
526 |
|
527 |
|
528 |
|
529 |
|
530 |
|
531 | export const isEnabled = function(playlist) {
|
532 | const blacklisted = isBlacklisted(playlist);
|
533 |
|
534 | return (!playlist.disabled && !blacklisted);
|
535 | };
|
536 |
|
537 |
|
538 |
|
539 |
|
540 |
|
541 |
|
542 |
|
543 |
|
544 | export const isDisabled = function(playlist) {
|
545 | return playlist.disabled;
|
546 | };
|
547 |
|
548 |
|
549 |
|
550 |
|
551 |
|
552 |
|
553 | export const isAes = function(media) {
|
554 | for (let i = 0; i < media.segments.length; i++) {
|
555 | if (media.segments[i].key) {
|
556 | return true;
|
557 | }
|
558 | }
|
559 | return false;
|
560 | };
|
561 |
|
562 |
|
563 |
|
564 |
|
565 |
|
566 |
|
567 |
|
568 |
|
569 |
|
570 |
|
571 |
|
572 |
|
573 | export const hasAttribute = function(attr, playlist) {
|
574 | return playlist.attributes && playlist.attributes[attr];
|
575 | };
|
576 |
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 |
|
589 |
|
590 |
|
591 |
|
592 |
|
593 | export const estimateSegmentRequestTime = function(
|
594 | segmentDuration,
|
595 | bandwidth,
|
596 | playlist,
|
597 | bytesReceived = 0
|
598 | ) {
|
599 | if (!hasAttribute('BANDWIDTH', playlist)) {
|
600 | return NaN;
|
601 | }
|
602 |
|
603 | const size = segmentDuration * playlist.attributes.BANDWIDTH;
|
604 |
|
605 | return (size - (bytesReceived * 8)) / bandwidth;
|
606 | };
|
607 |
|
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 | export const isLowestEnabledRendition = (master, media) => {
|
614 | if (master.playlists.length === 1) {
|
615 | return true;
|
616 | }
|
617 |
|
618 | const currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
|
619 |
|
620 | return (master.playlists.filter((playlist) => {
|
621 | if (!isEnabled(playlist)) {
|
622 | return false;
|
623 | }
|
624 |
|
625 | return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
|
626 |
|
627 | }).length === 0);
|
628 | };
|
629 |
|
630 | export const playlistMatch = (a, b) => {
|
631 |
|
632 |
|
633 |
|
634 | if (!a && !b || (!a && b) || (a && !b)) {
|
635 | return false;
|
636 | }
|
637 |
|
638 |
|
639 | if (a === b) {
|
640 | return true;
|
641 | }
|
642 |
|
643 |
|
644 |
|
645 | if (a.id && b.id && a.id === b.id) {
|
646 | return true;
|
647 | }
|
648 |
|
649 |
|
650 |
|
651 | if (a.resolvedUri && b.resolvedUri && a.resolvedUri === b.resolvedUri) {
|
652 | return true;
|
653 | }
|
654 |
|
655 |
|
656 |
|
657 | if (a.uri && b.uri && a.uri === b.uri) {
|
658 | return true;
|
659 | }
|
660 |
|
661 | return false;
|
662 | };
|
663 |
|
664 | const someAudioVariant = function(master, callback) {
|
665 | const AUDIO = master && master.mediaGroups && master.mediaGroups.AUDIO || {};
|
666 | let found = false;
|
667 |
|
668 | for (const groupName in AUDIO) {
|
669 | for (const label in AUDIO[groupName]) {
|
670 | found = callback(AUDIO[groupName][label]);
|
671 |
|
672 | if (found) {
|
673 | break;
|
674 | }
|
675 | }
|
676 |
|
677 | if (found) {
|
678 | break;
|
679 | }
|
680 | }
|
681 |
|
682 | return !!found;
|
683 | };
|
684 |
|
685 | export const isAudioOnly = (master) => {
|
686 |
|
687 |
|
688 | if (!master || !master.playlists || !master.playlists.length) {
|
689 |
|
690 |
|
691 | const found = someAudioVariant(master, (variant) =>
|
692 | (variant.playlists && variant.playlists.length) || variant.uri);
|
693 |
|
694 | return found;
|
695 | }
|
696 |
|
697 |
|
698 | for (let i = 0; i < master.playlists.length; i++) {
|
699 | const playlist = master.playlists[i];
|
700 | const CODECS = playlist.attributes && playlist.attributes.CODECS;
|
701 |
|
702 |
|
703 | if (CODECS && CODECS.split(',').every((c) => isAudioCodec(c))) {
|
704 | continue;
|
705 | }
|
706 |
|
707 |
|
708 | const found = someAudioVariant(master, (variant) => playlistMatch(playlist, variant));
|
709 |
|
710 | if (found) {
|
711 | continue;
|
712 | }
|
713 |
|
714 |
|
715 |
|
716 | return false;
|
717 | }
|
718 |
|
719 |
|
720 |
|
721 | return true;
|
722 | };
|
723 |
|
724 |
|
725 | export default {
|
726 | liveEdgeDelay,
|
727 | duration,
|
728 | seekable,
|
729 | getMediaInfoForTime,
|
730 | isEnabled,
|
731 | isDisabled,
|
732 | isBlacklisted,
|
733 | isIncompatible,
|
734 | playlistEnd,
|
735 | isAes,
|
736 | hasAttribute,
|
737 | estimateSegmentRequestTime,
|
738 | isLowestEnabledRendition,
|
739 | isAudioOnly,
|
740 | playlistMatch
|
741 | };
|