shaka-player 3.2.2 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- package/.github/ISSUE_TEMPLATE/question.md +1 -1
- package/AUTHORS +6 -5
- package/CHANGELOG.md +247 -0
- package/CONTRIBUTORS +4 -2
- package/build/types/core +3 -0
- package/build/types/offline +1 -0
- package/build/types/polyfill +1 -0
- package/build/types/ui +2 -0
- package/demo/common/assets.js +18 -0
- package/demo/common/message_ids.js +13 -1
- package/demo/config.js +31 -1
- package/demo/index.html +1 -1
- package/demo/locales/en.json +12 -0
- package/demo/locales/source.json +48 -0
- package/dist/controls.css +26 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +19 -16
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +19 -16
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +18 -11
- package/dist/locales.js +28 -26
- package/dist/receiver.compiled.debug.js +11 -9
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +11 -9
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +205 -15
- package/dist/shaka-player.compiled.debug.d.ts +205 -15
- package/dist/shaka-player.compiled.debug.externs.js +256 -6
- package/dist/shaka-player.compiled.debug.js +406 -354
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +256 -6
- package/dist/shaka-player.compiled.js +826 -800
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +221 -16
- package/dist/shaka-player.ui.debug.d.ts +221 -16
- package/dist/shaka-player.ui.debug.externs.js +283 -6
- package/dist/shaka-player.ui.debug.js +547 -476
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +283 -6
- package/dist/shaka-player.ui.js +1019 -980
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/bg-fetch-after.gv +29 -0
- package/docs/design/bg-fetch-after.gv.png +0 -0
- package/docs/design/bg-fetch-before.gv +18 -0
- package/docs/design/bg-fetch-before.gv.png +0 -0
- package/docs/design/bg-fetch.md +134 -0
- package/docs/tutorials/blob-url.md +15 -0
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/ui-customization.md +66 -0
- package/docs/tutorials/ui.md +5 -5
- package/externs/ima.js +3 -0
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/manifest.js +12 -1
- package/externs/shaka/manifest_parser.js +10 -1
- package/externs/shaka/net.js +20 -4
- package/externs/shaka/offline.js +31 -2
- package/externs/shaka/player.js +130 -5
- package/externs/shaka/text.js +14 -0
- package/externs/webcrypto.js +18 -0
- package/lib/abr/ewma.js +14 -0
- package/lib/abr/ewma_bandwidth_estimator.js +16 -2
- package/lib/abr/simple_abr_manager.js +6 -0
- package/lib/ads/client_side_ad.js +8 -0
- package/lib/ads/client_side_ad_manager.js +8 -4
- package/lib/ads/server_side_ad.js +7 -0
- package/lib/cast/cast_utils.js +1 -0
- package/lib/dash/dash_parser.js +48 -14
- package/lib/dash/segment_base.js +25 -1
- package/lib/dash/segment_list.js +18 -9
- package/lib/dash/segment_template.js +23 -10
- package/lib/hls/hls_parser.js +86 -23
- package/lib/media/quality_observer.js +291 -0
- package/lib/media/segment_index.js +6 -0
- package/lib/media/segment_reference.js +18 -3
- package/lib/media/streaming_engine.js +42 -4
- package/lib/net/http_fetch_plugin.js +31 -11
- package/lib/net/http_plugin_utils.js +1 -0
- package/lib/net/http_xhr_plugin.js +40 -14
- package/lib/net/networking_engine.js +81 -18
- package/lib/offline/download_info.js +68 -0
- package/lib/offline/download_manager.js +13 -5
- package/lib/offline/indexeddb/base_storage_cell.js +35 -0
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
- package/lib/offline/storage.js +393 -284
- package/lib/offline/stored_content_utils.js +2 -0
- package/lib/player.js +206 -7
- package/lib/polyfill/random_uuid.js +52 -0
- package/lib/text/cue.js +12 -0
- package/lib/text/ttml_text_parser.js +25 -2
- package/lib/text/ui_text_displayer.js +2 -2
- package/lib/util/cmcd_manager.js +826 -0
- package/lib/util/error.js +6 -0
- package/lib/util/language_utils.js +12 -0
- package/lib/util/mutex.js +52 -0
- package/lib/util/periods.js +98 -64
- package/lib/util/player_configuration.js +18 -0
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +3 -0
- package/test/dash/dash_parser_content_protection_unit.js +2 -0
- package/test/dash/dash_parser_live_unit.js +101 -1
- package/test/dash/dash_parser_manifest_unit.js +35 -1
- package/test/dash/dash_parser_segment_base_unit.js +2 -0
- package/test/dash/dash_parser_segment_list_unit.js +2 -1
- package/test/dash/dash_parser_segment_template_unit.js +2 -1
- package/test/hls/hls_live_unit.js +2 -0
- package/test/hls/hls_parser_unit.js +2 -0
- package/test/media/quality_observer_unit.js +139 -0
- package/test/media/segment_reference_unit.js +16 -1
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +2 -0
- package/test/net/http_plugin_unit.js +20 -15
- package/test/offline/storage_integration.js +108 -4
- package/test/player_integration.js +99 -0
- package/test/test/util/dash_parser_util.js +4 -0
- package/test/test/util/fake_ad.js +7 -0
- package/test/test/util/fake_drm_engine.js +9 -2
- package/test/test/util/manifest_generator.js +3 -2
- package/test/test/util/test_scheme.js +14 -1
- package/test/text/ttml_text_parser_unit.js +47 -0
- package/test/ui/ui_unit.js +250 -0
- package/test/util/cmcd_manager_unit.js +277 -0
- package/ui/airplay_button.js +1 -0
- package/ui/audio_language_selection.js +7 -0
- package/ui/cast_button.js +1 -0
- package/ui/context_menu.js +109 -0
- package/ui/controls.js +41 -7
- package/ui/controls.less +1 -0
- package/ui/enums.js +4 -0
- package/ui/externs/ui.js +28 -1
- package/ui/fast_forward_button.js +14 -5
- package/ui/fullscreen_button.js +1 -0
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +74 -0
- package/ui/less/overflow_menu.less +0 -10
- package/ui/less/tooltip.less +97 -0
- package/ui/locales/en.json +1 -0
- package/ui/locales/source.json +4 -0
- package/ui/loop_button.js +5 -0
- package/ui/mute_button.js +2 -1
- package/ui/overflow_menu.js +5 -25
- package/ui/pip_button.js +5 -0
- package/ui/play_button.js +2 -2
- package/ui/playback_rate_selection.js +8 -9
- package/ui/resolution_selection.js +7 -0
- package/ui/rewind_button.js +15 -6
- package/ui/seek_bar.js +4 -2
- package/ui/settings_menu.js +36 -4
- package/ui/small_play_button.js +1 -0
- package/ui/statistics_button.js +253 -0
- package/ui/text_selection.js +11 -4
- package/ui/ui.js +30 -0
- package/ui/volume_bar.js +1 -1
package/lib/util/error.js
CHANGED
|
@@ -986,6 +986,12 @@ shaka.util.Error.Code = {
|
|
|
986
986
|
*/
|
|
987
987
|
'DOWNLOAD_SIZE_CALLBACK_ERROR': 9015,
|
|
988
988
|
|
|
989
|
+
/**
|
|
990
|
+
* The storage cell does not allow new operations that significantly change
|
|
991
|
+
* existing data.
|
|
992
|
+
*/
|
|
993
|
+
'MODIFY_OPERATION_NOT_SUPPORTED': 9016,
|
|
994
|
+
|
|
989
995
|
/**
|
|
990
996
|
* CS IMA SDK, required for ad insertion, has not been included on the page.
|
|
991
997
|
*/
|
|
@@ -13,6 +13,7 @@ goog.require('shaka.util.ManifestParserUtils');
|
|
|
13
13
|
/**
|
|
14
14
|
* @summary A set of language utility functions.
|
|
15
15
|
* @final
|
|
16
|
+
* @export
|
|
16
17
|
*/
|
|
17
18
|
shaka.util.LanguageUtils = class {
|
|
18
19
|
/**
|
|
@@ -32,6 +33,7 @@ shaka.util.LanguageUtils = class {
|
|
|
32
33
|
* @param {string} locale1
|
|
33
34
|
* @param {string} locale2
|
|
34
35
|
* @return {boolean}
|
|
36
|
+
* @export
|
|
35
37
|
*/
|
|
36
38
|
static areLocaleCompatible(locale1, locale2) {
|
|
37
39
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -62,6 +64,7 @@ shaka.util.LanguageUtils = class {
|
|
|
62
64
|
* @param {string} locale1
|
|
63
65
|
* @param {string} locale2
|
|
64
66
|
* @return {boolean}
|
|
67
|
+
* @export
|
|
65
68
|
*/
|
|
66
69
|
static areLanguageCompatible(locale1, locale2) {
|
|
67
70
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -102,6 +105,7 @@ shaka.util.LanguageUtils = class {
|
|
|
102
105
|
* @param {string} possibleParent
|
|
103
106
|
* @param {string} possibleChild
|
|
104
107
|
* @return {boolean}
|
|
108
|
+
* @export
|
|
105
109
|
*/
|
|
106
110
|
static isParentOf(possibleParent, possibleChild) {
|
|
107
111
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -146,6 +150,7 @@ shaka.util.LanguageUtils = class {
|
|
|
146
150
|
* @param {string} localeA
|
|
147
151
|
* @param {string} localeB
|
|
148
152
|
* @return {boolean}
|
|
153
|
+
* @export
|
|
149
154
|
*/
|
|
150
155
|
static isSiblingOf(localeA, localeB) {
|
|
151
156
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -181,6 +186,7 @@ shaka.util.LanguageUtils = class {
|
|
|
181
186
|
*
|
|
182
187
|
* @param {string} locale
|
|
183
188
|
* @return {string}
|
|
189
|
+
* @export
|
|
184
190
|
*/
|
|
185
191
|
static normalize(locale) {
|
|
186
192
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -217,6 +223,7 @@ shaka.util.LanguageUtils = class {
|
|
|
217
223
|
* @param {string} a
|
|
218
224
|
* @param {string} b
|
|
219
225
|
* @return {boolean}
|
|
226
|
+
* @export
|
|
220
227
|
*/
|
|
221
228
|
static areSiblings(a, b) {
|
|
222
229
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -235,6 +242,7 @@ shaka.util.LanguageUtils = class {
|
|
|
235
242
|
* @param {string} target
|
|
236
243
|
* @param {string} candidate
|
|
237
244
|
* @return {number}
|
|
245
|
+
* @export
|
|
238
246
|
*/
|
|
239
247
|
static relatedness(target, candidate) {
|
|
240
248
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -271,6 +279,7 @@ shaka.util.LanguageUtils = class {
|
|
|
271
279
|
*
|
|
272
280
|
* @param {string} lang
|
|
273
281
|
* @return {string}
|
|
282
|
+
* @export
|
|
274
283
|
*/
|
|
275
284
|
static getBase(lang) {
|
|
276
285
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -300,6 +309,7 @@ shaka.util.LanguageUtils = class {
|
|
|
300
309
|
*
|
|
301
310
|
* @param {shaka.extern.Stream} stream
|
|
302
311
|
* @return {string}
|
|
312
|
+
* @export
|
|
303
313
|
*/
|
|
304
314
|
static getLocaleForText(stream) {
|
|
305
315
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -322,6 +332,7 @@ shaka.util.LanguageUtils = class {
|
|
|
322
332
|
*
|
|
323
333
|
* @param {shaka.extern.Variant} variant
|
|
324
334
|
* @return {string}
|
|
335
|
+
* @export
|
|
325
336
|
*/
|
|
326
337
|
static getLocaleForVariant(variant) {
|
|
327
338
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -356,6 +367,7 @@ shaka.util.LanguageUtils = class {
|
|
|
356
367
|
* @param {string} target
|
|
357
368
|
* @param {!Iterable.<string>} searchSpace
|
|
358
369
|
* @return {?string}
|
|
370
|
+
* @export
|
|
359
371
|
*/
|
|
360
372
|
static findClosestLocale(target, searchSpace) {
|
|
361
373
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.provide('shaka.util.Mutex');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @summary A simple mutex.
|
|
12
|
+
*/
|
|
13
|
+
shaka.util.Mutex = class {
|
|
14
|
+
/** Creates the mutex. */
|
|
15
|
+
constructor() {
|
|
16
|
+
/** @private {!Array.<function()>} */
|
|
17
|
+
this.waiting_ = [];
|
|
18
|
+
|
|
19
|
+
/** @private {number} */
|
|
20
|
+
this.nextMutexId_ = 0;
|
|
21
|
+
|
|
22
|
+
/** @private {number} */
|
|
23
|
+
this.acquiredMutexId_ = 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @return {!Promise.<number>} mutexId */
|
|
27
|
+
async acquire() {
|
|
28
|
+
const mutexId = ++this.nextMutexId_;
|
|
29
|
+
if (!this.acquiredMutexId_) {
|
|
30
|
+
this.acquiredMutexId_ = mutexId;
|
|
31
|
+
} else {
|
|
32
|
+
await (new Promise((resolve, reject) => {
|
|
33
|
+
this.waiting_.push(() => {
|
|
34
|
+
this.acquiredMutexId_ = mutexId;
|
|
35
|
+
resolve();
|
|
36
|
+
});
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
return mutexId;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @param {number} mutexId */
|
|
43
|
+
release(mutexId) {
|
|
44
|
+
if (mutexId == this.acquiredMutexId_) {
|
|
45
|
+
this.acquiredMutexId_ = 0;
|
|
46
|
+
if (this.waiting_.length > 0) {
|
|
47
|
+
const callback = this.waiting_.shift();
|
|
48
|
+
callback();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
package/lib/util/periods.js
CHANGED
|
@@ -547,8 +547,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
547
547
|
for (const stream of unusedStreams) {
|
|
548
548
|
// Create a new output stream which includes this input stream.
|
|
549
549
|
const outputStream =
|
|
550
|
-
|
|
551
|
-
await shaka.util.PeriodCombiner.createNewOutputStream_(
|
|
550
|
+
shaka.util.PeriodCombiner.createNewOutputStream_(
|
|
552
551
|
stream, streamsPerPeriod, clone, concat,
|
|
553
552
|
unusedStreamsPerPeriod);
|
|
554
553
|
if (outputStream) {
|
|
@@ -619,52 +618,69 @@ shaka.util.PeriodCombiner = class {
|
|
|
619
618
|
static async extendExistingOutputStream_(
|
|
620
619
|
outputStream, streamsPerPeriod, firstNewPeriodIndex, concat,
|
|
621
620
|
unusedStreamsPerPeriod) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
if (!matches) {
|
|
626
|
-
// We were unable to extend this output stream.
|
|
627
|
-
shaka.log.error('No matches extending output stream!',
|
|
628
|
-
outputStream, streamsPerPeriod);
|
|
629
|
-
return false;
|
|
630
|
-
}
|
|
621
|
+
shaka.util.PeriodCombiner.findMatchesInAllPeriods_(streamsPerPeriod,
|
|
622
|
+
outputStream);
|
|
631
623
|
|
|
632
624
|
// This only exists where T == Stream, and this should only ever be called
|
|
633
625
|
// on Stream types. StreamDB should not have pre-existing output streams.
|
|
634
626
|
goog.asserts.assert(outputStream.createSegmentIndex,
|
|
635
627
|
'outputStream should be a Stream type!');
|
|
636
628
|
|
|
629
|
+
if (!outputStream.matchedStreams) {
|
|
630
|
+
// We were unable to extend this output stream.
|
|
631
|
+
shaka.log.error('No matches extending output stream!',
|
|
632
|
+
outputStream, streamsPerPeriod);
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
637
635
|
// We need to create all the per-period segment indexes and append them to
|
|
638
636
|
// the output's MetaSegmentIndex.
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
goog.asserts.assert(matches, 'Matches should be non-null');
|
|
637
|
+
if (outputStream.segmentIndex) {
|
|
638
|
+
await shaka.util.PeriodCombiner.extendOutputSegmentIndex_(outputStream,
|
|
639
|
+
firstNewPeriodIndex);
|
|
640
|
+
}
|
|
644
641
|
|
|
645
|
-
shaka.util.PeriodCombiner.extendOutputStream_(
|
|
646
|
-
|
|
647
|
-
unusedStreamsPerPeriod);
|
|
642
|
+
shaka.util.PeriodCombiner.extendOutputStream_(outputStream,
|
|
643
|
+
firstNewPeriodIndex, concat, unusedStreamsPerPeriod);
|
|
648
644
|
return true;
|
|
649
645
|
}
|
|
650
646
|
|
|
651
647
|
/**
|
|
652
|
-
* Creates
|
|
653
|
-
* segment index
|
|
648
|
+
* Creates the segment indexes for an array of input streams, and append them
|
|
649
|
+
* to the output stream's segment index.
|
|
654
650
|
*
|
|
655
|
-
* @param {
|
|
656
|
-
* @
|
|
651
|
+
* @param {shaka.extern.Stream} outputStream
|
|
652
|
+
* @param {number} firstNewPeriodIndex An index into streamsPerPeriod which
|
|
653
|
+
* represents the first new period that hasn't been processed yet.
|
|
657
654
|
* @private
|
|
658
655
|
*/
|
|
659
|
-
static
|
|
656
|
+
static async extendOutputSegmentIndex_(outputStream, firstNewPeriodIndex) {
|
|
660
657
|
const operations = [];
|
|
658
|
+
const streams = outputStream.matchedStreams;
|
|
659
|
+
goog.asserts.assert(streams, 'matched streams should be valid');
|
|
660
|
+
|
|
661
661
|
for (const stream of streams) {
|
|
662
662
|
operations.push(stream.createSegmentIndex());
|
|
663
663
|
if (stream.trickModeVideo && !stream.trickModeVideo.segmentIndex) {
|
|
664
664
|
operations.push(stream.trickModeVideo.createSegmentIndex());
|
|
665
665
|
}
|
|
666
666
|
}
|
|
667
|
-
|
|
667
|
+
await Promise.all(operations);
|
|
668
|
+
|
|
669
|
+
// Concatenate the new matches onto the stream, starting at the first new
|
|
670
|
+
// period.
|
|
671
|
+
const Iterables = shaka.util.Iterables;
|
|
672
|
+
// Satisfy the compiler about the type.
|
|
673
|
+
// Also checks if the segmentIndex is still valid after the async
|
|
674
|
+
// operations, to make sure we stop if the active stream has changed.
|
|
675
|
+
if (outputStream.segmentIndex instanceof shaka.media.MetaSegmentIndex) {
|
|
676
|
+
for (const {i, item: match} of Iterables.enumerate(streams)) {
|
|
677
|
+
if (match.segmentIndex && i >= firstNewPeriodIndex) {
|
|
678
|
+
goog.asserts.assert(match.segmentIndex,
|
|
679
|
+
'stream should have a segmentIndex.');
|
|
680
|
+
outputStream.segmentIndex.appendSegmentIndex(match.segmentIndex);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
668
684
|
}
|
|
669
685
|
|
|
670
686
|
/**
|
|
@@ -682,7 +698,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
682
698
|
* @param {!Array.<!Set.<T>>} unusedStreamsPerPeriod An array of sets of
|
|
683
699
|
* unused streams from each period.
|
|
684
700
|
*
|
|
685
|
-
* @return {
|
|
701
|
+
* @return {?T} A newly-created output Stream, or null if matches
|
|
686
702
|
* could not be found.`
|
|
687
703
|
*
|
|
688
704
|
* @template T
|
|
@@ -690,35 +706,36 @@ shaka.util.PeriodCombiner = class {
|
|
|
690
706
|
*
|
|
691
707
|
* @private
|
|
692
708
|
*/
|
|
693
|
-
static
|
|
709
|
+
static createNewOutputStream_(
|
|
694
710
|
stream, streamsPerPeriod, clone, concat, unusedStreamsPerPeriod) {
|
|
695
711
|
// Start by cloning the stream without segments, key IDs, etc.
|
|
696
712
|
const outputStream = clone(stream);
|
|
697
713
|
|
|
698
714
|
// Find best-matching streams in all periods.
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
if (!matches) {
|
|
703
|
-
// This is not a stream we can build output from, but it may become part
|
|
704
|
-
// of another output based on another period's stream.
|
|
705
|
-
return null;
|
|
706
|
-
}
|
|
715
|
+
shaka.util.PeriodCombiner.findMatchesInAllPeriods_(streamsPerPeriod,
|
|
716
|
+
outputStream);
|
|
707
717
|
|
|
708
718
|
// This only exists where T == Stream.
|
|
709
719
|
if (outputStream.createSegmentIndex) {
|
|
720
|
+
// Override the createSegmentIndex function of the outputStream.
|
|
721
|
+
outputStream.createSegmentIndex = async () => {
|
|
722
|
+
if (!outputStream.segmentIndex) {
|
|
723
|
+
outputStream.segmentIndex = new shaka.media.MetaSegmentIndex();
|
|
724
|
+
await shaka.util.PeriodCombiner.extendOutputSegmentIndex_(
|
|
725
|
+
outputStream, /* firstNewPeriodIndex= */ 0);
|
|
726
|
+
}
|
|
727
|
+
};
|
|
710
728
|
// For T == Stream, we need to create all the per-period segment indexes
|
|
711
729
|
// in advance. concat() will add them to the output's MetaSegmentIndex.
|
|
712
|
-
await shaka.util.PeriodCombiner.createSegmentIndexes_(matches);
|
|
713
730
|
}
|
|
714
731
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
unusedStreamsPerPeriod);
|
|
732
|
+
if (!outputStream.matchedStreams) {
|
|
733
|
+
// This is not a stream we can build output from, but it may become part
|
|
734
|
+
// of another output based on another period's stream.
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
shaka.util.PeriodCombiner.extendOutputStream_(outputStream,
|
|
738
|
+
/* firstNewPeriodIndex= */ 0, concat, unusedStreamsPerPeriod);
|
|
722
739
|
|
|
723
740
|
return outputStream;
|
|
724
741
|
}
|
|
@@ -726,7 +743,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
726
743
|
/**
|
|
727
744
|
* @param {T} outputStream An existing output stream which needs to be
|
|
728
745
|
* extended into new periods.
|
|
729
|
-
* @param {!Array.<T>} matches A list of matching Streams from each period.
|
|
730
746
|
* @param {number} firstNewPeriodIndex An index into streamsPerPeriod which
|
|
731
747
|
* represents the first new period that hasn't been processed yet.
|
|
732
748
|
* @param {function(T, T)} concat Concatenate the second stream onto the end
|
|
@@ -740,10 +756,15 @@ shaka.util.PeriodCombiner = class {
|
|
|
740
756
|
* @private
|
|
741
757
|
*/
|
|
742
758
|
static extendOutputStream_(
|
|
743
|
-
outputStream,
|
|
744
|
-
unusedStreamsPerPeriod) {
|
|
759
|
+
outputStream, firstNewPeriodIndex, concat, unusedStreamsPerPeriod) {
|
|
745
760
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
746
761
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
762
|
+
const matches = outputStream.matchedStreams;
|
|
763
|
+
|
|
764
|
+
// Assure the compiler that matches didn't become null during the async
|
|
765
|
+
// operation before.
|
|
766
|
+
goog.asserts.assert(outputStream.matchedStreams,
|
|
767
|
+
'matchedStreams should be non-null');
|
|
747
768
|
|
|
748
769
|
// Concatenate the new matches onto the stream, starting at the first new
|
|
749
770
|
// period.
|
|
@@ -787,7 +808,23 @@ shaka.util.PeriodCombiner = class {
|
|
|
787
808
|
// streams that match this output.
|
|
788
809
|
clone.originalId = null;
|
|
789
810
|
clone.createSegmentIndex = () => Promise.resolve();
|
|
790
|
-
clone.
|
|
811
|
+
clone.closeSegmentIndex = () => {
|
|
812
|
+
if (clone.segmentIndex) {
|
|
813
|
+
clone.segmentIndex.release();
|
|
814
|
+
clone.segmentIndex = null;
|
|
815
|
+
}
|
|
816
|
+
// Close the segment index of the matched streams.
|
|
817
|
+
if (clone.matchedStreams) {
|
|
818
|
+
for (const match of clone.matchedStreams) {
|
|
819
|
+
if (match.segmentIndex) {
|
|
820
|
+
match.segmentIndex.release();
|
|
821
|
+
match.segmentIndex = null;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
clone.segmentIndex = null;
|
|
791
828
|
clone.emsgSchemeIdUris = [];
|
|
792
829
|
clone.keyIds = new Set();
|
|
793
830
|
clone.closedCaptions = null;
|
|
@@ -873,26 +910,24 @@ shaka.util.PeriodCombiner = class {
|
|
|
873
910
|
}
|
|
874
911
|
}
|
|
875
912
|
|
|
876
|
-
// Satisfy the compiler about the type.
|
|
877
|
-
goog.asserts.assert(
|
|
878
|
-
output.segmentIndex instanceof shaka.media.MetaSegmentIndex,
|
|
879
|
-
'Output streams should have a MetaSegmentIndex!');
|
|
880
|
-
// Satisfy the compiler that the input index has been created.
|
|
881
|
-
goog.asserts.assert(
|
|
882
|
-
input.segmentIndex,
|
|
883
|
-
'Input segment index should have been created by now!');
|
|
884
|
-
|
|
885
|
-
output.segmentIndex.appendSegmentIndex(input.segmentIndex);
|
|
886
|
-
|
|
887
913
|
// Combine trick-play video streams, if present.
|
|
888
914
|
if (input.trickModeVideo) {
|
|
889
915
|
if (!output.trickModeVideo) {
|
|
890
916
|
// Create a fresh output stream for trick-mode playback.
|
|
891
917
|
output.trickModeVideo = shaka.util.PeriodCombiner.cloneStream_(
|
|
892
918
|
input.trickModeVideo);
|
|
893
|
-
//
|
|
894
|
-
//
|
|
895
|
-
|
|
919
|
+
// TODO: fix the createSegmentIndex function for trickModeVideo.
|
|
920
|
+
// The trick-mode tracks in multi-period content should have trick-mode
|
|
921
|
+
// segment indexes whenever available, rather than only regular-mode
|
|
922
|
+
// segment indexes.
|
|
923
|
+
output.trickModeVideo.createSegmentIndex = () => {
|
|
924
|
+
// Satisfy the compiler about the type.
|
|
925
|
+
goog.asserts.assert(
|
|
926
|
+
output.segmentIndex instanceof shaka.media.MetaSegmentIndex,
|
|
927
|
+
'The stream should have a MetaSegmentIndex.');
|
|
928
|
+
output.trickModeVideo.segmentIndex = output.segmentIndex.clone();
|
|
929
|
+
return Promise.resolve();
|
|
930
|
+
};
|
|
896
931
|
}
|
|
897
932
|
|
|
898
933
|
// Concatenate the trick mode input onto the trick mode output.
|
|
@@ -943,7 +978,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
943
978
|
*
|
|
944
979
|
* @param {!Array.<!Array.<T>>} streamsPerPeriod
|
|
945
980
|
* @param {T} outputStream
|
|
946
|
-
* @return {Array.<T>}
|
|
947
981
|
*
|
|
948
982
|
* @template T
|
|
949
983
|
* Accepts either a StreamDB or Stream type.
|
|
@@ -956,11 +990,11 @@ shaka.util.PeriodCombiner = class {
|
|
|
956
990
|
const match = shaka.util.PeriodCombiner.findBestMatchInPeriod_(
|
|
957
991
|
streams, outputStream);
|
|
958
992
|
if (!match) {
|
|
959
|
-
return
|
|
993
|
+
return;
|
|
960
994
|
}
|
|
961
995
|
matches.push(match);
|
|
962
996
|
}
|
|
963
|
-
|
|
997
|
+
outputStream.matchedStreams = matches;
|
|
964
998
|
}
|
|
965
999
|
|
|
966
1000
|
/**
|
|
@@ -120,6 +120,8 @@ shaka.util.PlayerConfiguration = class {
|
|
|
120
120
|
ignoreTextStreamFailures: false,
|
|
121
121
|
ignoreImageStreamFailures: false,
|
|
122
122
|
useFullSegmentsForStartTime: false,
|
|
123
|
+
defaultAudioCodec: 'mp4a.40.2',
|
|
124
|
+
defaultVideoCodec: 'avc1.42E01E',
|
|
123
125
|
},
|
|
124
126
|
};
|
|
125
127
|
|
|
@@ -163,6 +165,8 @@ shaka.util.PlayerConfiguration = class {
|
|
|
163
165
|
forceHTTPS: false,
|
|
164
166
|
preferNativeHls: false,
|
|
165
167
|
updateIntervalSeconds: 1,
|
|
168
|
+
dispatchAllEmsgBoxes: false,
|
|
169
|
+
observeQualityChanges: false,
|
|
166
170
|
};
|
|
167
171
|
|
|
168
172
|
// Some browsers will stop earlier than others before a gap (e.g., Edge
|
|
@@ -235,6 +239,19 @@ shaka.util.PlayerConfiguration = class {
|
|
|
235
239
|
minBandwidth: 0,
|
|
236
240
|
maxBandwidth: Infinity,
|
|
237
241
|
},
|
|
242
|
+
advanced: {
|
|
243
|
+
minTotalBytes: 128e3,
|
|
244
|
+
minBytes: 16e3,
|
|
245
|
+
fastHalfLife: 2,
|
|
246
|
+
slowHalfLife: 5,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const cmcd = {
|
|
251
|
+
enabled: false,
|
|
252
|
+
sessionId: '',
|
|
253
|
+
contentId: '',
|
|
254
|
+
useHeaders: false,
|
|
238
255
|
};
|
|
239
256
|
|
|
240
257
|
/** @type {shaka.extern.PlayerConfiguration} */
|
|
@@ -269,6 +286,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
269
286
|
playRangeStart: 0,
|
|
270
287
|
playRangeEnd: Infinity,
|
|
271
288
|
textDisplayFactory: () => null,
|
|
289
|
+
cmcd: cmcd,
|
|
272
290
|
};
|
|
273
291
|
|
|
274
292
|
// Add this callback so that we can reference the preferred audio language
|
package/package.json
CHANGED
|
@@ -44,6 +44,7 @@ goog.require('shaka.polyfill.PatchedMediaKeysMs');
|
|
|
44
44
|
goog.require('shaka.polyfill.PatchedMediaKeysNop');
|
|
45
45
|
goog.require('shaka.polyfill.PatchedMediaKeysWebkit');
|
|
46
46
|
goog.require('shaka.polyfill.PiPWebkit');
|
|
47
|
+
goog.require('shaka.polyfill.RandomUUID');
|
|
47
48
|
goog.require('shaka.polyfill.VTTCue');
|
|
48
49
|
goog.require('shaka.polyfill.VideoPlayPromise');
|
|
49
50
|
goog.require('shaka.polyfill.VideoPlaybackQuality');
|
|
@@ -70,6 +71,7 @@ goog.require('shaka.ui.AdPosition');
|
|
|
70
71
|
goog.require('shaka.ui.AirPlayButton');
|
|
71
72
|
goog.require('shaka.ui.BigPlayButton');
|
|
72
73
|
goog.require('shaka.ui.CastButton');
|
|
74
|
+
goog.require('shaka.ui.ContextMenu');
|
|
73
75
|
goog.require('shaka.ui.Element');
|
|
74
76
|
goog.require('shaka.ui.FastForwardButton');
|
|
75
77
|
goog.require('shaka.ui.FullscreenButton');
|
|
@@ -85,6 +87,7 @@ goog.require('shaka.ui.RewindButton');
|
|
|
85
87
|
goog.require('shaka.ui.SkipAdButton');
|
|
86
88
|
goog.require('shaka.ui.SmallPlayButton');
|
|
87
89
|
goog.require('shaka.ui.Spacer');
|
|
90
|
+
goog.require('shaka.ui.StatisticsButton');
|
|
88
91
|
goog.require('shaka.ui.TextSelection');
|
|
89
92
|
goog.require('shaka.ui.VolumeBar');
|
|
90
93
|
goog.require('shaka.util.Dom');
|
|
@@ -42,6 +42,8 @@ describe('DashParser ContentProtection', () => {
|
|
|
42
42
|
|
|
43
43
|
const playerInterface = {
|
|
44
44
|
networkingEngine: netEngine,
|
|
45
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
46
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
45
47
|
filter: (manifest) => Promise.resolve(),
|
|
46
48
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
47
49
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -39,6 +39,8 @@ describe('DashParser Live', () => {
|
|
|
39
39
|
parser.configure(shaka.util.PlayerConfiguration.createDefault().manifest);
|
|
40
40
|
playerInterface = {
|
|
41
41
|
networkingEngine: fakeNetEngine,
|
|
42
|
+
modifyManifestRequest: (request, manifestInfo) => {},
|
|
43
|
+
modifySegmentRequest: (request, segmentInfo) => {},
|
|
42
44
|
filter: (manifest) => Promise.resolve(),
|
|
43
45
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
44
46
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
@@ -364,7 +366,7 @@ describe('DashParser Live', () => {
|
|
|
364
366
|
});
|
|
365
367
|
}
|
|
366
368
|
|
|
367
|
-
it('can add Periods', async () => {
|
|
369
|
+
it('can add Periods with SegmentTemplate', async () => {
|
|
368
370
|
const template1 = [
|
|
369
371
|
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
370
372
|
' suggestedPresentationDelay="PT5S"',
|
|
@@ -440,6 +442,104 @@ describe('DashParser Live', () => {
|
|
|
440
442
|
// two segments (10-14s).
|
|
441
443
|
expect(stream.segmentIndex.find(9)).not.toBe(null);
|
|
442
444
|
expect(stream.segmentIndex.find(13)).not.toBe(null);
|
|
445
|
+
|
|
446
|
+
stream.closeSegmentIndex();
|
|
447
|
+
await stream.createSegmentIndex();
|
|
448
|
+
|
|
449
|
+
expect(stream.segmentIndex.find(9)).not.toBe(null);
|
|
450
|
+
expect(stream.segmentIndex.find(13)).not.toBe(null);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('can add Periods with SegmentList', async () => {
|
|
454
|
+
const list1 = [
|
|
455
|
+
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
456
|
+
' suggestedPresentationDelay="PT5S"',
|
|
457
|
+
' minimumUpdatePeriod="PT%(updateTime)dS">',
|
|
458
|
+
' <Period id="1">',
|
|
459
|
+
' <AdaptationSet mimeType="video/mp4">',
|
|
460
|
+
' <Representation id="1" bandwidth="500">',
|
|
461
|
+
' <BaseURL>http://example.com</BaseURL>',
|
|
462
|
+
' <SegmentList>',
|
|
463
|
+
' <SegmentURL media="s1.mp4" />',
|
|
464
|
+
' <SegmentURL media="s2.mp4" />',
|
|
465
|
+
' <SegmentURL media="s3.mp4" />',
|
|
466
|
+
' <SegmentTimeline>',
|
|
467
|
+
' <S d="10" t="0" r="2"/>',
|
|
468
|
+
' </SegmentTimeline>',
|
|
469
|
+
' </SegmentList>',
|
|
470
|
+
' </Representation>',
|
|
471
|
+
' </AdaptationSet>',
|
|
472
|
+
' </Period>',
|
|
473
|
+
'</MPD>',
|
|
474
|
+
].join('\n');
|
|
475
|
+
const list2 = [
|
|
476
|
+
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
477
|
+
' suggestedPresentationDelay="PT5S"',
|
|
478
|
+
' minimumUpdatePeriod="PT%(updateTime)dS">',
|
|
479
|
+
' <Period id="1" duration="PT40S">',
|
|
480
|
+
' <AdaptationSet mimeType="video/mp4">',
|
|
481
|
+
' <Representation id="1" bandwidth="500">',
|
|
482
|
+
' <BaseURL>http://example.com</BaseURL>',
|
|
483
|
+
' <SegmentList>',
|
|
484
|
+
' <SegmentURL media="s1.mp4" />',
|
|
485
|
+
' <SegmentURL media="s2.mp4" />',
|
|
486
|
+
' <SegmentURL media="s3.mp4" />',
|
|
487
|
+
' <SegmentURL media="s4.mp4" />',
|
|
488
|
+
' <SegmentTimeline>',
|
|
489
|
+
' <S d="10" t="0" r="3"/>',
|
|
490
|
+
' </SegmentTimeline>',
|
|
491
|
+
' </SegmentList>',
|
|
492
|
+
' </Representation>',
|
|
493
|
+
' </AdaptationSet>',
|
|
494
|
+
' </Period>',
|
|
495
|
+
' <Period id="2">',
|
|
496
|
+
' <AdaptationSet mimeType="video/mp4">',
|
|
497
|
+
' <Representation id="2" bandwidth="500">',
|
|
498
|
+
' <BaseURL>http://example.com</BaseURL>',
|
|
499
|
+
' <SegmentList>',
|
|
500
|
+
' <SegmentURL media="s1.mp4" />',
|
|
501
|
+
' <SegmentURL media="s2.mp4" />',
|
|
502
|
+
' <SegmentTimeline>',
|
|
503
|
+
' <S d="10" t="0" r="1"/>',
|
|
504
|
+
' </SegmentTimeline>',
|
|
505
|
+
' </SegmentList>',
|
|
506
|
+
' </Representation>',
|
|
507
|
+
' </AdaptationSet>',
|
|
508
|
+
' </Period>',
|
|
509
|
+
'</MPD>',
|
|
510
|
+
].join('\n');
|
|
511
|
+
const firstManifest = sprintf(list1, {updateTime: updateTime});
|
|
512
|
+
const secondManifest = sprintf(list2, {updateTime: updateTime});
|
|
513
|
+
|
|
514
|
+
fakeNetEngine.setResponseText('dummy://foo', firstManifest);
|
|
515
|
+
// First three segments should exist.
|
|
516
|
+
Date.now = () => 5;
|
|
517
|
+
|
|
518
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
519
|
+
const variant = manifest.variants[0];
|
|
520
|
+
const stream = variant.video;
|
|
521
|
+
await stream.createSegmentIndex();
|
|
522
|
+
|
|
523
|
+
// First three segments exist, but not the fourth.
|
|
524
|
+
expect(stream.segmentIndex.find(25)).not.toBe(null);
|
|
525
|
+
expect(stream.segmentIndex.find(45)).toBe(null);
|
|
526
|
+
|
|
527
|
+
fakeNetEngine.setResponseText('dummy://foo', secondManifest);
|
|
528
|
+
Date.now = () => 25;
|
|
529
|
+
|
|
530
|
+
await updateManifest();
|
|
531
|
+
|
|
532
|
+
// The update should have affected the same variant object we captured
|
|
533
|
+
// before. Now the entire first period should exist (0-40s), plus the next
|
|
534
|
+
// two segments of the second period(40-60s).
|
|
535
|
+
expect(stream.segmentIndex.find(25)).not.toBe(null);
|
|
536
|
+
expect(stream.segmentIndex.find(45)).not.toBe(null);
|
|
537
|
+
|
|
538
|
+
stream.closeSegmentIndex();
|
|
539
|
+
await stream.createSegmentIndex();
|
|
540
|
+
|
|
541
|
+
expect(stream.segmentIndex.find(25)).not.toBe(null);
|
|
542
|
+
expect(stream.segmentIndex.find(45)).not.toBe(null);
|
|
443
543
|
});
|
|
444
544
|
|
|
445
545
|
it('uses redirect URL for manifest BaseURL and updates', async () => {
|