shaka-player 3.1.0 → 3.1.4
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/AUTHORS +8 -2
- package/CHANGELOG.md +209 -0
- package/CONTRIBUTORS +10 -3
- package/README.md +3 -0
- package/build/apps.py +2 -2
- package/build/check.py +2 -2
- package/build/conformance.textproto +0 -9
- package/build/generateExterns.js +1 -1
- package/build/misspellings.txt +1 -0
- package/build/shakaBuildHelpers.py +6 -5
- package/build/subprocessWindowsPatch.py +103 -0
- package/demo/common/asset.js +11 -0
- package/demo/common/assets.js +1 -1
- package/demo/common/message_ids.js +5 -0
- package/demo/config.js +2 -1
- package/demo/custom.js +84 -3
- package/demo/demo.less +13 -1
- package/demo/demo_utils.js +1 -0
- package/demo/input.js +31 -12
- package/demo/input_container.js +24 -1
- package/demo/locales/en.json +6 -1
- package/demo/locales/source.json +20 -0
- package/demo/main.js +5 -1
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +99 -91
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +99 -91
- package/dist/demo.compiled.map +3 -3
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +9 -9
- package/dist/locales.js +36 -34
- package/dist/receiver.compiled.debug.js +74 -70
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +74 -70
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +165 -9
- package/dist/shaka-player.compiled.debug.d.ts +165 -9
- package/dist/shaka-player.compiled.debug.externs.js +179 -11
- package/dist/shaka-player.compiled.debug.js +367 -339
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +179 -11
- package/dist/shaka-player.compiled.js +818 -807
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +165 -9
- package/dist/shaka-player.ui.debug.d.ts +165 -9
- package/dist/shaka-player.ui.debug.externs.js +181 -13
- package/dist/shaka-player.ui.debug.js +459 -429
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +181 -13
- package/dist/shaka-player.ui.js +973 -961
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/docs/tutorials/faq.md +25 -0
- package/docs/tutorials/index.json +2 -2
- package/docs/tutorials/upgrade-v3.md +38 -0
- package/docs/tutorials/upgrade.md +3 -3
- package/externs/ima.js +4 -0
- package/externs/media_capabilities.js +16 -0
- package/externs/shaka/ads.js +2 -0
- package/externs/shaka/net.js +2 -2
- package/externs/shaka/player.js +6 -0
- package/karma.conf.js +1 -0
- package/lib/abr/simple_abr_manager.js +1 -1
- package/lib/ads/ad_manager.js +29 -11
- package/lib/ads/client_side_ad_manager.js +76 -86
- package/lib/ads/server_side_ad_manager.js +26 -11
- package/lib/cast/cast_proxy.js +3 -2
- package/lib/cast/cast_receiver.js +2 -1
- package/lib/cast/cast_sender.js +2 -2
- package/lib/cea/sei_processor.js +24 -17
- package/lib/dash/dash_parser.js +107 -4
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +45 -15
- package/lib/dependencies/all.js +15 -15
- package/lib/hls/hls_parser.js +46 -18
- package/lib/media/closed_caption_parser.js +3 -1
- package/lib/media/content_workarounds.js +18 -0
- package/lib/media/drm_engine.js +20 -1
- package/lib/media/gap_jumping_controller.js +8 -4
- package/lib/media/media_source_engine.js +1 -2
- package/lib/media/playhead.js +1 -1
- package/lib/media/presentation_timeline.js +3 -1
- package/lib/media/segment_index.js +38 -12
- package/lib/media/segment_reference.js +8 -0
- package/lib/media/streaming_engine.js +30 -21
- package/lib/net/http_fetch_plugin.js +2 -2
- package/lib/net/networking_engine.js +3 -2
- package/lib/offline/storage.js +4 -0
- package/lib/player.js +113 -56
- package/lib/polyfill/encryption_scheme.js +2 -0
- package/lib/polyfill/fullscreen.js +2 -0
- package/lib/polyfill/mathround.js +2 -0
- package/lib/polyfill/media_capabilities.js +62 -23
- package/lib/polyfill/mediasource.js +8 -0
- package/lib/polyfill/orientation.js +3 -1
- package/lib/polyfill/patchedmediakeys_apple.js +6 -4
- package/lib/polyfill/patchedmediakeys_ms.js +6 -4
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
- package/lib/polyfill/pip_webkit.js +2 -0
- package/lib/polyfill/storage_estimate.js +2 -0
- package/lib/polyfill/video_play_promise.js +2 -0
- package/lib/polyfill/videoplaybackquality.js +2 -0
- package/lib/polyfill/vttcue.js +2 -0
- package/lib/text/cue.js +11 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/text_engine.js +14 -2
- package/lib/text/ttml_text_parser.js +52 -30
- package/lib/text/ui_text_displayer.js +255 -82
- package/lib/text/vtt_text_parser.js +4 -2
- package/lib/util/error.js +1 -4
- package/lib/util/fake_event.js +53 -7
- package/lib/util/mime_utils.js +33 -0
- package/lib/util/mp4_box_parsers.js +1 -1
- package/lib/util/mp4_parser.js +14 -0
- package/lib/util/networking.js +1 -1
- package/lib/util/periods.js +132 -37
- package/lib/util/platform.js +13 -1
- package/lib/util/stream_utils.js +46 -7
- package/package.json +3 -3
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_receiver_integration.js +1 -1
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/cea/mp4_cea_parser_unit.js +26 -0
- package/test/dash/dash_parser_live_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +187 -1
- package/test/dash/dash_parser_segment_template_unit.js +3 -3
- package/test/demo/demo_unit.js +0 -1
- package/test/hls/hls_parser_unit.js +45 -1
- package/test/media/closed_caption_parser_unit.js +20 -0
- package/test/media/drm_engine_unit.js +59 -3
- package/test/media/playhead_unit.js +27 -1
- package/test/media/presentation_timeline_unit.js +19 -0
- package/test/media/segment_index_unit.js +76 -3
- package/test/media/streaming_engine_unit.js +3 -3
- package/test/player_integration.js +38 -0
- package/test/player_unit.js +125 -6
- package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
- package/test/test/assets/3675/dash_0.mpd +30 -0
- package/test/test/assets/3675/dash_1.mpd +30 -0
- package/test/test/assets/3675/dash_2.mpd +30 -0
- package/test/test/assets/3675/dash_3.mpd +30 -0
- package/test/test/assets/3675/dash_4.mpd +30 -0
- package/test/test/assets/3675/dash_5.mpd +26 -0
- package/test/test/assets/3675/init-stream0.m4s +0 -0
- package/test/test/assets/empty_caption_video_init.mp4 +0 -0
- package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
- package/test/test/util/fake_ad_manager.js +4 -1
- package/test/test/util/simple_fakes.js +5 -3
- package/test/test/util/waiter.js +36 -2
- package/test/text/ttml_text_parser_unit.js +55 -0
- package/test/text/ui_text_displayer_unit.js +95 -18
- package/test/text/vtt_text_parser_unit.js +17 -1
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/text_displayer_layout_unit.js +52 -1
- package/test/ui/ui_unit.js +4 -0
- package/test/util/mp4_box_parsers_unit.js +28 -0
- package/test/util/periods_unit.js +190 -2
- package/test/util/stream_utils_unit.js +56 -5
- package/ui/airplay_button.js +5 -0
- package/ui/big_play_button.js +10 -0
- package/ui/cast_button.js +2 -3
- package/ui/controls.js +9 -7
- package/ui/less/containers.less +10 -5
- package/ui/locales/en.json +1 -0
- package/ui/locales/ru.json +6 -6
- package/ui/locales/source.json +4 -0
- package/ui/localization.js +28 -51
- package/ui/pip_button.js +2 -3
- package/ui/play_button.js +10 -10
- package/ui/small_play_button.js +12 -0
- package/ui/ui.js +1 -1
- package/docs/tutorials/upgrade-v2-4.md +0 -481
|
@@ -14,10 +14,12 @@ goog.provide('shaka.ads.ServerSideAdManager');
|
|
|
14
14
|
goog.require('goog.asserts');
|
|
15
15
|
goog.require('shaka.ads.ServerSideAd');
|
|
16
16
|
goog.require('shaka.log');
|
|
17
|
+
goog.require('shaka.util.IReleasable');
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* A class responsible for server-side ad interactions.
|
|
22
|
+
* @implements {shaka.util.IReleasable}
|
|
21
23
|
*/
|
|
22
24
|
shaka.ads.ServerSideAdManager = class {
|
|
23
25
|
/**
|
|
@@ -75,9 +77,7 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
75
77
|
|
|
76
78
|
this.onEvent_(new shaka.util.FakeEvent(
|
|
77
79
|
shaka.ads.AdManager.IMA_STREAM_MANAGER_LOADED,
|
|
78
|
-
|
|
79
|
-
'imaStreamManager': this.streamManager_,
|
|
80
|
-
}));
|
|
80
|
+
(new Map()).set('imaStreamManager', this.streamManager_)));
|
|
81
81
|
|
|
82
82
|
// Events
|
|
83
83
|
this.eventManager_.listen(this.streamManager_,
|
|
@@ -201,11 +201,27 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
201
201
|
* Resets the stream manager and removes any continuous polling.
|
|
202
202
|
*/
|
|
203
203
|
stop() {
|
|
204
|
-
|
|
204
|
+
// TODO:
|
|
205
|
+
// For SS DAI streams, if a different asset gets unloaded as
|
|
206
|
+
// part of the process
|
|
207
|
+
// of loading a DAI asset, stream manager state gets reset and we
|
|
208
|
+
// don't get any ad events.
|
|
209
|
+
// We need to figure out if it makes sense to stop the SS
|
|
210
|
+
// manager on unload, and, if it does, find
|
|
211
|
+
// a way to do it safely.
|
|
212
|
+
// this.streamManager_.reset();
|
|
205
213
|
this.backupUrl_ = '';
|
|
206
214
|
this.snapForwardTime_ = null;
|
|
207
215
|
}
|
|
208
216
|
|
|
217
|
+
/** @override */
|
|
218
|
+
release() {
|
|
219
|
+
this.stop();
|
|
220
|
+
if (this.eventManager_) {
|
|
221
|
+
this.eventManager_.release();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
209
225
|
/**
|
|
210
226
|
* @param {string} type
|
|
211
227
|
* @param {Uint8Array|string} data
|
|
@@ -280,7 +296,7 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
280
296
|
}
|
|
281
297
|
|
|
282
298
|
this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STARTED,
|
|
283
|
-
|
|
299
|
+
(new Map()).set('ad', this.ad_)));
|
|
284
300
|
this.adContainer_.setAttribute('ad-active', 'true');
|
|
285
301
|
}
|
|
286
302
|
|
|
@@ -306,9 +322,8 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
306
322
|
onLoaded_(e) {
|
|
307
323
|
const now = Date.now() / 1000;
|
|
308
324
|
const loadTime = now - this.streamRequestStartTime_;
|
|
309
|
-
this.onEvent_(
|
|
310
|
-
new
|
|
311
|
-
{'loadTime': loadTime}));
|
|
325
|
+
this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.ADS_LOADED,
|
|
326
|
+
(new Map()).set('loadTime', loadTime)));
|
|
312
327
|
|
|
313
328
|
const streamData = e.getStreamData();
|
|
314
329
|
const url = streamData.url;
|
|
@@ -368,8 +383,8 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
368
383
|
cuePoints.push(shakaCuePoint);
|
|
369
384
|
}
|
|
370
385
|
|
|
371
|
-
this.onEvent_(
|
|
372
|
-
|
|
373
|
-
|
|
386
|
+
this.onEvent_(new shaka.util.FakeEvent(
|
|
387
|
+
shaka.ads.AdManager.CUEPOINTS_CHANGED,
|
|
388
|
+
(new Map()).set('cuepoints', cuePoints)));
|
|
374
389
|
}
|
|
375
390
|
};
|
package/lib/cast/cast_proxy.js
CHANGED
|
@@ -525,7 +525,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
|
|
|
525
525
|
goog.asserts.assert(error instanceof shaka.util.Error,
|
|
526
526
|
'Wrong error type!');
|
|
527
527
|
const eventType = shaka.Player.EventName.Error;
|
|
528
|
-
const
|
|
528
|
+
const data = (new Map()).set('detail', error);
|
|
529
|
+
const event = new shaka.util.FakeEvent(eventType, data);
|
|
529
530
|
this.localPlayer_.dispatchEvent(event);
|
|
530
531
|
});
|
|
531
532
|
}
|
|
@@ -598,7 +599,7 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
|
|
|
598
599
|
|
|
599
600
|
// Convert this real Event into a FakeEvent for dispatch from our
|
|
600
601
|
// FakeEventListener.
|
|
601
|
-
const fakeEvent =
|
|
602
|
+
const fakeEvent = shaka.util.FakeEvent.fromRealEvent(event);
|
|
602
603
|
this.videoEventTarget_.dispatchEvent(fakeEvent);
|
|
603
604
|
}
|
|
604
605
|
|
|
@@ -407,7 +407,8 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
|
|
|
407
407
|
goog.asserts.assert(error instanceof shaka.util.Error,
|
|
408
408
|
'Wrong error type!');
|
|
409
409
|
const eventType = shaka.Player.EventName.Error;
|
|
410
|
-
const
|
|
410
|
+
const data = (new Map()).set('detail', error);
|
|
411
|
+
const event = new shaka.util.FakeEvent(eventType, data);
|
|
411
412
|
// Only dispatch the event if the player still exists.
|
|
412
413
|
if (this.player_) {
|
|
413
414
|
this.player_.dispatchEvent(event);
|
package/lib/cast/cast_sender.js
CHANGED
|
@@ -647,7 +647,7 @@ shaka.cast.CastSender = class {
|
|
|
647
647
|
case 'event': {
|
|
648
648
|
const targetName = message['targetName'];
|
|
649
649
|
const event = message['event'];
|
|
650
|
-
const fakeEvent =
|
|
650
|
+
const fakeEvent = shaka.util.FakeEvent.fromRealEvent(event);
|
|
651
651
|
this.onRemoteEvent_(targetName, fakeEvent);
|
|
652
652
|
break;
|
|
653
653
|
}
|
|
@@ -725,7 +725,7 @@ shaka.cast.CastSender = class {
|
|
|
725
725
|
|
|
726
726
|
// Dispatch it through the Player proxy
|
|
727
727
|
const fakeEvent = new shaka.util.FakeEvent(
|
|
728
|
-
'error',
|
|
728
|
+
'error', (new Map()).set('detail', shakaError));
|
|
729
729
|
this.onRemoteEvent_('player', fakeEvent);
|
|
730
730
|
|
|
731
731
|
// Force this session to disconnect and transfer playback to the local
|
package/lib/cea/sei_processor.js
CHANGED
|
@@ -17,31 +17,31 @@ shaka.cea.SeiProcessor = class {
|
|
|
17
17
|
* @return {!Iterable.<!Uint8Array>}
|
|
18
18
|
*/
|
|
19
19
|
* process(naluData) {
|
|
20
|
-
const
|
|
20
|
+
const naluClone = this.removeEmu(naluData);
|
|
21
21
|
|
|
22
22
|
// The following is an implementation of section 7.3.2.3.1
|
|
23
23
|
// in Rec. ITU-T H.264 (06/2019), the H.264 spec.
|
|
24
24
|
let offset = 0;
|
|
25
25
|
|
|
26
|
-
while (offset
|
|
26
|
+
while (offset < naluClone.length) {
|
|
27
27
|
let payloadType = 0; // SEI payload type as defined by H.264 spec
|
|
28
|
-
while (
|
|
28
|
+
while (naluClone[offset] == 0xFF) {
|
|
29
29
|
payloadType += 255;
|
|
30
30
|
offset++;
|
|
31
31
|
}
|
|
32
|
-
payloadType +=
|
|
32
|
+
payloadType += naluClone[offset++];
|
|
33
33
|
|
|
34
34
|
let payloadSize = 0; // SEI payload size as defined by H.264 spec
|
|
35
|
-
while (
|
|
35
|
+
while (naluClone[offset] == 0xFF) {
|
|
36
36
|
payloadSize += 255;
|
|
37
37
|
offset++;
|
|
38
38
|
}
|
|
39
|
-
payloadSize +=
|
|
39
|
+
payloadSize += naluClone[offset++];
|
|
40
40
|
|
|
41
41
|
// Payload type 4 is user_data_registered_itu_t_t35, as per the H.264
|
|
42
42
|
// spec. This payload type contains caption data.
|
|
43
43
|
if (payloadType == 0x04) {
|
|
44
|
-
yield
|
|
44
|
+
yield naluClone.subarray(offset, offset + payloadSize);
|
|
45
45
|
}
|
|
46
46
|
offset += payloadSize;
|
|
47
47
|
}
|
|
@@ -50,28 +50,35 @@ shaka.cea.SeiProcessor = class {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Removes H.264 emulation prevention bytes from the byte array.
|
|
53
|
+
*
|
|
54
|
+
* Note: Remove bytes by shifting will cause Chromium (VDA) to complain
|
|
55
|
+
* about conformance. Recreating a new array solves it.
|
|
56
|
+
*
|
|
53
57
|
* @param {!Uint8Array} naluData NALU from which EMUs should be removed.
|
|
54
|
-
* @return {
|
|
55
|
-
* @private
|
|
58
|
+
* @return {!Uint8Array} The NALU with the emulation prevention byte removed.
|
|
56
59
|
*/
|
|
57
|
-
|
|
60
|
+
removeEmu(naluData) {
|
|
61
|
+
let naluClone = naluData;
|
|
58
62
|
let zeroCount = 0;
|
|
59
63
|
let src = 0;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
while (src < naluClone.length) {
|
|
65
|
+
if (zeroCount == 2 && naluClone[src] == 0x03) {
|
|
66
|
+
// 0x00, 0x00, 0x03 pattern detected
|
|
63
67
|
zeroCount = 0;
|
|
68
|
+
|
|
69
|
+
// Splice the array and recreate a new one, instead of shifting bytes
|
|
70
|
+
const newArr = [...naluClone];
|
|
71
|
+
newArr.splice(src, 1);
|
|
72
|
+
naluClone = new Uint8Array(newArr);
|
|
64
73
|
} else {
|
|
65
|
-
if (
|
|
74
|
+
if (naluClone[src] == 0x00) {
|
|
66
75
|
zeroCount++;
|
|
67
76
|
} else {
|
|
68
77
|
zeroCount = 0;
|
|
69
78
|
}
|
|
70
|
-
naluData[dst] = naluData[src];
|
|
71
|
-
dst++;
|
|
72
79
|
}
|
|
73
80
|
src++;
|
|
74
81
|
}
|
|
75
|
-
return
|
|
82
|
+
return naluClone;
|
|
76
83
|
}
|
|
77
84
|
};
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -65,6 +65,12 @@ shaka.dash.DashParser = class {
|
|
|
65
65
|
*/
|
|
66
66
|
this.segmentIndexMap_ = {};
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* A map of period ids to their durations
|
|
70
|
+
* @private {!Object.<string, number>}
|
|
71
|
+
*/
|
|
72
|
+
this.periodDurations_ = {};
|
|
73
|
+
|
|
68
74
|
/** @private {shaka.util.PeriodCombiner} */
|
|
69
75
|
this.periodCombiner_ = new shaka.util.PeriodCombiner();
|
|
70
76
|
|
|
@@ -89,6 +95,18 @@ shaka.dash.DashParser = class {
|
|
|
89
95
|
/** @private {!shaka.util.OperationManager} */
|
|
90
96
|
this.operationManager_ = new shaka.util.OperationManager();
|
|
91
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Largest period start time seen.
|
|
100
|
+
* @private {?number}
|
|
101
|
+
*/
|
|
102
|
+
this.largestPeriodStartTime_ = null;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Period IDs seen in previous manifest.
|
|
106
|
+
* @private {!Array.<number>}
|
|
107
|
+
*/
|
|
108
|
+
this.lastManifestUpdatePeriodIds_ = [];
|
|
109
|
+
|
|
92
110
|
/**
|
|
93
111
|
* The minimum of the availabilityTimeOffset values among the adaptation
|
|
94
112
|
* sets.
|
|
@@ -527,6 +545,7 @@ shaka.dash.DashParser = class {
|
|
|
527
545
|
for (const {i, item: elem, next} of enumerate(periodNodes)) {
|
|
528
546
|
const start = /** @type {number} */ (
|
|
529
547
|
XmlUtils.parseAttr(elem, 'start', XmlUtils.parseDuration, prevEnd));
|
|
548
|
+
const periodId = elem.id;
|
|
530
549
|
const givenDuration =
|
|
531
550
|
XmlUtils.parseAttr(elem, 'duration', XmlUtils.parseDuration);
|
|
532
551
|
|
|
@@ -560,6 +579,42 @@ shaka.dash.DashParser = class {
|
|
|
560
579
|
periodDuration = givenDuration;
|
|
561
580
|
}
|
|
562
581
|
|
|
582
|
+
/**
|
|
583
|
+
* This is to improve robustness when the player observes manifest with
|
|
584
|
+
* past periods that are inconsistent to previous ones.
|
|
585
|
+
*
|
|
586
|
+
* This may happen when a CDN or proxy server switches its upstream from
|
|
587
|
+
* one encoder to another redundant encoder.
|
|
588
|
+
*
|
|
589
|
+
* Skip periods that match all of the following criteria:
|
|
590
|
+
* - Start time is earlier than latest period start time ever seen
|
|
591
|
+
* - Period ID is never seen in the previous manifest
|
|
592
|
+
* - Not the last period in the manifest
|
|
593
|
+
*
|
|
594
|
+
* Periods that meet the aforementioned criteria are considered invalid
|
|
595
|
+
* and should be safe to discard.
|
|
596
|
+
*/
|
|
597
|
+
|
|
598
|
+
if (this.largestPeriodStartTime_ !== null &&
|
|
599
|
+
periodId !== null && start !== null &&
|
|
600
|
+
start < this.largestPeriodStartTime_ &&
|
|
601
|
+
!this.lastManifestUpdatePeriodIds_.includes(periodId) &&
|
|
602
|
+
i + 1 != periodNodes.length) {
|
|
603
|
+
shaka.log.debug(
|
|
604
|
+
`Skipping Period with ID ${periodId} as its start time is smaller` +
|
|
605
|
+
' than the largest period start time that has been seen, and ID ' +
|
|
606
|
+
'is unseen before');
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
// Save maximum period start time if it is the last period
|
|
612
|
+
if (start !== null &&
|
|
613
|
+
(this.largestPeriodStartTime_ === null ||
|
|
614
|
+
start > this.largestPeriodStartTime_)) {
|
|
615
|
+
this.largestPeriodStartTime_ = start;
|
|
616
|
+
}
|
|
617
|
+
|
|
563
618
|
// Parse child nodes.
|
|
564
619
|
const info = {
|
|
565
620
|
start: start,
|
|
@@ -570,6 +625,10 @@ shaka.dash.DashParser = class {
|
|
|
570
625
|
const period = this.parsePeriod_(context, baseUris, info);
|
|
571
626
|
periods.push(period);
|
|
572
627
|
|
|
628
|
+
if (context.period.id && periodDuration) {
|
|
629
|
+
this.periodDurations_[context.period.id] = periodDuration;
|
|
630
|
+
}
|
|
631
|
+
|
|
573
632
|
if (periodDuration == null) {
|
|
574
633
|
if (next) {
|
|
575
634
|
// If the duration is still null and we aren't at the end, then we
|
|
@@ -587,6 +646,9 @@ shaka.dash.DashParser = class {
|
|
|
587
646
|
prevEnd = start + periodDuration;
|
|
588
647
|
} // end of period parsing loop
|
|
589
648
|
|
|
649
|
+
// Replace previous seen periods with the current one.
|
|
650
|
+
this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
|
|
651
|
+
|
|
590
652
|
if (presentationDuration != null) {
|
|
591
653
|
if (prevEnd != presentationDuration) {
|
|
592
654
|
shaka.log.warning(
|
|
@@ -798,6 +860,25 @@ shaka.dash.DashParser = class {
|
|
|
798
860
|
}
|
|
799
861
|
}
|
|
800
862
|
|
|
863
|
+
// Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
|
|
864
|
+
let videoRange;
|
|
865
|
+
const videoRangeScheme = 'urn:mpeg:mpegB:cicp:TransferCharacteristics';
|
|
866
|
+
const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
|
|
867
|
+
switch (cicp) {
|
|
868
|
+
case 1:
|
|
869
|
+
case 6:
|
|
870
|
+
case 13:
|
|
871
|
+
case 14:
|
|
872
|
+
case 15:
|
|
873
|
+
return 'SDR';
|
|
874
|
+
case 16:
|
|
875
|
+
return 'PQ';
|
|
876
|
+
case 18:
|
|
877
|
+
return 'HLG';
|
|
878
|
+
}
|
|
879
|
+
return undefined;
|
|
880
|
+
};
|
|
881
|
+
|
|
801
882
|
const essentialProperties =
|
|
802
883
|
XmlUtils.findChildren(elem, 'EssentialProperty');
|
|
803
884
|
// ID of real AdaptationSet if this is a trick mode set:
|
|
@@ -807,11 +888,26 @@ shaka.dash.DashParser = class {
|
|
|
807
888
|
const schemeId = prop.getAttribute('schemeIdUri');
|
|
808
889
|
if (schemeId == 'http://dashif.org/guidelines/trickmode') {
|
|
809
890
|
trickModeFor = prop.getAttribute('value');
|
|
891
|
+
} else if (schemeId == videoRangeScheme) {
|
|
892
|
+
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
893
|
+
parseInt(prop.getAttribute('value'), 10),
|
|
894
|
+
);
|
|
810
895
|
} else {
|
|
811
896
|
unrecognizedEssentialProperty = true;
|
|
812
897
|
}
|
|
813
898
|
}
|
|
814
899
|
|
|
900
|
+
const supplementalProperties =
|
|
901
|
+
XmlUtils.findChildren(elem, 'SupplementalProperty');
|
|
902
|
+
for (const prop of supplementalProperties) {
|
|
903
|
+
const schemeId = prop.getAttribute('schemeIdUri');
|
|
904
|
+
if (schemeId == videoRangeScheme) {
|
|
905
|
+
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
906
|
+
parseInt(prop.getAttribute('value'), 10),
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
815
911
|
const accessibilities = XmlUtils.findChildren(elem, 'Accessibility');
|
|
816
912
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
817
913
|
const closedCaptions = new Map();
|
|
@@ -848,7 +944,9 @@ shaka.dash.DashParser = class {
|
|
|
848
944
|
channel = channelAndLanguage[0].startsWith('CC') ?
|
|
849
945
|
channelAndLanguage[0] : `CC${channelAndLanguage[0]}`;
|
|
850
946
|
|
|
851
|
-
|
|
947
|
+
// 3 letters (ISO 639-2). In b/187442669, we saw a blank string
|
|
948
|
+
// (CC2=;CC3=), so default to "und" (the code for "undetermined").
|
|
949
|
+
language = channelAndLanguage[1] || 'und';
|
|
852
950
|
}
|
|
853
951
|
closedCaptions.set(channel, LanguageUtils.normalize(language));
|
|
854
952
|
}
|
|
@@ -935,8 +1033,13 @@ shaka.dash.DashParser = class {
|
|
|
935
1033
|
// Parse Representations into Streams.
|
|
936
1034
|
const representations = XmlUtils.findChildren(elem, 'Representation');
|
|
937
1035
|
const streams = representations.map((representation) => {
|
|
938
|
-
|
|
939
|
-
language, label, main, roleValues,
|
|
1036
|
+
const parsedRepresentation = this.parseRepresentation_(context,
|
|
1037
|
+
contentProtection, kind, language, label, main, roleValues,
|
|
1038
|
+
closedCaptions, representation);
|
|
1039
|
+
if (parsedRepresentation) {
|
|
1040
|
+
parsedRepresentation.hdr = parsedRepresentation.hdr || videoRange;
|
|
1041
|
+
}
|
|
1042
|
+
return parsedRepresentation;
|
|
940
1043
|
}).filter((s) => !!s);
|
|
941
1044
|
|
|
942
1045
|
if (streams.length == 0) {
|
|
@@ -1060,7 +1163,7 @@ shaka.dash.DashParser = class {
|
|
|
1060
1163
|
const hasManifest = !!this.manifest_;
|
|
1061
1164
|
streamInfo = shaka.dash.SegmentTemplate.createStreamInfo(
|
|
1062
1165
|
context, requestInitSegment, this.segmentIndexMap_, hasManifest,
|
|
1063
|
-
this.config_.dash.initialSegmentLimit);
|
|
1166
|
+
this.config_.dash.initialSegmentLimit, this.periodDurations_);
|
|
1064
1167
|
} else {
|
|
1065
1168
|
goog.asserts.assert(isText,
|
|
1066
1169
|
'Must have Segment* with non-text streams.');
|
package/lib/dash/segment_list.js
CHANGED
|
@@ -63,12 +63,12 @@ shaka.dash.SegmentList = class {
|
|
|
63
63
|
const start = context.presentationTimeline.getSegmentAvailabilityStart();
|
|
64
64
|
segmentIndex.mergeAndEvict(references, start);
|
|
65
65
|
} else {
|
|
66
|
-
context.presentationTimeline.notifySegments(references);
|
|
67
66
|
segmentIndex = new shaka.media.SegmentIndex(references);
|
|
68
67
|
if (id && context.dynamic) {
|
|
69
68
|
segmentIndexMap[id] = segmentIndex;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
71
|
+
context.presentationTimeline.notifySegments(references);
|
|
72
72
|
|
|
73
73
|
if (!context.dynamic || !context.periodInfo.isLastPeriod) {
|
|
74
74
|
const periodStart = context.periodInfo.start;
|
|
@@ -35,11 +35,12 @@ shaka.dash.SegmentTemplate = class {
|
|
|
35
35
|
* @param {boolean} isUpdate True if the manifest is being updated.
|
|
36
36
|
* @param {number} segmentLimit The maximum number of segments to generate for
|
|
37
37
|
* a SegmentTemplate with fixed duration.
|
|
38
|
+
* @param {!Object.<string, number>} periodDurationMap
|
|
38
39
|
* @return {shaka.dash.DashParser.StreamInfo}
|
|
39
40
|
*/
|
|
40
41
|
static createStreamInfo(
|
|
41
42
|
context, requestInitSegment, segmentIndexMap, isUpdate,
|
|
42
|
-
segmentLimit) {
|
|
43
|
+
segmentLimit, periodDurationMap) {
|
|
43
44
|
goog.asserts.assert(context.representation.segmentTemplate,
|
|
44
45
|
'Should only be called with SegmentTemplate');
|
|
45
46
|
const SegmentTemplate = shaka.dash.SegmentTemplate;
|
|
@@ -77,7 +78,8 @@ shaka.dash.SegmentTemplate = class {
|
|
|
77
78
|
return {
|
|
78
79
|
generateSegmentIndex: () => {
|
|
79
80
|
return SegmentTemplate.generateSegmentIndexFromDuration_(
|
|
80
|
-
shallowCopyOfContext, info, segmentLimit, initSegmentReference
|
|
81
|
+
shallowCopyOfContext, info, segmentLimit, initSegmentReference,
|
|
82
|
+
periodDurationMap);
|
|
81
83
|
},
|
|
82
84
|
};
|
|
83
85
|
} else {
|
|
@@ -114,12 +116,12 @@ shaka.dash.SegmentTemplate = class {
|
|
|
114
116
|
segmentIndex.mergeAndEvict(references,
|
|
115
117
|
context.presentationTimeline.getSegmentAvailabilityStart());
|
|
116
118
|
} else {
|
|
117
|
-
context.presentationTimeline.notifySegments(references);
|
|
118
119
|
segmentIndex = new shaka.media.SegmentIndex(references);
|
|
119
120
|
if (id && context.dynamic) {
|
|
120
121
|
segmentIndexMap[id] = segmentIndex;
|
|
121
122
|
}
|
|
122
123
|
}
|
|
124
|
+
context.presentationTimeline.notifySegments(references);
|
|
123
125
|
|
|
124
126
|
if (shouldFit) {
|
|
125
127
|
segmentIndex.fit(periodStart, periodEnd);
|
|
@@ -259,11 +261,12 @@ shaka.dash.SegmentTemplate = class {
|
|
|
259
261
|
* @param {shaka.dash.SegmentTemplate.SegmentTemplateInfo} info
|
|
260
262
|
* @param {number} segmentLimit The maximum number of segments to generate.
|
|
261
263
|
* @param {shaka.media.InitSegmentReference} initSegmentReference
|
|
264
|
+
* @param {!Object.<string, number>} periodDurationMap
|
|
262
265
|
* @return {!Promise.<shaka.media.SegmentIndex>}
|
|
263
266
|
* @private
|
|
264
267
|
*/
|
|
265
268
|
static generateSegmentIndexFromDuration_(
|
|
266
|
-
context, info, segmentLimit, initSegmentReference) {
|
|
269
|
+
context, info, segmentLimit, initSegmentReference, periodDurationMap) {
|
|
267
270
|
goog.asserts.assert(info.mediaTemplate,
|
|
268
271
|
'There should be a media template with duration');
|
|
269
272
|
|
|
@@ -275,9 +278,20 @@ shaka.dash.SegmentTemplate = class {
|
|
|
275
278
|
// Capture values that could change as the parsing context moves on to
|
|
276
279
|
// other parts of the manifest.
|
|
277
280
|
const periodStart = context.periodInfo.start;
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
+
const periodId = context.period.id;
|
|
282
|
+
const initialPeriodDuration = context.periodInfo.duration;
|
|
283
|
+
|
|
284
|
+
// For multi-period live streams the period duration may not be known until
|
|
285
|
+
// the following period appears in an updated manifest. periodDurationMap
|
|
286
|
+
// provides the updated period duration.
|
|
287
|
+
const getPeriodEnd = () => {
|
|
288
|
+
const periodDuration =
|
|
289
|
+
(periodId != null && periodDurationMap[periodId]) ||
|
|
290
|
+
initialPeriodDuration;
|
|
291
|
+
const periodEnd = periodDuration ?
|
|
292
|
+
(periodStart + periodDuration) : Infinity;
|
|
293
|
+
return periodEnd;
|
|
294
|
+
};
|
|
281
295
|
|
|
282
296
|
const segmentDuration = info.segmentDuration;
|
|
283
297
|
goog.asserts.assert(
|
|
@@ -304,7 +318,7 @@ shaka.dash.SegmentTemplate = class {
|
|
|
304
318
|
|
|
305
319
|
Math.min(
|
|
306
320
|
presentationTimeline.getSegmentAvailabilityEnd(),
|
|
307
|
-
|
|
321
|
+
getPeriodEnd()),
|
|
308
322
|
];
|
|
309
323
|
};
|
|
310
324
|
|
|
@@ -376,16 +390,17 @@ shaka.dash.SegmentTemplate = class {
|
|
|
376
390
|
|
|
377
391
|
// Relative to the presentation.
|
|
378
392
|
const segmentStart = segmentPeriodTime + periodStart;
|
|
393
|
+
const trueSegmentEnd = segmentStart + segmentDuration;
|
|
379
394
|
// Cap the segment end at the period end so that references from the
|
|
380
395
|
// next period will fit neatly after it.
|
|
381
|
-
const segmentEnd = Math.min(
|
|
396
|
+
const segmentEnd = Math.min(trueSegmentEnd, getPeriodEnd());
|
|
382
397
|
|
|
383
398
|
// This condition will be true unless the segmentStart was >= periodEnd.
|
|
384
399
|
// If we've done the position calculations correctly, this won't happen.
|
|
385
400
|
goog.asserts.assert(segmentStart < segmentEnd,
|
|
386
401
|
'Generated a segment outside of the period!');
|
|
387
402
|
|
|
388
|
-
|
|
403
|
+
const ref = new shaka.media.SegmentReference(
|
|
389
404
|
segmentStart,
|
|
390
405
|
segmentEnd,
|
|
391
406
|
getUris,
|
|
@@ -394,7 +409,10 @@ shaka.dash.SegmentTemplate = class {
|
|
|
394
409
|
initSegmentReference,
|
|
395
410
|
timestampOffset,
|
|
396
411
|
/* appendWindowStart= */ periodStart,
|
|
397
|
-
/* appendWindowEnd= */
|
|
412
|
+
/* appendWindowEnd= */ getPeriodEnd());
|
|
413
|
+
// This is necessary information for thumbnail streams:
|
|
414
|
+
ref.trueEndTime = trueSegmentEnd;
|
|
415
|
+
return ref;
|
|
398
416
|
};
|
|
399
417
|
|
|
400
418
|
for (let position = minPosition; position <= maxPosition; ++position) {
|
|
@@ -407,7 +425,15 @@ shaka.dash.SegmentTemplate = class {
|
|
|
407
425
|
|
|
408
426
|
// If the availability timeline currently ends before the period, we will
|
|
409
427
|
// need to add references over time.
|
|
410
|
-
|
|
428
|
+
const willNeedToAddReferences =
|
|
429
|
+
presentationTimeline.getSegmentAvailabilityEnd() < getPeriodEnd();
|
|
430
|
+
|
|
431
|
+
// When we start a live stream with a period that ends within the
|
|
432
|
+
// availability window we will not need to add more references, but we will
|
|
433
|
+
// need to evict old references.
|
|
434
|
+
const willNeedToEvictReferences = presentationTimeline.isLive();
|
|
435
|
+
|
|
436
|
+
if (willNeedToAddReferences || willNeedToEvictReferences) {
|
|
411
437
|
// The period continues to get longer over time, so check for new
|
|
412
438
|
// references once every |segmentDuration| seconds.
|
|
413
439
|
// We clamp to |minPosition| in case the initial range was reversed and no
|
|
@@ -416,7 +442,9 @@ shaka.dash.SegmentTemplate = class {
|
|
|
416
442
|
let nextPosition = Math.max(minPosition, maxPosition + 1);
|
|
417
443
|
segmentIndex.updateEvery(segmentDuration, () => {
|
|
418
444
|
// Evict any references outside the window.
|
|
419
|
-
|
|
445
|
+
const availabilityStartTime =
|
|
446
|
+
presentationTimeline.getSegmentAvailabilityStart();
|
|
447
|
+
segmentIndex.evict(availabilityStartTime);
|
|
420
448
|
|
|
421
449
|
// Compute any new references that need to be added.
|
|
422
450
|
const [_, maxPosition] = computeAvailablePositionRange();
|
|
@@ -426,8 +454,10 @@ shaka.dash.SegmentTemplate = class {
|
|
|
426
454
|
references.push(reference);
|
|
427
455
|
nextPosition++;
|
|
428
456
|
}
|
|
429
|
-
|
|
430
|
-
|
|
457
|
+
|
|
458
|
+
// The timer must continue firing until the entire period is
|
|
459
|
+
// unavailable, so that all references will be evicted.
|
|
460
|
+
if (availabilityStartTime > getPeriodEnd() && !references.length) {
|
|
431
461
|
// Signal stop.
|
|
432
462
|
return null;
|
|
433
463
|
}
|
package/lib/dependencies/all.js
CHANGED
|
@@ -14,7 +14,7 @@ shaka.dependencies = class {
|
|
|
14
14
|
* Registers a new dependency.
|
|
15
15
|
*
|
|
16
16
|
* @param {shaka.dependencies.Allowed} key which is used for retrieving a
|
|
17
|
-
*
|
|
17
|
+
* dependency
|
|
18
18
|
* @param {?} dep a dependency
|
|
19
19
|
* @export
|
|
20
20
|
*/
|
|
@@ -22,14 +22,15 @@ shaka.dependencies = class {
|
|
|
22
22
|
if (!shaka.dependencies.Allowed[key]) {
|
|
23
23
|
throw new Error(`${key} is not supported`);
|
|
24
24
|
}
|
|
25
|
-
shaka.dependencies.dependencies_.set(key, dep);
|
|
25
|
+
shaka.dependencies.dependencies_.set(key, () => dep);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Check if we have a dependency for the key
|
|
30
|
-
*
|
|
29
|
+
* Check if we have a dependency for the key.
|
|
30
|
+
*
|
|
31
31
|
* @param {shaka.dependencies.Allowed} key key
|
|
32
32
|
* @return {boolean}
|
|
33
|
+
* @export
|
|
33
34
|
*/
|
|
34
35
|
static has(key) {
|
|
35
36
|
return shaka.dependencies.dependencies_.has(key);
|
|
@@ -38,16 +39,10 @@ shaka.dependencies = class {
|
|
|
38
39
|
/** @return {?muxjs} */
|
|
39
40
|
static muxjs() {
|
|
40
41
|
return /** @type {?muxjs} */ (shaka.dependencies.dependencies_.get(
|
|
41
|
-
shaka.dependencies.Allowed.muxjs));
|
|
42
|
+
shaka.dependencies.Allowed.muxjs)());
|
|
42
43
|
}
|
|
43
44
|
};
|
|
44
45
|
|
|
45
|
-
/**
|
|
46
|
-
* Contains shared dependencies that could be used by other components.
|
|
47
|
-
* @private {!Map.<string, Object>}
|
|
48
|
-
*/
|
|
49
|
-
shaka.dependencies.dependencies_ = new Map();
|
|
50
|
-
|
|
51
46
|
/**
|
|
52
47
|
* @export
|
|
53
48
|
* @enum {string}
|
|
@@ -56,7 +51,12 @@ shaka.dependencies.Allowed = {
|
|
|
56
51
|
muxjs: 'muxjs',
|
|
57
52
|
};
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Contains accessor functions to shared dependencies that could be used by
|
|
56
|
+
* other components. The default accessors can be overridden.
|
|
57
|
+
*
|
|
58
|
+
* @private {!Map.<shaka.dependencies.Allowed, function():?>}
|
|
59
|
+
*/
|
|
60
|
+
shaka.dependencies.dependencies_ = new Map([
|
|
61
|
+
[shaka.dependencies.Allowed.muxjs, () => window.muxjs],
|
|
62
|
+
]);
|