shaka-player 3.0.11 → 3.0.15
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 +5 -0
- package/CHANGELOG.md +132 -0
- package/CONTRIBUTORS +5 -0
- 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/shakaBuildHelpers.py +6 -5
- package/build/subprocessWindowsPatch.py +103 -0
- package/demo/common/assets.js +1 -1
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +2 -1
- package/demo/demo.less +4 -0
- package/demo/demo_utils.js +1 -0
- package/demo/input_container.js +2 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +63 -60
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +63 -60
- 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 +35 -33
- package/dist/receiver.compiled.debug.js +59 -56
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +59 -56
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +131 -5
- package/dist/shaka-player.compiled.debug.d.ts +131 -5
- package/dist/shaka-player.compiled.debug.externs.js +142 -8
- package/dist/shaka-player.compiled.debug.js +330 -312
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +142 -8
- package/dist/shaka-player.compiled.js +707 -702
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +131 -5
- package/dist/shaka-player.ui.debug.d.ts +131 -5
- package/dist/shaka-player.ui.debug.externs.js +144 -10
- package/dist/shaka-player.ui.debug.js +354 -333
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +144 -10
- package/dist/shaka-player.ui.js +860 -855
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/docs/tutorials/faq.md +25 -0
- package/externs/aria.js +18 -0
- package/externs/ima.js +4 -0
- package/externs/shaka/ads.js +2 -0
- package/karma.conf.js +1 -0
- 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 +6 -2
- package/lib/cast/cast_sender.js +2 -2
- package/lib/dash/dash_parser.js +63 -1
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +41 -14
- package/lib/hls/hls_parser.js +41 -11
- package/lib/media/content_workarounds.js +18 -0
- package/lib/media/drm_engine.js +8 -0
- package/lib/media/gap_jumping_controller.js +8 -4
- package/lib/media/media_source_engine.js +7 -0
- package/lib/media/playhead.js +1 -1
- package/lib/media/presentation_timeline.js +3 -2
- package/lib/media/segment_index.js +41 -11
- package/lib/media/streaming_engine.js +34 -21
- package/lib/net/networking_engine.js +2 -1
- package/lib/offline/download_manager.js +1 -2
- package/lib/player.js +99 -44
- 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/mediasource.js +2 -0
- 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/video_play_promise.js +2 -0
- package/lib/polyfill/videoplaybackquality.js +2 -0
- package/lib/polyfill/vttcue.js +2 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/ttml_text_parser.js +5 -0
- package/lib/text/ui_text_displayer.js +37 -3
- 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/periods.js +25 -3
- package/lib/util/stream_utils.js +11 -0
- 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/dash/dash_parser_manifest_unit.js +73 -0
- package/test/demo/demo_unit.js +0 -1
- package/test/hls/hls_parser_unit.js +45 -1
- package/test/media/drm_engine_unit.js +56 -0
- package/test/media/playhead_unit.js +27 -1
- package/test/media/presentation_timeline_unit.js +19 -0
- package/test/media/segment_index_unit.js +79 -6
- package/test/media/streaming_engine_unit.js +3 -3
- package/test/player_integration.js +38 -0
- package/test/player_unit.js +19 -1
- 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/util/fake_ad_manager.js +4 -1
- package/test/test/util/fake_media_source_engine.js +4 -0
- 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 +9 -0
- package/test/text/ui_text_displayer_unit.js +19 -3
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/ui_unit.js +4 -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 +3 -0
- 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
|
@@ -88,6 +88,9 @@ shaka.text.Mp4TtmlParser = class {
|
|
|
88
88
|
|
|
89
89
|
shaka.text.TextEngine.registerParser(
|
|
90
90
|
'application/mp4; codecs="stpp"', () => new shaka.text.Mp4TtmlParser());
|
|
91
|
+
shaka.text.TextEngine.registerParser(
|
|
92
|
+
'application/mp4; codecs="stpp.ttml"',
|
|
93
|
+
() => new shaka.text.Mp4TtmlParser());
|
|
91
94
|
shaka.text.TextEngine.registerParser(
|
|
92
95
|
'application/mp4; codecs="stpp.ttml.im1t"',
|
|
93
96
|
() => new shaka.text.Mp4TtmlParser());
|
|
@@ -277,6 +277,11 @@ shaka.text.TtmlTextParser = class {
|
|
|
277
277
|
!cueElement.hasAttribute('begin') &&
|
|
278
278
|
!cueElement.hasAttribute('end');
|
|
279
279
|
|
|
280
|
+
if (cueElement.nodeType == Node.COMMENT_NODE) {
|
|
281
|
+
// The comments do not contain information that interests us here.
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
280
285
|
if (
|
|
281
286
|
cueElement.nodeType != Node.ELEMENT_NODE ||
|
|
282
287
|
/* Disregards empty elements without time attributes nor content
|
|
@@ -11,6 +11,7 @@ goog.require('goog.asserts');
|
|
|
11
11
|
goog.require('shaka.text.Cue');
|
|
12
12
|
goog.require('shaka.text.CueRegion');
|
|
13
13
|
goog.require('shaka.util.Dom');
|
|
14
|
+
goog.require('shaka.util.EventManager');
|
|
14
15
|
goog.require('shaka.util.Timer');
|
|
15
16
|
|
|
16
17
|
|
|
@@ -70,6 +71,22 @@ shaka.text.UITextDisplayer = class {
|
|
|
70
71
|
|
|
71
72
|
/** private {Map.<!shaka.extern.Cue, !HTMLElement>} */
|
|
72
73
|
this.currentCuesMap_ = new Map();
|
|
74
|
+
|
|
75
|
+
/** @private {shaka.util.EventManager} */
|
|
76
|
+
this.eventManager_ = new shaka.util.EventManager();
|
|
77
|
+
|
|
78
|
+
this.eventManager_.listen(document, 'fullscreenchange', () => {
|
|
79
|
+
this.updateCaptions_(/* forceUpdate= */ true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/** @private {ResizeObserver} */
|
|
83
|
+
this.resizeObserver_ = null;
|
|
84
|
+
if ('ResizeObserver' in window) {
|
|
85
|
+
this.resizeObserver_ = new ResizeObserver(() => {
|
|
86
|
+
this.updateCaptions_(/* forceUpdate= */ true);
|
|
87
|
+
});
|
|
88
|
+
this.resizeObserver_.observe(this.textContainer_);
|
|
89
|
+
}
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
|
|
@@ -114,6 +131,17 @@ shaka.text.UITextDisplayer = class {
|
|
|
114
131
|
}
|
|
115
132
|
|
|
116
133
|
this.currentCuesMap_.clear();
|
|
134
|
+
|
|
135
|
+
// Tear-down the event manager to ensure messages stop moving around.
|
|
136
|
+
if (this.eventManager_) {
|
|
137
|
+
this.eventManager_.release();
|
|
138
|
+
this.eventManager_ = null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (this.resizeObserver_) {
|
|
142
|
+
this.resizeObserver_.disconnect();
|
|
143
|
+
this.resizeObserver_ = null;
|
|
144
|
+
}
|
|
117
145
|
}
|
|
118
146
|
|
|
119
147
|
|
|
@@ -128,9 +156,14 @@ shaka.text.UITextDisplayer = class {
|
|
|
128
156
|
}
|
|
129
157
|
|
|
130
158
|
// Remove the cues out of the time range.
|
|
159
|
+
const oldNumCues = this.cues_.length;
|
|
131
160
|
this.cues_ = this.cues_.filter(
|
|
132
161
|
(cue) => cue.startTime < start || cue.endTime >= end);
|
|
133
|
-
this
|
|
162
|
+
// If anything was actually removed in this process, force the captions to
|
|
163
|
+
// update. This makes sure that the currently-displayed cues will stop
|
|
164
|
+
// displaying if removed (say, due to the user changing languages).
|
|
165
|
+
const forceUpdate = oldNumCues > this.cues_.length;
|
|
166
|
+
this.updateCaptions_(forceUpdate);
|
|
134
167
|
|
|
135
168
|
return true;
|
|
136
169
|
}
|
|
@@ -154,9 +187,10 @@ shaka.text.UITextDisplayer = class {
|
|
|
154
187
|
|
|
155
188
|
/**
|
|
156
189
|
* Display the current captions.
|
|
190
|
+
* @param {boolean=} forceUpdate
|
|
157
191
|
* @private
|
|
158
192
|
*/
|
|
159
|
-
updateCaptions_() {
|
|
193
|
+
updateCaptions_(forceUpdate = false) {
|
|
160
194
|
const currentTime = this.video_.currentTime;
|
|
161
195
|
|
|
162
196
|
// Return true if the cue should be displayed at the current time point.
|
|
@@ -168,7 +202,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
168
202
|
// For each cue in the current cues map, if the cue's end time has passed,
|
|
169
203
|
// remove the entry from the map, and remove the captions from the page.
|
|
170
204
|
for (const cue of this.currentCuesMap_.keys()) {
|
|
171
|
-
if (!shouldCueBeDisplayed(cue)) {
|
|
205
|
+
if (!shouldCueBeDisplayed(cue) || forceUpdate) {
|
|
172
206
|
const captions = this.currentCuesMap_.get(cue);
|
|
173
207
|
this.textContainer_.removeChild(captions);
|
|
174
208
|
this.currentCuesMap_.delete(cue);
|
package/lib/util/error.js
CHANGED
|
@@ -22,10 +22,7 @@ goog.provide('shaka.util.Error');
|
|
|
22
22
|
* <ul>
|
|
23
23
|
* <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaError">MediaError</a>
|
|
24
24
|
* <li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">HTTP Codes</a>
|
|
25
|
-
* <li>
|
|
26
|
-
* <a href="https://docs.microsoft.com/en-us/windows/win32/wmdm/error-codes">PlayReady errors</a>
|
|
27
|
-
* or
|
|
28
|
-
* <a href="https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/winrt/Windows.Media.Protection.PlayReadyErrors.h">more PlayReady errors</a>
|
|
25
|
+
* <li><a href="https://hresult.info">Edge/PlayReady errors</a>
|
|
29
26
|
* </ul>
|
|
30
27
|
*
|
|
31
28
|
* @export
|
package/lib/util/fake_event.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
goog.provide('shaka.util.FakeEvent');
|
|
8
8
|
|
|
9
|
+
goog.require('goog.asserts');
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* @summary Create an Event work-alike object based on the provided dictionary.
|
|
@@ -16,18 +18,62 @@ goog.provide('shaka.util.FakeEvent');
|
|
|
16
18
|
*/
|
|
17
19
|
shaka.util.FakeEvent = class {
|
|
18
20
|
/**
|
|
19
|
-
* @param {
|
|
20
|
-
* @
|
|
21
|
+
* @param {!Event} event
|
|
22
|
+
* @return {!shaka.util.FakeEvent}
|
|
21
23
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
for (const key in
|
|
25
|
-
Object.defineProperty(
|
|
26
|
-
value:
|
|
24
|
+
static fromRealEvent(event) {
|
|
25
|
+
const fakeEvent = new shaka.util.FakeEvent(event.type);
|
|
26
|
+
for (const key in event) {
|
|
27
|
+
Object.defineProperty(fakeEvent, key, {
|
|
28
|
+
value: event[key],
|
|
27
29
|
writable: true,
|
|
28
30
|
enumerable: true,
|
|
29
31
|
});
|
|
30
32
|
}
|
|
33
|
+
return fakeEvent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Allows us to tell the compiler that the dictionary "map" is actually a
|
|
38
|
+
* generic object, for backwards compatibility.
|
|
39
|
+
* @param {!Map.<string, Object>} dict
|
|
40
|
+
* @return {!Object}
|
|
41
|
+
* @suppress {invalidCasts}
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
static recastDictAsObject_(dict) {
|
|
45
|
+
goog.asserts.assert(!(dict instanceof Map), 'dict should not be a map');
|
|
46
|
+
return /** @type {!Object} */ (dict);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} type
|
|
51
|
+
* @param {Map.<string, Object>=} dict
|
|
52
|
+
*/
|
|
53
|
+
constructor(type, dict) {
|
|
54
|
+
if (dict) {
|
|
55
|
+
if (dict instanceof Map) {
|
|
56
|
+
// Take properties from dict if present.
|
|
57
|
+
for (const key of dict.keys()) {
|
|
58
|
+
Object.defineProperty(this, key, {
|
|
59
|
+
value: dict.get(key),
|
|
60
|
+
writable: true,
|
|
61
|
+
enumerable: true,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// For backwards compatibility with external apps that may make use of
|
|
66
|
+
// this public constructor, this should still accept generic objects.
|
|
67
|
+
const obj = shaka.util.FakeEvent.recastDictAsObject_(dict);
|
|
68
|
+
for (const key in obj) {
|
|
69
|
+
Object.defineProperty(this, key, {
|
|
70
|
+
value: obj[key],
|
|
71
|
+
writable: true,
|
|
72
|
+
enumerable: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
31
77
|
|
|
32
78
|
// The properties below cannot be set by the dict. They are all provided
|
|
33
79
|
// for compatibility with native events.
|
package/lib/util/mime_utils.js
CHANGED
|
@@ -79,6 +79,39 @@ shaka.util.MimeUtils = class {
|
|
|
79
79
|
return parts[0];
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Takes a full MIME type (with codecs) or basic MIME type (without codecs)
|
|
84
|
+
* and returns a basic MIME type (without codecs or other parameters).
|
|
85
|
+
*
|
|
86
|
+
* @param {string} mimeType
|
|
87
|
+
* @return {string}
|
|
88
|
+
*/
|
|
89
|
+
static getBasicType(mimeType) {
|
|
90
|
+
return mimeType.split(';')[0];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Takes a MIME type and returns the codecs parameter, or an empty string if
|
|
95
|
+
* there is no codecs parameter.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} mimeType
|
|
98
|
+
* @return {string}
|
|
99
|
+
*/
|
|
100
|
+
static getCodecs(mimeType) {
|
|
101
|
+
// Parse the basic MIME type from its parameters.
|
|
102
|
+
const pieces = mimeType.split(/ *; */);
|
|
103
|
+
pieces.shift(); // Remove basic MIME type from pieces.
|
|
104
|
+
|
|
105
|
+
const codecs = pieces.find((piece) => piece.startsWith('codecs='));
|
|
106
|
+
if (!codecs) {
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// The value may be quoted, so remove quotes at the beginning or end.
|
|
111
|
+
const value = codecs.split('=')[1].replace(/^"|"$/g, '');
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
|
|
82
115
|
/**
|
|
83
116
|
* Get the base and profile of a codec string. Where [0] will be the codec
|
|
84
117
|
* base and [1] will be the profile.
|
package/lib/util/periods.js
CHANGED
|
@@ -75,7 +75,10 @@ shaka.util.PeriodCombiner = class {
|
|
|
75
75
|
|
|
76
76
|
/** @return {!Array.<shaka.extern.Stream>} */
|
|
77
77
|
getTextStreams() {
|
|
78
|
-
|
|
78
|
+
// Return a copy of the array because makeTextStreamsForClosedCaptions
|
|
79
|
+
// may make changes to the contents of the array. Those changes should not
|
|
80
|
+
// propagate back to the PeriodCombiner.
|
|
81
|
+
return this.textStreams_.slice();
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
/**
|
|
@@ -551,7 +554,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
551
554
|
|
|
552
555
|
// We need to create all the per-period segment indexes and append them to
|
|
553
556
|
// the output's MetaSegmentIndex.
|
|
554
|
-
await
|
|
557
|
+
await shaka.util.PeriodCombiner.createSegmentIndexes_(matches);
|
|
555
558
|
|
|
556
559
|
// Assure the compiler that matches didn't become null during the async
|
|
557
560
|
// operation above.
|
|
@@ -563,6 +566,25 @@ shaka.util.PeriodCombiner = class {
|
|
|
563
566
|
return true;
|
|
564
567
|
}
|
|
565
568
|
|
|
569
|
+
/**
|
|
570
|
+
* Creates all segment indexes for an array of streams. Returns once every
|
|
571
|
+
* segment index is created.
|
|
572
|
+
*
|
|
573
|
+
* @param {!Array.<!shaka.extern.Stream>} streams
|
|
574
|
+
* @return {!Promise}
|
|
575
|
+
* @private
|
|
576
|
+
*/
|
|
577
|
+
static createSegmentIndexes_(streams) {
|
|
578
|
+
const operations = [];
|
|
579
|
+
for (const stream of streams) {
|
|
580
|
+
operations.push(stream.createSegmentIndex());
|
|
581
|
+
if (stream.trickModeVideo && !stream.trickModeVideo.segmentIndex) {
|
|
582
|
+
operations.push(stream.trickModeVideo.createSegmentIndex());
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return Promise.all(operations);
|
|
586
|
+
}
|
|
587
|
+
|
|
566
588
|
/**
|
|
567
589
|
* Create a new output Stream based on a particular input Stream. Locates
|
|
568
590
|
* matching Streams in all other periods and combines them into an output
|
|
@@ -605,7 +627,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
605
627
|
if (outputStream.createSegmentIndex) {
|
|
606
628
|
// For T == Stream, we need to create all the per-period segment indexes
|
|
607
629
|
// in advance. concat() will add them to the output's MetaSegmentIndex.
|
|
608
|
-
await
|
|
630
|
+
await shaka.util.PeriodCombiner.createSegmentIndexes_(matches);
|
|
609
631
|
}
|
|
610
632
|
|
|
611
633
|
// Assure the compiler that matches didn't become null during the async
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -16,6 +16,7 @@ goog.require('shaka.util.LanguageUtils');
|
|
|
16
16
|
goog.require('shaka.util.ManifestParserUtils');
|
|
17
17
|
goog.require('shaka.util.MimeUtils');
|
|
18
18
|
goog.require('shaka.util.MultiMap');
|
|
19
|
+
goog.require('shaka.util.Platform');
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -296,6 +297,16 @@ shaka.util.StreamUtils = class {
|
|
|
296
297
|
return false;
|
|
297
298
|
}
|
|
298
299
|
|
|
300
|
+
// See: https://github.com/google/shaka-player/issues/3380
|
|
301
|
+
if (shaka.util.Platform.isXboxOne() && video &&
|
|
302
|
+
((video.width && video.width > 1920) ||
|
|
303
|
+
(video.height && video.height > 1080)) &&
|
|
304
|
+
video.codecs.includes('avc1.')) {
|
|
305
|
+
shaka.log.debug('Dropping variant - video not compatible with platform',
|
|
306
|
+
StreamUtils.getStreamSummaryString_(video));
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
|
|
299
310
|
if (audio && currentVariant && currentVariant.audio) {
|
|
300
311
|
if (!StreamUtils.areStreamsCompatible_(audio, currentVariant.audio)) {
|
|
301
312
|
shaka.log.debug('Droping variant - not compatible with active audio',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shaka-player",
|
|
3
3
|
"description": "DASH/EME video player library",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.15",
|
|
5
5
|
"homepage": "https://github.com/google/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"eslint-plugin-shaka-rules": "file:./build/eslint-plugin-shaka-rules",
|
|
28
28
|
"esprima": "^4.0.1",
|
|
29
29
|
"fontfaceonload": "^1.0.2",
|
|
30
|
-
"google-closure-compiler-java": "^
|
|
31
|
-
"google-closure-library": "^
|
|
30
|
+
"google-closure-compiler-java": "^20210505.0.0",
|
|
31
|
+
"google-closure-library": "^20210406.0.0",
|
|
32
32
|
"htmlhint": "^0.11.0",
|
|
33
33
|
"jasmine-ajax": "^4.0.0",
|
|
34
34
|
"jimp": "^0.11.0",
|
|
@@ -297,7 +297,8 @@ describe('CastProxy', () => {
|
|
|
297
297
|
'timeupdate', Util.spyFunc(proxyListener));
|
|
298
298
|
|
|
299
299
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
300
|
-
const fakeEvent = new FakeEvent(
|
|
300
|
+
const fakeEvent = new FakeEvent(
|
|
301
|
+
'timeupdate', (new Map()).set('detail', 8675309));
|
|
301
302
|
mockVideo.on['timeupdate'](fakeEvent);
|
|
302
303
|
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
303
304
|
type: 'timeupdate',
|
|
@@ -315,7 +316,8 @@ describe('CastProxy', () => {
|
|
|
315
316
|
mockSender.hasRemoteProperties.and.returnValue(true);
|
|
316
317
|
|
|
317
318
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
318
|
-
const fakeEvent = new FakeEvent(
|
|
319
|
+
const fakeEvent = new FakeEvent(
|
|
320
|
+
'timeupdate', (new Map()).set('detail', 8675309));
|
|
319
321
|
mockVideo.on['timeupdate'](fakeEvent);
|
|
320
322
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
321
323
|
});
|
|
@@ -332,7 +334,8 @@ describe('CastProxy', () => {
|
|
|
332
334
|
mockSender.hasRemoteProperties.and.returnValue(true);
|
|
333
335
|
|
|
334
336
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
335
|
-
const fakeEvent = new FakeEvent(
|
|
337
|
+
const fakeEvent = new FakeEvent(
|
|
338
|
+
'timeupdate', (new Map()).set('detail', 8675309));
|
|
336
339
|
mockSender.onRemoteEvent('video', fakeEvent);
|
|
337
340
|
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
338
341
|
type: 'timeupdate',
|
|
@@ -446,7 +449,8 @@ describe('CastProxy', () => {
|
|
|
446
449
|
'buffering', Util.spyFunc(proxyListener));
|
|
447
450
|
|
|
448
451
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
449
|
-
const fakeEvent = new FakeEvent(
|
|
452
|
+
const fakeEvent = new FakeEvent(
|
|
453
|
+
'buffering', (new Map()).set('detail', 8675309));
|
|
450
454
|
mockPlayer.listeners['buffering'](fakeEvent);
|
|
451
455
|
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
452
456
|
type: 'buffering',
|
|
@@ -464,7 +468,8 @@ describe('CastProxy', () => {
|
|
|
464
468
|
mockSender.hasRemoteProperties.and.returnValue(true);
|
|
465
469
|
|
|
466
470
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
467
|
-
const fakeEvent = new FakeEvent(
|
|
471
|
+
const fakeEvent = new FakeEvent(
|
|
472
|
+
'buffering', (new Map()).set('detail', 8675309));
|
|
468
473
|
mockPlayer.listeners['buffering'](fakeEvent);
|
|
469
474
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
470
475
|
});
|
|
@@ -481,7 +486,8 @@ describe('CastProxy', () => {
|
|
|
481
486
|
mockSender.hasRemoteProperties.and.returnValue(true);
|
|
482
487
|
|
|
483
488
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
484
|
-
const fakeEvent = new FakeEvent(
|
|
489
|
+
const fakeEvent = new FakeEvent(
|
|
490
|
+
'buffering', (new Map()).set('detail', 8675309));
|
|
485
491
|
mockSender.onRemoteEvent('player', fakeEvent);
|
|
486
492
|
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
487
493
|
type: 'buffering',
|
|
@@ -170,7 +170,7 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
170
170
|
// Check that the update message is of a reasonable size. From previous
|
|
171
171
|
// testing we found that the socket would silently reject data that got
|
|
172
172
|
// too big. 6KB is safely below the limit.
|
|
173
|
-
expect(message.length).toBeLessThan(
|
|
173
|
+
expect(message.length).toBeLessThan(7 * 1024);
|
|
174
174
|
});
|
|
175
175
|
|
|
176
176
|
drmIt('has reasonable average message size', async () => {
|
|
@@ -129,7 +129,7 @@ describe('CastUtils', () => {
|
|
|
129
129
|
expect(typeof deserialized).toBe('object');
|
|
130
130
|
|
|
131
131
|
// The object can be used to construct a FakeEvent.
|
|
132
|
-
const fakeEvent =
|
|
132
|
+
const fakeEvent = FakeEvent.fromRealEvent(deserialized);
|
|
133
133
|
|
|
134
134
|
// The fake event has the same type and properties as the original.
|
|
135
135
|
const asObj = /** @type {!Object} */ (fakeEvent);
|
|
@@ -1796,4 +1796,77 @@ describe('DashParser Manifest', () => {
|
|
|
1796
1796
|
expect(uri).not.toContain('/p2/');
|
|
1797
1797
|
}
|
|
1798
1798
|
});
|
|
1799
|
+
|
|
1800
|
+
/**
|
|
1801
|
+
* @param {!Array.<number>} periods Start time of multiple periods
|
|
1802
|
+
* @return {string}
|
|
1803
|
+
*/
|
|
1804
|
+
function buildManifestWithPeriodStartTime(periods) {
|
|
1805
|
+
const mpdTemplate = [
|
|
1806
|
+
`<MPD type="dynamic"`,
|
|
1807
|
+
'availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
1808
|
+
'timeShiftBufferDepth="PT10H">',
|
|
1809
|
+
' %(periods)s',
|
|
1810
|
+
'</MPD>',
|
|
1811
|
+
].join('\n');
|
|
1812
|
+
const periodTemplate = (id, period, duration) => {
|
|
1813
|
+
return [
|
|
1814
|
+
` <Period id="${id}" start="PT${period}S">`,
|
|
1815
|
+
' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
|
|
1816
|
+
' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
|
|
1817
|
+
' <SegmentTimeline>',
|
|
1818
|
+
` <S t="0" d="${duration}" />`,
|
|
1819
|
+
' </SegmentTimeline>',
|
|
1820
|
+
' </SegmentTemplate>',
|
|
1821
|
+
' <Representation id="1"/>',
|
|
1822
|
+
' </AdaptationSet>',
|
|
1823
|
+
' </Period>',
|
|
1824
|
+
].join('\n');
|
|
1825
|
+
};
|
|
1826
|
+
const periodXmls = periods.map((period, i) => {
|
|
1827
|
+
const duration = i+1 === periods.length ? 10 : periods[i+1] - period;
|
|
1828
|
+
// Period start time as ID here. If we use index then there will be
|
|
1829
|
+
// periods with same period ID and different start time which are invalid.
|
|
1830
|
+
return periodTemplate(period, period, duration);
|
|
1831
|
+
});
|
|
1832
|
+
return sprintf(mpdTemplate, {
|
|
1833
|
+
periods: periodXmls.join('\n'),
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// Bug description: Inconsistent period start time in the manifests due
|
|
1838
|
+
// to failover triggered in backend servers
|
|
1839
|
+
|
|
1840
|
+
// When one of the servers is down, the manifest will be served by other
|
|
1841
|
+
// redundant servers. The period start time might become out of sync
|
|
1842
|
+
// during the switch-over/recovery.
|
|
1843
|
+
|
|
1844
|
+
it('skip periods that are earlier than max period start time', async () => {
|
|
1845
|
+
const sources = [
|
|
1846
|
+
buildManifestWithPeriodStartTime([5, 15]),
|
|
1847
|
+
buildManifestWithPeriodStartTime([6, 15]), // simulate out-of-sync of +1s
|
|
1848
|
+
buildManifestWithPeriodStartTime([4, 15]), // simulate out-of-sync of -1s
|
|
1849
|
+
];
|
|
1850
|
+
const segments = [];
|
|
1851
|
+
|
|
1852
|
+
for (const source of sources) {
|
|
1853
|
+
fakeNetEngine.setResponseText('dummy://foo', source);
|
|
1854
|
+
/** @type {shaka.extern.Manifest} */
|
|
1855
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1856
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
1857
|
+
const video = manifest.variants[0].video;
|
|
1858
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1859
|
+
await video.createSegmentIndex();
|
|
1860
|
+
goog.asserts.assert(video.segmentIndex, 'Null segmentIndex!');
|
|
1861
|
+
segments.push(Array.from(video.segmentIndex));
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// Expect identical segments
|
|
1865
|
+
expect(segments[0].length).toBe(2);
|
|
1866
|
+
expect(segments[1].length).toBe(2);
|
|
1867
|
+
expect(segments[0][0].startTime).toBe(5);
|
|
1868
|
+
expect(segments[1][0].startTime).toBe(5);
|
|
1869
|
+
expect(segments[0][1].startTime).toBe(15);
|
|
1870
|
+
expect(segments[1][1].startTime).toBe(15);
|
|
1871
|
+
});
|
|
1799
1872
|
});
|
package/test/demo/demo_unit.js
CHANGED
|
@@ -202,6 +202,46 @@ describe('HlsParser', () => {
|
|
|
202
202
|
expect(actual).toEqual(manifest);
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
+
it('prioritize AVERAGE-BANDWIDTH to BANDWIDTH', async () => {
|
|
206
|
+
const master = [
|
|
207
|
+
'#EXTM3U\n',
|
|
208
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
209
|
+
'RESOLUTION=960x540,FRAME-RATE=60,',
|
|
210
|
+
'AVERAGE-BANDWIDTH=100\n',
|
|
211
|
+
'video\n',
|
|
212
|
+
].join('');
|
|
213
|
+
|
|
214
|
+
const media = [
|
|
215
|
+
'#EXTM3U\n',
|
|
216
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
217
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
218
|
+
'#EXTINF:5,\n',
|
|
219
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
220
|
+
'main.mp4',
|
|
221
|
+
].join('');
|
|
222
|
+
|
|
223
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
224
|
+
manifest.anyTimeline();
|
|
225
|
+
manifest.addPartialVariant((variant) => {
|
|
226
|
+
variant.bandwidth = 100;
|
|
227
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
228
|
+
stream.frameRate = 60;
|
|
229
|
+
stream.mime('video/mp4', 'avc1');
|
|
230
|
+
stream.size(960, 540);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
fakeNetEngine
|
|
236
|
+
.setResponseText('test:/master', master)
|
|
237
|
+
.setResponseText('test:/video', media)
|
|
238
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
239
|
+
.setResponseValue('test:/main.mp4', segmentData);
|
|
240
|
+
|
|
241
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
242
|
+
expect(actual).toEqual(manifest);
|
|
243
|
+
});
|
|
244
|
+
|
|
205
245
|
it('ignores duplicate CODECS', async () => {
|
|
206
246
|
const master = [
|
|
207
247
|
'#EXTM3U\n',
|
|
@@ -736,7 +776,7 @@ describe('HlsParser', () => {
|
|
|
736
776
|
'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
|
|
737
777
|
'video2\n',
|
|
738
778
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
|
739
|
-
'URI="audio"\n',
|
|
779
|
+
'DEFAULT=YES,URI="audio"\n',
|
|
740
780
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="fr",',
|
|
741
781
|
'URI="audio2"\n',
|
|
742
782
|
].join('');
|
|
@@ -754,6 +794,7 @@ describe('HlsParser', () => {
|
|
|
754
794
|
manifest.anyTimeline();
|
|
755
795
|
manifest.addPartialVariant((variant) => {
|
|
756
796
|
variant.bandwidth = 200;
|
|
797
|
+
variant.primary = true;
|
|
757
798
|
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
758
799
|
stream.size(960, 540);
|
|
759
800
|
});
|
|
@@ -763,6 +804,7 @@ describe('HlsParser', () => {
|
|
|
763
804
|
});
|
|
764
805
|
manifest.addPartialVariant((variant) => {
|
|
765
806
|
variant.bandwidth = 300;
|
|
807
|
+
variant.primary = false;
|
|
766
808
|
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
767
809
|
stream.size(960, 540);
|
|
768
810
|
});
|
|
@@ -1171,6 +1213,8 @@ describe('HlsParser', () => {
|
|
|
1171
1213
|
// stream.
|
|
1172
1214
|
const timeline = actual.presentationTimeline;
|
|
1173
1215
|
expect(timeline.getDuration()).toBe(10);
|
|
1216
|
+
expect(timeline.getSeekRangeStart()).toBe(0);
|
|
1217
|
+
expect(timeline.getSeekRangeEnd()).toBe(10);
|
|
1174
1218
|
|
|
1175
1219
|
expect(actual.textStreams.length).toBe(1);
|
|
1176
1220
|
expect(actual.variants.length).toBe(1);
|
|
@@ -1954,6 +1954,49 @@ describe('DrmEngine', () => {
|
|
|
1954
1954
|
[drmInfoAudio]);
|
|
1955
1955
|
expect(returned).toEqual([drmInfoDesired]);
|
|
1956
1956
|
});
|
|
1957
|
+
|
|
1958
|
+
it('dedupes the merged init data based on keyId matching', () => {
|
|
1959
|
+
const serverCert = new Uint8Array(0);
|
|
1960
|
+
const drmInfoVideo = {
|
|
1961
|
+
keySystem: 'drm.abc',
|
|
1962
|
+
licenseServerUri: 'http://abc.drm/license',
|
|
1963
|
+
distinctiveIdentifierRequired: false,
|
|
1964
|
+
persistentStateRequired: true,
|
|
1965
|
+
videoRobustness: 'really_really_ridiculously_good',
|
|
1966
|
+
serverCertificate: serverCert,
|
|
1967
|
+
serverCertificateUri: '',
|
|
1968
|
+
initData: [{keyId: 'v-init'}],
|
|
1969
|
+
keyIds: new Set(['deadbeefdeadbeefdeadbeefdeadbeef']),
|
|
1970
|
+
};
|
|
1971
|
+
const drmInfoAudio = {
|
|
1972
|
+
keySystem: 'drm.abc',
|
|
1973
|
+
licenseServerUri: undefined,
|
|
1974
|
+
distinctiveIdentifierRequired: true,
|
|
1975
|
+
persistentStateRequired: false,
|
|
1976
|
+
audioRobustness: 'good',
|
|
1977
|
+
serverCertificate: undefined,
|
|
1978
|
+
serverCertificateUri: '',
|
|
1979
|
+
initData: [{keyId: 'v-init'}, {keyId: 'a-init'}],
|
|
1980
|
+
keyIds: new Set(['eadbeefdeadbeefdeadbeefdeadbeefd']),
|
|
1981
|
+
};
|
|
1982
|
+
const drmInfoDesired = {
|
|
1983
|
+
keySystem: 'drm.abc',
|
|
1984
|
+
licenseServerUri: 'http://abc.drm/license',
|
|
1985
|
+
distinctiveIdentifierRequired: true,
|
|
1986
|
+
persistentStateRequired: true,
|
|
1987
|
+
audioRobustness: 'good',
|
|
1988
|
+
videoRobustness: 'really_really_ridiculously_good',
|
|
1989
|
+
serverCertificate: serverCert,
|
|
1990
|
+
initData: [{keyId: 'v-init'}, {keyId: 'a-init'}],
|
|
1991
|
+
keyIds: new Set([
|
|
1992
|
+
'deadbeefdeadbeefdeadbeefdeadbeef',
|
|
1993
|
+
'eadbeefdeadbeefdeadbeefdeadbeefd',
|
|
1994
|
+
]),
|
|
1995
|
+
};
|
|
1996
|
+
const returned = shaka.media.DrmEngine.getCommonDrmInfos([drmInfoVideo],
|
|
1997
|
+
[drmInfoAudio]);
|
|
1998
|
+
expect(returned).toEqual([drmInfoDesired]);
|
|
1999
|
+
});
|
|
1957
2000
|
}); // describe('getCommonDrmInfos')
|
|
1958
2001
|
|
|
1959
2002
|
describe('configure', () => {
|
|
@@ -2087,6 +2130,19 @@ describe('DrmEngine', () => {
|
|
|
2087
2130
|
await expectAsync(drmEngine.removeSession('abc'))
|
|
2088
2131
|
.toBeRejectedWith(expected);
|
|
2089
2132
|
});
|
|
2133
|
+
|
|
2134
|
+
// Regression test for #3534
|
|
2135
|
+
it('does not remove the same session again on destroy', async () => {
|
|
2136
|
+
updatePromise.resolve();
|
|
2137
|
+
expect(session1.remove).not.toHaveBeenCalled();
|
|
2138
|
+
await drmEngine.removeSession('abc');
|
|
2139
|
+
expect(session1.remove).toHaveBeenCalled();
|
|
2140
|
+
session1.remove.calls.reset();
|
|
2141
|
+
await drmEngine.destroy();
|
|
2142
|
+
// The session should only be removed ONCE. If it's double-removed, it
|
|
2143
|
+
// will make a (non-fatal) DOMException.
|
|
2144
|
+
expect(session1.remove).not.toHaveBeenCalled();
|
|
2145
|
+
});
|
|
2090
2146
|
});
|
|
2091
2147
|
|
|
2092
2148
|
describe('expiration', () => {
|