shaka-player 4.1.3 → 4.2.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/CHANGELOG.md +33 -34
- package/demo/common/assets.js +25 -0
- package/demo/common/message_ids.js +2 -0
- package/demo/config.js +5 -1
- package/demo/index.html +1 -1
- package/demo/locales/en.json +2 -0
- package/demo/locales/source.json +8 -0
- package/dist/demo.compiled.debug.js +25 -23
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +25 -23
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +3 -2
- package/dist/locales.js +31 -31
- package/dist/receiver.compiled.debug.js +24 -22
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +24 -22
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +57 -4
- package/dist/shaka-player.compiled.debug.d.ts +57 -4
- package/dist/shaka-player.compiled.debug.externs.js +62 -4
- package/dist/shaka-player.compiled.debug.js +285 -241
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +62 -4
- package/dist/shaka-player.compiled.js +815 -798
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +57 -4
- package/dist/shaka-player.ui.debug.d.ts +57 -4
- package/dist/shaka-player.ui.debug.externs.js +62 -4
- package/dist/shaka-player.ui.debug.js +283 -239
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +62 -4
- package/dist/shaka-player.ui.js +979 -960
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +149 -0
- package/docs/tutorials/ui.md +6 -6
- package/externs/shaka/abr_manager.js +8 -0
- package/externs/shaka/manifest.js +43 -0
- package/externs/shaka/player.js +13 -2
- package/lib/abr/simple_abr_manager.js +72 -4
- package/lib/hls/hls_parser.js +117 -25
- package/lib/media/drm_engine.js +0 -2
- package/lib/media/segment_index.js +8 -0
- package/lib/media/segment_reference.js +7 -1
- package/lib/media/streaming_engine.js +38 -2
- package/lib/net/networking_engine.js +1 -0
- package/lib/player.js +8 -1
- package/lib/polyfill/patchedmediakeys_apple.js +729 -0
- package/lib/text/vtt_text_parser.js +61 -4
- package/lib/util/error.js +21 -1
- package/lib/util/fairplay_utils.js +100 -2
- package/lib/util/platform.js +12 -1
- package/lib/util/player_configuration.js +3 -10
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +1 -0
- package/test/hls/hls_parser_unit.js +49 -33
- package/test/media/streaming_engine_unit.js +79 -5
- package/test/test/util/simple_fakes.js +4 -0
- package/test/test/util/streaming_engine_util.js +10 -2
- package/test/text/vtt_text_parser_unit.js +26 -0
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
# FairPlay Support
|
|
2
2
|
|
|
3
3
|
We support FairPlay with EME on compatible environments or native `src=`.
|
|
4
|
+
|
|
5
|
+
By default Shaka Player supports Modern EME, if your provider doesn't support
|
|
6
|
+
Modern EME yet, you can use legacy Apple Media Keys with:
|
|
7
|
+
```js
|
|
8
|
+
shaka.polyfill.PatchedMediaKeysApple.install();
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The support in each case would be the following:
|
|
12
|
+
|
|
13
|
+
| |Modern EME |legacy Apple Media Keys|
|
|
14
|
+
|:----------:|:---------:|:---------------------:|
|
|
15
|
+
|src= (CMAF) |**Y** |**Y** |
|
|
16
|
+
|src= (TS) |**Y** |**Y** |
|
|
17
|
+
|MSE (CMAF) |**Y** | - |
|
|
18
|
+
|MSE (TS) | - | - |
|
|
19
|
+
|
|
20
|
+
|
|
4
21
|
Adding FairPlay support involves a bit more work than other key systems.
|
|
5
22
|
|
|
23
|
+
|
|
24
|
+
## Keysystem used in EME
|
|
25
|
+
|
|
26
|
+
Depending on the EME implementation that is being used, the Fairplay keysystem
|
|
27
|
+
varies.
|
|
28
|
+
|
|
29
|
+
For Modern EME:
|
|
30
|
+
```
|
|
31
|
+
com.apple.fps
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For legacy Apple Media Keys:
|
|
35
|
+
```
|
|
36
|
+
com.apple.fps.1_0
|
|
37
|
+
```
|
|
38
|
+
|
|
6
39
|
## Server certificate
|
|
7
40
|
|
|
8
41
|
All FairPlay content requires setting a server certificate. You can either
|
|
@@ -22,6 +55,27 @@ player.configure('drm.advanced.com\\.apple\\.fps\\.serverCertificateUri',
|
|
|
22
55
|
'https://example.com/cert.der');
|
|
23
56
|
```
|
|
24
57
|
|
|
58
|
+
## Content ID
|
|
59
|
+
|
|
60
|
+
Note: Normally only applies to legacy Apple Media Keys but some providers also
|
|
61
|
+
need it in Modern EME.
|
|
62
|
+
|
|
63
|
+
Some FairPlay content use custom signaling for the content ID. The content ID
|
|
64
|
+
is used by the browser to generate the license request. If you don't use the
|
|
65
|
+
default content ID derivation, you need to specify a custom init data transform:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
player.configure('drm.initDataTransform', (initData, initDataType) => {
|
|
69
|
+
if (initDataType != 'skd')
|
|
70
|
+
return initData;
|
|
71
|
+
// 'initData' is a buffer containing an 'skd://' URL as a UTF-8 string.
|
|
72
|
+
const skdUri = shaka.util.StringUtils.fromBytesAutoDetect(initData);
|
|
73
|
+
const contentId = getMyContentId(skdUri);
|
|
74
|
+
const cert = player.drmInfo().serverCertificate;
|
|
75
|
+
return shaka.util.FairPlayUtils.initDataTransform(initData, contentId, cert);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
25
79
|
## License wrapping
|
|
26
80
|
|
|
27
81
|
Some FairPlay servers need to accept the license request in a different format
|
|
@@ -61,3 +115,98 @@ player.getNetworkingEngine().registerResponseFilter((type, response) => {
|
|
|
61
115
|
response.data = shaka.util.Uint8ArrayUtils.fromBase64(responseText).buffer;
|
|
62
116
|
});
|
|
63
117
|
```
|
|
118
|
+
|
|
119
|
+
### Integration with some DRMs providers
|
|
120
|
+
|
|
121
|
+
Note: Some providers support both Modern EME and legacy Apple Media Keys.
|
|
122
|
+
|
|
123
|
+
#### EZDRM (Modern EME)
|
|
124
|
+
|
|
125
|
+
For integration with EZDRM the following can be used:
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
const FairPlayUtils = shaka.util.FairPlayUtils;
|
|
129
|
+
player.getNetworkingEngine()
|
|
130
|
+
.registerRequestFilter(FairPlayUtils.ezdrmFairPlayRequest);
|
|
131
|
+
player.getNetworkingEngine()
|
|
132
|
+
.registerResponseFilter(FairPlayUtils.commonFairPlayResponse);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Note: If the url of the license server has to undergo any transformation
|
|
136
|
+
(eg: add the contentId), you would have to create your filter manually.
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
player.getNetworkingEngine().registerRequestFilter((type, request) => {
|
|
140
|
+
if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const uri = request.uris[0];
|
|
144
|
+
const FairPlayUtils = shaka.util.FairPlayUtils;
|
|
145
|
+
const contentId = FairPlayUtils.defaultGetContentId(request.initData);
|
|
146
|
+
const newUri = uri.replace('^assetId^', contentId);
|
|
147
|
+
request.uris = [newUri];
|
|
148
|
+
request.headers['Content-Type'] = 'application/octet-stream'
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### EZDRM (legacy Apple Media Keys)
|
|
153
|
+
|
|
154
|
+
For integration with EZDRM the following can be used:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
shaka.polyfill.PatchedMediaKeysApple.install();
|
|
158
|
+
const FairPlayUtils = shaka.util.FairPlayUtils;
|
|
159
|
+
player.getNetworkingEngine()
|
|
160
|
+
.registerRequestFilter(FairPlayUtils.ezdrmFairPlayRequest);
|
|
161
|
+
player.getNetworkingEngine()
|
|
162
|
+
.registerResponseFilter(FairPlayUtils.commonFairPlayResponse);
|
|
163
|
+
player.configure('drm.initDataTransform',
|
|
164
|
+
FairPlayUtils.ezdrmInitDataTransform);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Note: If the url of the license server has to undergo any transformation
|
|
168
|
+
(eg: add the contentId), you would have to create your filter manually.
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
player.getNetworkingEngine().registerRequestFilter((type, request) => {
|
|
172
|
+
if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const uri = request.uris[0];
|
|
176
|
+
const FairPlayUtils = shaka.util.FairPlayUtils;
|
|
177
|
+
const contentId = FairPlayUtils.defaultGetContentId(request.initData);
|
|
178
|
+
const newUri = uri.replace('^assetId^', contentId);
|
|
179
|
+
request.uris = [newUri];
|
|
180
|
+
request.headers['Content-Type'] = 'application/octet-stream'
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### Verimatrix (legacy Apple Media Keys)
|
|
185
|
+
|
|
186
|
+
For integration with Verimatrix the following can be used:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
shaka.polyfill.PatchedMediaKeysApple.install();
|
|
190
|
+
const FairPlayUtils = shaka.util.FairPlayUtils;
|
|
191
|
+
player.getNetworkingEngine()
|
|
192
|
+
.registerRequestFilter(FairPlayUtils.verimatrixFairPlayRequest);
|
|
193
|
+
player.getNetworkingEngine()
|
|
194
|
+
.registerResponseFilter(FairPlayUtils.commonFairPlayResponse);
|
|
195
|
+
player.configure('drm.initDataTransform',
|
|
196
|
+
FairPlayUtils.verimatrixInitDataTransform);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Conax (legacy Apple Media Keys)
|
|
200
|
+
|
|
201
|
+
For integration with Conax the following can be used:
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
shaka.polyfill.PatchedMediaKeysApple.install();
|
|
205
|
+
const FairPlayUtils = shaka.util.FairPlayUtils;
|
|
206
|
+
player.getNetworkingEngine()
|
|
207
|
+
.registerRequestFilter(FairPlayUtils.conaxFairPlayRequest);
|
|
208
|
+
player.getNetworkingEngine()
|
|
209
|
+
.registerResponseFilter(FairPlayUtils.commonFairPlayResponse);
|
|
210
|
+
player.configure('drm.initDataTransform',
|
|
211
|
+
FairPlayUtils.conaxInitDataTransform);
|
|
212
|
+
```
|
package/docs/tutorials/ui.md
CHANGED
|
@@ -30,7 +30,7 @@ Set up controls with HTML data attributes:
|
|
|
30
30
|
The data-shaka-player-cast-receiver-id tag allows you to provide a Cast Application ID that
|
|
31
31
|
the cast button will cast to; the value provided here is the sample cast receiver. -->
|
|
32
32
|
<div data-shaka-player-container style="max-width:40em"
|
|
33
|
-
data-shaka-player-cast-receiver-id="
|
|
33
|
+
data-shaka-player-cast-receiver-id="07AEE832">
|
|
34
34
|
<!-- The data-shaka-player tag will make the UI library use this video element.
|
|
35
35
|
If no video is provided, the UI will automatically make one inside the container div. -->
|
|
36
36
|
<video autoplay data-shaka-player id="video" style="width:100%;height:100%"></video>
|
|
@@ -111,7 +111,7 @@ set up a listener for the 'caststatuschanged' events.
|
|
|
111
111
|
<!-- Add a data-shaka-player-cast-receiver-id tag to provide a Cast Application ID that
|
|
112
112
|
the cast button will cast to; the value provided here is the sample cast receiver. -->
|
|
113
113
|
<div data-shaka-player-container style="max-width:40em"
|
|
114
|
-
data-shaka-player-cast-receiver-id="
|
|
114
|
+
data-shaka-player-cast-receiver-id="07AEE832">
|
|
115
115
|
</div>
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -154,7 +154,7 @@ or a `<source>` tag inside it to enable auto loading of the specified content.
|
|
|
154
154
|
|
|
155
155
|
```html
|
|
156
156
|
<div data-shaka-player-container style="max-width:40em"
|
|
157
|
-
data-shaka-player-cast-receiver-id="
|
|
157
|
+
data-shaka-player-cast-receiver-id="07AEE832">
|
|
158
158
|
<!-- The manifest url in the src attribute will be automatically loaded -->
|
|
159
159
|
<video autoplay data-shaka-player id="video" style="width:100%;height:100%"
|
|
160
160
|
src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"></video>
|
|
@@ -165,7 +165,7 @@ or
|
|
|
165
165
|
|
|
166
166
|
```html
|
|
167
167
|
<div data-shaka-player-container style="max-width:40em"
|
|
168
|
-
data-shaka-player-cast-receiver-id="
|
|
168
|
+
data-shaka-player-cast-receiver-id="07AEE832">
|
|
169
169
|
<video autoplay data-shaka-player id="video" style="width:100%;height:100%">
|
|
170
170
|
<!-- The manifest url in the src attribute will be auto loaded -->
|
|
171
171
|
<source src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"/>
|
|
@@ -178,7 +178,7 @@ call to the first one fails.
|
|
|
178
178
|
|
|
179
179
|
```html
|
|
180
180
|
<div data-shaka-player-container style="max-width:40em"
|
|
181
|
-
data-shaka-player-cast-receiver-id="
|
|
181
|
+
data-shaka-player-cast-receiver-id="07AEE832">
|
|
182
182
|
<video autoplay data-shaka-player id="video" style="width:100%;height:100%">
|
|
183
183
|
<!-- Try this first -->
|
|
184
184
|
<source src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"/>
|
|
@@ -222,7 +222,7 @@ const video = controls.getVideo();
|
|
|
222
222
|
// Receiver Compatability.
|
|
223
223
|
ui.configure({
|
|
224
224
|
// Set the castReceiverAppId
|
|
225
|
-
'castReceiverAppId': '
|
|
225
|
+
'castReceiverAppId': '07AEE832',
|
|
226
226
|
// Enable casting to native Android Apps (e.g. Android TV Apps)
|
|
227
227
|
'castAndroidReceiverCompatible': true,
|
|
228
228
|
});
|
|
@@ -103,6 +103,14 @@ shaka.extern.AbrManager = class {
|
|
|
103
103
|
*/
|
|
104
104
|
playbackRateChanged(rate) {}
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Set media element.
|
|
108
|
+
*
|
|
109
|
+
* @param {HTMLMediaElement} mediaElement
|
|
110
|
+
* @exportDoc
|
|
111
|
+
*/
|
|
112
|
+
setMediaElement(mediaElement) {}
|
|
113
|
+
|
|
106
114
|
/**
|
|
107
115
|
* Sets the ABR configuration.
|
|
108
116
|
*
|
|
@@ -244,6 +244,49 @@ shaka.extern.Variant;
|
|
|
244
244
|
shaka.extern.CreateSegmentIndexFunction;
|
|
245
245
|
|
|
246
246
|
|
|
247
|
+
/**
|
|
248
|
+
* @typedef {{
|
|
249
|
+
* method: string,
|
|
250
|
+
* cryptoKey: (webCrypto.CryptoKey|undefined),
|
|
251
|
+
* fetchKey: (shaka.extern.CreateSegmentIndexFunction|undefined),
|
|
252
|
+
* iv: (!Uint8Array|undefined),
|
|
253
|
+
* firstMediaSequenceNumber: number
|
|
254
|
+
* }}
|
|
255
|
+
*
|
|
256
|
+
* @description
|
|
257
|
+
* AES-128 key and iv info from the HLS manifest.
|
|
258
|
+
*
|
|
259
|
+
* @property {string} method
|
|
260
|
+
* The key method defined in the HLS manifest.
|
|
261
|
+
* @property {webCrypto.CryptoKey|undefined} cryptoKey
|
|
262
|
+
* Web crypto key object of the AES-128 CBC key. If unset, the "fetchKey"
|
|
263
|
+
* property should be provided.
|
|
264
|
+
* @property {shaka.extern.FetchCryptoKeysFunction|undefined} fetchKey
|
|
265
|
+
* A function that fetches the key.
|
|
266
|
+
* Should be provided if the "cryptoKey" property is unset.
|
|
267
|
+
* Should update this object in-place, to set "cryptoKey".
|
|
268
|
+
* @property {(!Uint8Array|undefined)} iv
|
|
269
|
+
* The IV in the HLS manifest, if defined. See HLS RFC 8216 Section 5.2 for
|
|
270
|
+
* handling undefined IV.
|
|
271
|
+
* @property {number} firstMediaSequenceNumber
|
|
272
|
+
* The starting Media Sequence Number of the playlist, used when IV is
|
|
273
|
+
* undefined.
|
|
274
|
+
*
|
|
275
|
+
* @exportDoc
|
|
276
|
+
*/
|
|
277
|
+
shaka.extern.HlsAes128Key;
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* A function that fetches the crypto keys for AES-128.
|
|
282
|
+
* Returns a promise that resolves when the keys have been fetched.
|
|
283
|
+
*
|
|
284
|
+
* @typedef {function(): !Promise}
|
|
285
|
+
* @exportDoc
|
|
286
|
+
*/
|
|
287
|
+
shaka.extern.FetchCryptoKeysFunction;
|
|
288
|
+
|
|
289
|
+
|
|
247
290
|
/**
|
|
248
291
|
* @typedef {{
|
|
249
292
|
* id: number,
|
package/externs/shaka/player.js
CHANGED
|
@@ -1000,7 +1000,9 @@ shaka.extern.StreamingConfiguration;
|
|
|
1000
1000
|
* switchInterval: number,
|
|
1001
1001
|
* bandwidthUpgradeTarget: number,
|
|
1002
1002
|
* bandwidthDowngradeTarget: number,
|
|
1003
|
-
* advanced: shaka.extern.AdvancedAbrConfiguration
|
|
1003
|
+
* advanced: shaka.extern.AdvancedAbrConfiguration,
|
|
1004
|
+
* restrictToElementSize: boolean,
|
|
1005
|
+
* ignoreDevicePixelRatio: boolean
|
|
1004
1006
|
* }}
|
|
1005
1007
|
*
|
|
1006
1008
|
* @property {boolean} enabled
|
|
@@ -1029,7 +1031,16 @@ shaka.extern.StreamingConfiguration;
|
|
|
1029
1031
|
* The largest fraction of the estimated bandwidth we should use. We should
|
|
1030
1032
|
* downgrade to avoid this.
|
|
1031
1033
|
* @property {shaka.extern.AdvancedAbrConfiguration} advanced
|
|
1032
|
-
* Advanced ABR configuration
|
|
1034
|
+
* Advanced ABR configuration
|
|
1035
|
+
* @property {boolean} restrictToElementSize
|
|
1036
|
+
* If true, restrict the quality to media element size.
|
|
1037
|
+
* Note: The use of ResizeObserver is required for it to work properly. If
|
|
1038
|
+
* true without ResizeObserver, it behaves as false.
|
|
1039
|
+
* Defaults false.
|
|
1040
|
+
* @property {boolean} ignoreDevicePixelRatio
|
|
1041
|
+
* If true,device pixel ratio is ignored when restricting the quality to
|
|
1042
|
+
* media element size.
|
|
1043
|
+
* Defaults false.
|
|
1033
1044
|
* @exportDoc
|
|
1034
1045
|
*/
|
|
1035
1046
|
shaka.extern.AbrConfiguration;
|
|
@@ -10,6 +10,7 @@ goog.require('goog.asserts');
|
|
|
10
10
|
goog.require('shaka.abr.EwmaBandwidthEstimator');
|
|
11
11
|
goog.require('shaka.log');
|
|
12
12
|
goog.require('shaka.util.StreamUtils');
|
|
13
|
+
goog.require('shaka.util.Timer');
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -85,6 +86,22 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
85
86
|
|
|
86
87
|
/** @private {?shaka.extern.AbrConfiguration} */
|
|
87
88
|
this.config_ = null;
|
|
89
|
+
|
|
90
|
+
/** @private {HTMLMediaElement} */
|
|
91
|
+
this.mediaElement_ = null;
|
|
92
|
+
|
|
93
|
+
/** @private {ResizeObserver} */
|
|
94
|
+
this.resizeObserver_ = null;
|
|
95
|
+
|
|
96
|
+
/** @private {shaka.util.Timer} */
|
|
97
|
+
this.resizeObserverTimer_ = new shaka.util.Timer(() => {
|
|
98
|
+
if (this.config_.restrictToElementSize) {
|
|
99
|
+
const chosenVariant = this.chooseVariant();
|
|
100
|
+
if (chosenVariant) {
|
|
101
|
+
this.switch_(chosenVariant);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
88
105
|
}
|
|
89
106
|
|
|
90
107
|
|
|
@@ -98,6 +115,14 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
98
115
|
this.variants_ = [];
|
|
99
116
|
this.playbackRate_ = 1;
|
|
100
117
|
this.lastTimeChosenMs_ = null;
|
|
118
|
+
this.mediaElement_ = null;
|
|
119
|
+
|
|
120
|
+
if (this.resizeObserver_) {
|
|
121
|
+
this.resizeObserver_.disconnect();
|
|
122
|
+
this.resizeObserver_ = null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.resizeObserverTimer_.stop();
|
|
101
126
|
|
|
102
127
|
// Don't reset |startupComplete_|: if we've left the startup interval, we
|
|
103
128
|
// can start using bandwidth estimates right away after init() is called.
|
|
@@ -120,9 +145,19 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
120
145
|
chooseVariant() {
|
|
121
146
|
const SimpleAbrManager = shaka.abr.SimpleAbrManager;
|
|
122
147
|
|
|
148
|
+
let maxHeight = Infinity;
|
|
149
|
+
let maxWidth = Infinity;
|
|
150
|
+
|
|
151
|
+
if (this.resizeObserver_ && this.config_.restrictToElementSize) {
|
|
152
|
+
const devicePixelRatio =
|
|
153
|
+
this.config_.ignoreDevicePixelRatio ? 1 : window.devicePixelRatio;
|
|
154
|
+
maxHeight = this.mediaElement_.clientWidth * devicePixelRatio;
|
|
155
|
+
maxWidth = this.mediaElement_.clientHeight * devicePixelRatio;
|
|
156
|
+
}
|
|
157
|
+
|
|
123
158
|
// Get sorted Variants.
|
|
124
159
|
let sortedVariants = SimpleAbrManager.filterAndSortVariants_(
|
|
125
|
-
this.config_.restrictions, this.variants_);
|
|
160
|
+
this.config_.restrictions, this.variants_, maxHeight, maxWidth);
|
|
126
161
|
|
|
127
162
|
const defaultBandwidthEstimate = this.getDefaultBandwidth_();
|
|
128
163
|
const currentBandwidth = this.bandwidthEstimator_.getBandwidthEstimate(
|
|
@@ -137,7 +172,8 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
137
172
|
shaka.log.warning('No variants met the ABR restrictions. ' +
|
|
138
173
|
'Choosing a variant by lowest bandwidth.');
|
|
139
174
|
sortedVariants = SimpleAbrManager.filterAndSortVariants_(
|
|
140
|
-
/* restrictions= */ null, this.variants_
|
|
175
|
+
/* restrictions= */ null, this.variants_,
|
|
176
|
+
/* maxHeight= */ Infinity, /* maxWidth= */ Infinity);
|
|
141
177
|
sortedVariants = [sortedVariants[0]];
|
|
142
178
|
}
|
|
143
179
|
|
|
@@ -243,6 +279,28 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
243
279
|
}
|
|
244
280
|
|
|
245
281
|
|
|
282
|
+
/**
|
|
283
|
+
* @override
|
|
284
|
+
* @export
|
|
285
|
+
*/
|
|
286
|
+
setMediaElement(mediaElement) {
|
|
287
|
+
this.mediaElement_ = mediaElement;
|
|
288
|
+
if (this.resizeObserver_) {
|
|
289
|
+
this.resizeObserver_.disconnect();
|
|
290
|
+
this.resizeObserver_ = null;
|
|
291
|
+
}
|
|
292
|
+
if (this.mediaElement_ && 'ResizeObserver' in window) {
|
|
293
|
+
this.resizeObserver_ = new ResizeObserver(() => {
|
|
294
|
+
const SimpleAbrManager = shaka.abr.SimpleAbrManager;
|
|
295
|
+
// Batch up resize changes before checking them.
|
|
296
|
+
this.resizeObserverTimer_.tickAfter(
|
|
297
|
+
/* seconds= */ SimpleAbrManager.RESIZE_OBSERVER_BATCH_TIME);
|
|
298
|
+
});
|
|
299
|
+
this.resizeObserver_.observe(this.mediaElement_);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
246
304
|
/**
|
|
247
305
|
* @override
|
|
248
306
|
* @export
|
|
@@ -320,11 +378,13 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
320
378
|
/**
|
|
321
379
|
* @param {?shaka.extern.Restrictions} restrictions
|
|
322
380
|
* @param {!Array.<shaka.extern.Variant>} variants
|
|
381
|
+
* @param {!number} maxHeight
|
|
382
|
+
* @param {!number} maxWidth
|
|
323
383
|
* @return {!Array.<shaka.extern.Variant>} variants filtered according to
|
|
324
384
|
* |restrictions| and sorted in ascending order of bandwidth.
|
|
325
385
|
* @private
|
|
326
386
|
*/
|
|
327
|
-
static filterAndSortVariants_(restrictions, variants) {
|
|
387
|
+
static filterAndSortVariants_(restrictions, variants, maxHeight, maxWidth) {
|
|
328
388
|
if (restrictions) {
|
|
329
389
|
variants = variants.filter((variant) => {
|
|
330
390
|
// This was already checked in another scope, but the compiler doesn't
|
|
@@ -333,7 +393,7 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
333
393
|
|
|
334
394
|
return shaka.util.StreamUtils.meetsRestrictions(
|
|
335
395
|
variant, restrictions,
|
|
336
|
-
/* maxHwRes= */ {width:
|
|
396
|
+
/* maxHwRes= */ {width: maxWidth, height: maxHeight});
|
|
337
397
|
});
|
|
338
398
|
}
|
|
339
399
|
|
|
@@ -342,3 +402,11 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
342
402
|
});
|
|
343
403
|
}
|
|
344
404
|
};
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* The amount of time, in seconds, we wait to batch up rapid resize changes.
|
|
409
|
+
* This allows us to avoid multiple resize events in most cases.
|
|
410
|
+
* @type {number}
|
|
411
|
+
*/
|
|
412
|
+
shaka.abr.SimpleAbrManager.RESIZE_OBSERVER_BATCH_TIME = 1;
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -37,6 +37,7 @@ goog.require('shaka.util.OperationManager');
|
|
|
37
37
|
goog.require('shaka.util.Pssh');
|
|
38
38
|
goog.require('shaka.util.Timer');
|
|
39
39
|
goog.require('shaka.util.Platform');
|
|
40
|
+
goog.require('shaka.util.Uint8ArrayUtils');
|
|
40
41
|
goog.require('shaka.util.XmlUtils');
|
|
41
42
|
goog.requireType('shaka.hls.Segment');
|
|
42
43
|
|
|
@@ -1644,34 +1645,30 @@ shaka.hls.HlsParser = class {
|
|
|
1644
1645
|
if (method != 'NONE') {
|
|
1645
1646
|
encrypted = true;
|
|
1646
1647
|
|
|
1647
|
-
// We do not support AES-128 encryption with HLS yet. So, do not create
|
|
1648
|
-
// StreamInfo for the playlist encrypted with AES-128.
|
|
1649
|
-
// TODO: Remove the error message once we add support for AES-128.
|
|
1650
1648
|
if (method == 'AES-128') {
|
|
1651
|
-
|
|
1649
|
+
// These keys are handled separately.
|
|
1652
1650
|
this.aesEncrypted_ = true;
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
keyIds.add(keyId);
|
|
1651
|
+
} else {
|
|
1652
|
+
const keyFormat = drmTag.getRequiredAttrValue('KEYFORMAT');
|
|
1653
|
+
const drmParser =
|
|
1654
|
+
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
|
|
1655
|
+
|
|
1656
|
+
const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
|
|
1657
|
+
if (drmInfo) {
|
|
1658
|
+
if (drmInfo.keyIds) {
|
|
1659
|
+
for (const keyId of drmInfo.keyIds) {
|
|
1660
|
+
keyIds.add(keyId);
|
|
1661
|
+
}
|
|
1665
1662
|
}
|
|
1663
|
+
drmInfos.push(drmInfo);
|
|
1664
|
+
} else {
|
|
1665
|
+
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
1666
1666
|
}
|
|
1667
|
-
drmInfos.push(drmInfo);
|
|
1668
|
-
} else {
|
|
1669
|
-
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
1670
1667
|
}
|
|
1671
1668
|
}
|
|
1672
1669
|
}
|
|
1673
1670
|
|
|
1674
|
-
if (encrypted && !drmInfos.length) {
|
|
1671
|
+
if (encrypted && !drmInfos.length && !this.aesEncrypted_) {
|
|
1675
1672
|
throw new shaka.util.Error(
|
|
1676
1673
|
shaka.util.Error.Severity.CRITICAL,
|
|
1677
1674
|
shaka.util.Error.Category.MANIFEST,
|
|
@@ -1688,9 +1685,8 @@ shaka.hls.HlsParser = class {
|
|
|
1688
1685
|
|
|
1689
1686
|
let segments;
|
|
1690
1687
|
try {
|
|
1691
|
-
segments = this.createSegments_(verbatimMediaPlaylistUri,
|
|
1692
|
-
|
|
1693
|
-
codecs);
|
|
1688
|
+
segments = this.createSegments_(verbatimMediaPlaylistUri, playlist, type,
|
|
1689
|
+
mimeType, mediaSequenceToStartTime, mediaVariables, codecs);
|
|
1694
1690
|
} catch (error) {
|
|
1695
1691
|
if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
|
|
1696
1692
|
shaka.log.alwaysWarn('Skipping unsupported HLS stream',
|
|
@@ -1765,6 +1761,78 @@ shaka.hls.HlsParser = class {
|
|
|
1765
1761
|
}
|
|
1766
1762
|
|
|
1767
1763
|
|
|
1764
|
+
/**
|
|
1765
|
+
* @param {!shaka.hls.Tag} drmTag
|
|
1766
|
+
* @param {!shaka.hls.Playlist} playlist
|
|
1767
|
+
* @return {!shaka.extern.HlsAes128Key}
|
|
1768
|
+
* @private
|
|
1769
|
+
*/
|
|
1770
|
+
parseAES128DrmTag_(drmTag, playlist) {
|
|
1771
|
+
// Check if the Web Crypto API is available.
|
|
1772
|
+
if (!window.crypto || !window.crypto.subtle) {
|
|
1773
|
+
shaka.log.alwaysWarn('Web Crypto API is not available to decrypt ' +
|
|
1774
|
+
'AES-128. (Web Crypto only exists in secure origins like https)');
|
|
1775
|
+
throw new shaka.util.Error(
|
|
1776
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1777
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1778
|
+
shaka.util.Error.Code.NO_WEB_CRYPTO_API);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// HLS RFC 8216 Section 5.2:
|
|
1782
|
+
// An EXT-X-KEY tag with a KEYFORMAT of "identity" that does not have an IV
|
|
1783
|
+
// attribute indicates that the Media Sequence Number is to be used as the
|
|
1784
|
+
// IV when decrypting a Media Segment, by putting its big-endian binary
|
|
1785
|
+
// representation into a 16-octet (128-bit) buffer and padding (on the left)
|
|
1786
|
+
// with zeros.
|
|
1787
|
+
let firstMediaSequenceNumber = 0;
|
|
1788
|
+
let iv;
|
|
1789
|
+
const ivHex = drmTag.getAttributeValue('IV', '');
|
|
1790
|
+
if (!ivHex) {
|
|
1791
|
+
// Media Sequence Number will be used as IV.
|
|
1792
|
+
firstMediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
1793
|
+
playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
|
|
1794
|
+
} else {
|
|
1795
|
+
// Exclude 0x at the start of string.
|
|
1796
|
+
iv = shaka.util.Uint8ArrayUtils.fromHex(ivHex.substr(2));
|
|
1797
|
+
if (iv.byteLength != 16) {
|
|
1798
|
+
throw new shaka.util.Error(
|
|
1799
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1800
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1801
|
+
shaka.util.Error.Code.HLS_AES_128_INVALID_IV_LENGTH);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
const keyUri = shaka.hls.Utils.constructAbsoluteUri(
|
|
1806
|
+
playlist.absoluteUri, drmTag.getRequiredAttrValue('URI'));
|
|
1807
|
+
|
|
1808
|
+
const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
|
|
1809
|
+
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
1810
|
+
[keyUri], this.config_.retryParameters);
|
|
1811
|
+
|
|
1812
|
+
const keyInfo = {method: 'AES-128', iv, firstMediaSequenceNumber};
|
|
1813
|
+
|
|
1814
|
+
// Don't download the key object until the segment is parsed, to avoid a
|
|
1815
|
+
// startup delay for long manifests with lots of keys.
|
|
1816
|
+
keyInfo.fetchKey = async () => {
|
|
1817
|
+
const keyResponse = await this.makeNetworkRequest_(request, requestType);
|
|
1818
|
+
|
|
1819
|
+
// keyResponse.status is undefined when URI is "data:text/plain;base64,"
|
|
1820
|
+
if (!keyResponse.data || keyResponse.data.byteLength != 16) {
|
|
1821
|
+
throw new shaka.util.Error(
|
|
1822
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1823
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1824
|
+
shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
keyInfo.cryptoKey = await window.crypto.subtle.importKey(
|
|
1828
|
+
'raw', keyResponse.data, 'AES-CBC', true, ['decrypt']);
|
|
1829
|
+
keyInfo.fetchKey = undefined; // No longer needed.
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1832
|
+
return keyInfo;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
|
|
1768
1836
|
/**
|
|
1769
1837
|
* @param {!shaka.hls.Playlist} playlist
|
|
1770
1838
|
* @private
|
|
@@ -1939,12 +2007,13 @@ shaka.hls.HlsParser = class {
|
|
|
1939
2007
|
* @param {!Map.<string, string>} variables
|
|
1940
2008
|
* @param {string} absoluteMediaPlaylistUri
|
|
1941
2009
|
* @param {string} type
|
|
2010
|
+
* @param {shaka.extern.HlsAes128Key=} hlsAes128Key
|
|
1942
2011
|
* @return {shaka.media.SegmentReference}
|
|
1943
2012
|
* @private
|
|
1944
2013
|
*/
|
|
1945
2014
|
createSegmentReference_(
|
|
1946
2015
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
1947
|
-
variables, absoluteMediaPlaylistUri, type) {
|
|
2016
|
+
variables, absoluteMediaPlaylistUri, type, hlsAes128Key) {
|
|
1948
2017
|
const tags = hlsSegment.tags;
|
|
1949
2018
|
const absoluteSegmentUri = this.variableSubstitution_(
|
|
1950
2019
|
hlsSegment.absoluteUri, variables);
|
|
@@ -2039,6 +2108,8 @@ shaka.hls.HlsParser = class {
|
|
|
2039
2108
|
partialStatus = shaka.media.SegmentReference.Status.MISSING;
|
|
2040
2109
|
}
|
|
2041
2110
|
|
|
2111
|
+
// We do not set the AES-128 key information for partial segments, as we
|
|
2112
|
+
// do not support AES-128 and low-latency at the same time.
|
|
2042
2113
|
const partial = new shaka.media.SegmentReference(
|
|
2043
2114
|
pStartTime,
|
|
2044
2115
|
pEndTime,
|
|
@@ -2120,6 +2191,7 @@ shaka.hls.HlsParser = class {
|
|
|
2120
2191
|
tileDuration,
|
|
2121
2192
|
syncTime,
|
|
2122
2193
|
status,
|
|
2194
|
+
hlsAes128Key,
|
|
2123
2195
|
);
|
|
2124
2196
|
}
|
|
2125
2197
|
|
|
@@ -2186,6 +2258,9 @@ shaka.hls.HlsParser = class {
|
|
|
2186
2258
|
/** @type {shaka.media.InitSegmentReference} */
|
|
2187
2259
|
let initSegmentRef;
|
|
2188
2260
|
|
|
2261
|
+
/** @type {shaka.extern.HlsAes128Key|undefined} */
|
|
2262
|
+
let hlsAes128Key = undefined;
|
|
2263
|
+
|
|
2189
2264
|
// We may need to look at the media itself to determine a segment start
|
|
2190
2265
|
// time.
|
|
2191
2266
|
const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
@@ -2214,6 +2289,14 @@ shaka.hls.HlsParser = class {
|
|
|
2214
2289
|
(i == 0) ? firstStartTime : previousReference.endTime;
|
|
2215
2290
|
position = mediaSequenceNumber + skippedSegments + i;
|
|
2216
2291
|
|
|
2292
|
+
// Apply new AES-128 tags as you see them, keeping a running total.
|
|
2293
|
+
for (const drmTag of item.tags) {
|
|
2294
|
+
if (drmTag.name == 'EXT-X-KEY' &&
|
|
2295
|
+
drmTag.getRequiredAttrValue('METHOD') == 'AES-128') {
|
|
2296
|
+
hlsAes128Key = this.parseAES128DrmTag_(drmTag, playlist);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2217
2300
|
mediaSequenceToStartTime.set(position, startTime);
|
|
2218
2301
|
|
|
2219
2302
|
initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
|
|
@@ -2238,7 +2321,8 @@ shaka.hls.HlsParser = class {
|
|
|
2238
2321
|
startTime,
|
|
2239
2322
|
variables,
|
|
2240
2323
|
playlist.absoluteUri,
|
|
2241
|
-
type
|
|
2324
|
+
type,
|
|
2325
|
+
hlsAes128Key);
|
|
2242
2326
|
previousReference = reference;
|
|
2243
2327
|
|
|
2244
2328
|
if (reference) {
|
|
@@ -2611,6 +2695,14 @@ shaka.hls.HlsParser = class {
|
|
|
2611
2695
|
shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
|
2612
2696
|
}
|
|
2613
2697
|
|
|
2698
|
+
if (shaka.util.Platform.isMediaKeysPolyfilled()) {
|
|
2699
|
+
throw new shaka.util.Error(
|
|
2700
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2701
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2702
|
+
shaka.util.Error.Code
|
|
2703
|
+
.HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED);
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2614
2706
|
/*
|
|
2615
2707
|
* Even if we're not able to construct initData through the HLS tag, adding
|
|
2616
2708
|
* a DRMInfo will allow DRM Engine to request a media key system access
|