shaka-player 3.3.17 → 3.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-and-test.yaml +4 -4
- package/.github/workflows/demo-version-index.yaml +6 -0
- package/.github/workflows/selenium-lab-tests.yaml +78 -17
- package/.github/workflows/update-screenshots.yaml +142 -0
- package/AUTHORS +2 -1
- package/CHANGELOG.md +44 -0
- package/CONTRIBUTORS +4 -1
- package/demo/common/asset.js +15 -0
- package/demo/common/assets.js +4 -2
- package/demo/common/message_ids.js +1 -0
- package/demo/custom.js +19 -0
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/demo/main.js +14 -0
- package/dist/controls.css +1 -1
- package/dist/demo.compiled.debug.js +146 -144
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +146 -144
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +1 -1
- package/dist/locales.js +31 -31
- package/dist/receiver.compiled.debug.js +15 -15
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +15 -15
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +13 -2
- package/dist/shaka-player.compiled.debug.d.ts +13 -2
- package/dist/shaka-player.compiled.debug.js +160 -158
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +338 -335
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +13 -2
- package/dist/shaka-player.ui.debug.d.ts +13 -2
- package/dist/shaka-player.ui.debug.js +224 -222
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +501 -498
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/errors.md +94 -0
- package/docs/tutorials/fairplay.md +2 -2
- package/docs/tutorials/index.json +2 -0
- package/docs/tutorials/widevine-service-certs.md +45 -0
- package/externs/ima.js +9 -0
- package/externs/shaka/net.js +1 -0
- package/externs/shaka/player.js +13 -7
- package/lib/ads/client_side_ad_manager.js +11 -5
- package/lib/dash/dash_parser.js +49 -48
- package/lib/hls/hls_parser.js +2 -0
- package/lib/media/media_source_engine.js +9 -4
- package/lib/media/streaming_engine.js +8 -3
- package/lib/media/transmuxer.js +13 -7
- package/lib/media/video_wrapper.js +3 -3
- package/lib/net/http_fetch_plugin.js +79 -57
- package/lib/net/networking_engine.js +1 -0
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/player.js +19 -2
- package/lib/polyfill/media_capabilities.js +15 -8
- package/lib/polyfill/mediasource.js +7 -2
- package/lib/text/mp4_vtt_parser.js +4 -0
- package/lib/text/text_engine.js +1 -0
- package/lib/text/ui_text_displayer.js +1 -0
- package/lib/text/vtt_text_parser.js +4 -5
- package/lib/util/manifest_parser_utils.js +1 -2
- package/lib/util/mime_utils.js +12 -1
- package/lib/util/periods.js +5 -5
- package/lib/util/stream_utils.js +28 -16
- package/maintained-branches.md +3 -4
- package/package.json +1 -1
- package/test/media/content_workarounds_unit.js +43 -0
- package/test/media/drm_engine_integration.js +4 -17
- package/test/media/streaming_engine_unit.js +6 -1
- package/test/net/networking_engine_unit.js +6 -1
- package/test/player_integration.js +39 -0
- package/test/player_unit.js +54 -0
- package/test/polyfill/media_capabilities_unit.js +20 -23
- package/test/test/assets/text-clip-alt.vtt +63 -0
- package/test/test/util/fake_drm_engine.js +3 -0
- package/test/test/util/simple_fakes.js +3 -0
- package/test/test/util/test_scheme.js +9 -14
- package/test/text/mp4_vtt_parser_unit.js +8 -0
- package/test/text/vtt_text_parser_unit.js +26 -2
- package/test/text/web_vtt_layout_integration.js +0 -3
- package/test/util/mime_utils_unit.js +10 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
The basics for handling native Error and {@link shaka.util.Error} thrown by Shaka.
|
|
4
|
+
|
|
5
|
+
## Listening To and Handling Errors
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const handleError = (error) => {
|
|
9
|
+
if (error instanceof Error) {
|
|
10
|
+
// shaka crashed with an unhandled native error
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (error.severity === shaka.util.Error.Severity.CRITICAL) {
|
|
14
|
+
// handle fatal error, playback can not continue
|
|
15
|
+
} else {
|
|
16
|
+
// handle non-fatal error, playback can continue
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const player = new shaka.Player(video);
|
|
21
|
+
|
|
22
|
+
// handle errors that occur after load
|
|
23
|
+
player.addEventListener('error', handleError);
|
|
24
|
+
|
|
25
|
+
// there are two options for catching errors that occur during load
|
|
26
|
+
|
|
27
|
+
// it's possible to listen directly on the promise
|
|
28
|
+
player.load(url).catch(handleError);
|
|
29
|
+
|
|
30
|
+
// or to use async/await with a try/catch
|
|
31
|
+
try {
|
|
32
|
+
await player.load(url);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
handleError(e);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Custom Handling of Streaming Errors
|
|
39
|
+
|
|
40
|
+
The `streaming.failureCallback` property of {@link shaka.extern.PlayerConfiguration} can be used to add custom handling or error conversion of errors that occur during streaming.
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
player.configure('streaming.failureCallback', (error) => {
|
|
44
|
+
if (error.severity === shaka.util.Error.Severity.CRITICAL) {
|
|
45
|
+
// custom handling of critical error
|
|
46
|
+
// e.g. player.retryStreaming();
|
|
47
|
+
} else {
|
|
48
|
+
// custom handling of recoverable error
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Custom Handling of Retries
|
|
54
|
+
|
|
55
|
+
When configuring retry parameters in Shaka there may be known error codes that should not be retried, or the need to break out of an infinite retry loop in a live context. Retries for failed network requests are not reported as `RECOVERABLE` errors.
|
|
56
|
+
|
|
57
|
+
For example, if a VOD manifest is missing, unlike when a LIVE manifest is missing, it can be expected to not show up and there is no need to retry.
|
|
58
|
+
|
|
59
|
+
This is how to convert a retry into a critical error, see {@link shaka.net.NetworkingEngine.RetryEvent} and {@link shaka.util.Error} to decipher how the event can be parsed:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const nwEngine = player.getNetworkingEngine();
|
|
63
|
+
|
|
64
|
+
const vodManifestNotFoundHandler = (event /* shaka.net.NetworkingEngine.RetryEvent */) => {
|
|
65
|
+
const code = event.error.code;
|
|
66
|
+
const data = event.error.data;
|
|
67
|
+
|
|
68
|
+
if (code === shaka.util.Error.Code.BAD_HTTP_STATUS) {
|
|
69
|
+
if (
|
|
70
|
+
// each type of error has its own data structure (or none at all), tread with care
|
|
71
|
+
Array.isArray(data) &&
|
|
72
|
+
data[1] === 404 &&
|
|
73
|
+
data[4] === shaka.net.NetworkingEngine.RequestType.MANIFEST
|
|
74
|
+
) {
|
|
75
|
+
// Throwing inside a retry callback will immediately stop retries
|
|
76
|
+
throw error;
|
|
77
|
+
|
|
78
|
+
// A proprietary error code can also be thrown
|
|
79
|
+
// throw new shaka.util.Error(
|
|
80
|
+
// shaka.util.Error.Severity.CRITICAL,
|
|
81
|
+
// shaka.util.Error.Category.NETWORK,
|
|
82
|
+
// 'RECOGNIZABLE_ERROR_MESSAGE'
|
|
83
|
+
// );
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
nwEngine.addEventListener('retry', vodManifestNotFoundHandler);
|
|
89
|
+
|
|
90
|
+
player.addEventListener('load'() => {
|
|
91
|
+
nwEngine.removeEventListener('retry', vodManifestNotFoundHandler);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
```
|
|
@@ -25,14 +25,14 @@ is used by the browser to generate the license request. If you don't use the
|
|
|
25
25
|
default content ID derivation, you need to specify a custom init data transform:
|
|
26
26
|
|
|
27
27
|
```js
|
|
28
|
-
player.configure('drm.initDataTransform', (initData, initDataType) => {
|
|
28
|
+
player.configure('drm.initDataTransform', (initData, initDataType, drmInfo) => {
|
|
29
29
|
if (initDataType != 'skd')
|
|
30
30
|
return initData;
|
|
31
31
|
|
|
32
32
|
// 'initData' is a buffer containing an 'skd://' URL as a UTF-8 string.
|
|
33
33
|
const skdUri = shaka.util.StringUtils.fromBytesAutoDetect(initData);
|
|
34
34
|
const contentId = getMyContentId(skdUri);
|
|
35
|
-
const cert =
|
|
35
|
+
const cert = drmInfo.serverCertificate;
|
|
36
36
|
return shaka.util.FairPlayUtils.initDataTransform(initData, contentId, cert);
|
|
37
37
|
});
|
|
38
38
|
```
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
{ "license-wrapping": { "title": "License Wrapping" } },
|
|
10
10
|
{ "ui": { "title": "UI Library" } },
|
|
11
11
|
{ "ui-customization": { "title": "Configuring the UI" } },
|
|
12
|
+
{ "errors": { "title": "Error Handling" } },
|
|
12
13
|
{ "a11y": { "title": "Creating accessible buttons" } },
|
|
13
14
|
{ "ad_monetization": { "title": "Serving ads with our IMA SDK integration" } },
|
|
14
15
|
{ "plugins": { "title": "Plugins and Customizing the Build" } },
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
{ "architecture": { "title": "Architecture Diagrams" } },
|
|
17
18
|
{ "service-worker": { "title": "Service Worker Caching" } },
|
|
18
19
|
{ "offline": { "title": "Offline Storage and Playback" } },
|
|
20
|
+
{ "widevine-service-certs": { "title": "Widevine Service Certificates" } },
|
|
19
21
|
{ "fairplay": { "title": "FairPlay support" } },
|
|
20
22
|
{ "application-level-redirects": { "title": "Application-Level Redirects" } },
|
|
21
23
|
{ "blob-url": { "title": "Blob URL" } },
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Widevine Service Certificates
|
|
2
|
+
|
|
3
|
+
With Widevine, you can eliminate one round-trip to your license server or proxy
|
|
4
|
+
per session by preloading a Widevine service certificate. You can either provide
|
|
5
|
+
it directly or set a URI and let Shaka fetch it for you. Do this before calling
|
|
6
|
+
`player.load()`. The values will persist across multiple calls to `load()` on
|
|
7
|
+
the same `shaka.Player` instance.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// This is an example of loading the certificate from your site at runtime.
|
|
11
|
+
// You could also choose to bundle it into your JavaScript as a Uint8Array.
|
|
12
|
+
const req = await fetch('https://example.com/service.cert');
|
|
13
|
+
const cert = new Uint8Array(await req.arrayBuffer());
|
|
14
|
+
|
|
15
|
+
// This is the short form for configuration of a certificate:
|
|
16
|
+
player.configure('drm.advanced.com\\.widevine\\.alpha.serverCertificate',
|
|
17
|
+
cert);
|
|
18
|
+
|
|
19
|
+
// This is the long form:
|
|
20
|
+
player.configure({
|
|
21
|
+
drm: {
|
|
22
|
+
advanced: {
|
|
23
|
+
'com.widevine.alpha': {
|
|
24
|
+
'serverCertificate': cert,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// This is the short form for configuration of a certificate URI:
|
|
32
|
+
player.configure('drm.advanced.com\\.widevine\\.alpha.serverCertificateUri',
|
|
33
|
+
'https://example.com/service.cert');
|
|
34
|
+
|
|
35
|
+
// This is the long form:
|
|
36
|
+
player.configure({
|
|
37
|
+
drm: {
|
|
38
|
+
advanced: {
|
|
39
|
+
'com.widevine.alpha': {
|
|
40
|
+
'serverCertificateUri': 'https://example.com/service.cert',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
package/externs/ima.js
CHANGED
|
@@ -501,6 +501,15 @@ google.ima.dai.api.StreamRequest.prototype.streamActivityMonitorId;
|
|
|
501
501
|
google.ima.dai.api.StreamRequest.prototype.format;
|
|
502
502
|
|
|
503
503
|
|
|
504
|
+
/**
|
|
505
|
+
* @enum {string}
|
|
506
|
+
*/
|
|
507
|
+
google.ima.dai.api.StreamRequest.StreamFormat = {
|
|
508
|
+
DASH: 'dash',
|
|
509
|
+
HLS: 'hls',
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
|
|
504
513
|
/** @const */
|
|
505
514
|
google.ima.dai.api.VODStreamRequest =
|
|
506
515
|
class extends google.ima.dai.api.StreamRequest {};
|
package/externs/shaka/net.js
CHANGED
package/externs/shaka/player.js
CHANGED
|
@@ -613,9 +613,7 @@ shaka.extern.AdvancedDrmConfiguration;
|
|
|
613
613
|
* clearKeys: !Object.<string, string>,
|
|
614
614
|
* delayLicenseRequestUntilPlayed: boolean,
|
|
615
615
|
* advanced: Object.<string, shaka.extern.AdvancedDrmConfiguration>,
|
|
616
|
-
* initDataTransform:
|
|
617
|
-
* ((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)|
|
|
618
|
-
* undefined),
|
|
616
|
+
* initDataTransform:(shaka.extern.InitDataTransform|undefined),
|
|
619
617
|
* logLicenseExchange: boolean,
|
|
620
618
|
* updateExpirationTime: number,
|
|
621
619
|
* preferredKeySystems: !Array.<string>
|
|
@@ -639,10 +637,7 @@ shaka.extern.AdvancedDrmConfiguration;
|
|
|
639
637
|
* <i>Optional.</i> <br>
|
|
640
638
|
* A dictionary which maps key system IDs to advanced DRM configuration for
|
|
641
639
|
* those key systems.
|
|
642
|
-
* @property
|
|
643
|
-
* {((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)|
|
|
644
|
-
* undefined)}
|
|
645
|
-
* initDataTransform
|
|
640
|
+
* @property {shaka.extern.InitDataTransform|undefined} initDataTransform
|
|
646
641
|
* <i>Optional.</i><br>
|
|
647
642
|
* If given, this function is called with the init data from the
|
|
648
643
|
* manifest/media and should return the (possibly transformed) init data to
|
|
@@ -664,6 +659,17 @@ shaka.extern.AdvancedDrmConfiguration;
|
|
|
664
659
|
*/
|
|
665
660
|
shaka.extern.DrmConfiguration;
|
|
666
661
|
|
|
662
|
+
/**
|
|
663
|
+
* @typedef {function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array}
|
|
664
|
+
*
|
|
665
|
+
* @description
|
|
666
|
+
* A callback function to handle custom content ID signaling for FairPlay
|
|
667
|
+
* content.
|
|
668
|
+
*
|
|
669
|
+
* @exportDoc
|
|
670
|
+
*/
|
|
671
|
+
shaka.extern.InitDataTransform;
|
|
672
|
+
|
|
667
673
|
|
|
668
674
|
/**
|
|
669
675
|
* @typedef {{
|
|
@@ -79,9 +79,9 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
// Notify the SDK when the video has ended, so it can play post-roll ads.
|
|
82
|
-
this.video_
|
|
82
|
+
this.eventManager_.listen(this.video_, 'ended', () => {
|
|
83
83
|
this.adsLoader_.contentComplete();
|
|
84
|
-
};
|
|
84
|
+
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -212,9 +212,9 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
212
212
|
// seen the ads actually play until requestAds() is called.
|
|
213
213
|
// Note: We listen for a play event to avoid autoplay issues that might
|
|
214
214
|
// crash IMA.
|
|
215
|
-
this.
|
|
215
|
+
this.eventManager_.listenOnce(this.video_, 'play', () => {
|
|
216
216
|
this.imaAdsManager_.start();
|
|
217
|
-
}
|
|
217
|
+
});
|
|
218
218
|
} catch (adError) {
|
|
219
219
|
// If there was a problem with the VAST response,
|
|
220
220
|
// we we won't be getting an ad. Hide ad UI if we showed it already
|
|
@@ -402,6 +402,10 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
402
402
|
if (this.ad_.isLinear()) {
|
|
403
403
|
this.adContainer_.setAttribute('ad-active', 'true');
|
|
404
404
|
this.video_.pause();
|
|
405
|
+
this.ad_.setVolume(this.video_.volume);
|
|
406
|
+
if (this.video_.muted) {
|
|
407
|
+
this.ad_.setMuted(true);
|
|
408
|
+
}
|
|
405
409
|
}
|
|
406
410
|
}
|
|
407
411
|
|
|
@@ -414,7 +418,9 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
414
418
|
(new Map()).set('originalEvent', e)));
|
|
415
419
|
if (this.ad_ && this.ad_.isLinear()) {
|
|
416
420
|
this.adContainer_.removeAttribute('ad-active');
|
|
417
|
-
this.video_.
|
|
421
|
+
if (!this.video_.ended) {
|
|
422
|
+
this.video_.play();
|
|
423
|
+
}
|
|
418
424
|
}
|
|
419
425
|
}
|
|
420
426
|
};
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -1278,55 +1278,56 @@ shaka.dash.DashParser = class {
|
|
|
1278
1278
|
context.period.id + ',' + context.representation.id : '';
|
|
1279
1279
|
|
|
1280
1280
|
/** @type {shaka.extern.Stream} */
|
|
1281
|
-
|
|
1282
|
-
id: this.globalId_++,
|
|
1283
|
-
originalId: context.representation.id,
|
|
1284
|
-
createSegmentIndex: async () => {
|
|
1285
|
-
// If we have a stream with the same context id stored in the map
|
|
1286
|
-
// that has no segmentIndex, we should set the segmentIndex for it.
|
|
1287
|
-
const storedInMap = contextId && context.dynamic &&
|
|
1288
|
-
this.streamMap_[contextId];
|
|
1289
|
-
|
|
1290
|
-
const currentStream = storedInMap ? this.streamMap_[contextId] : stream;
|
|
1291
|
-
if (!currentStream.segmentIndex) {
|
|
1292
|
-
currentStream.segmentIndex = await streamInfo.generateSegmentIndex();
|
|
1293
|
-
}
|
|
1294
|
-
},
|
|
1281
|
+
let stream;
|
|
1295
1282
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1283
|
+
if (contextId && this.streamMap_[contextId]) {
|
|
1284
|
+
stream = this.streamMap_[contextId];
|
|
1285
|
+
} else {
|
|
1286
|
+
stream = {
|
|
1287
|
+
id: this.globalId_++,
|
|
1288
|
+
originalId: context.representation.id,
|
|
1289
|
+
createSegmentIndex: () => Promise.resolve(),
|
|
1290
|
+
closeSegmentIndex: () => {
|
|
1291
|
+
if (stream.segmentIndex) {
|
|
1292
|
+
stream.segmentIndex.release();
|
|
1293
|
+
stream.segmentIndex = null;
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
segmentIndex: null,
|
|
1297
|
+
mimeType: context.representation.mimeType,
|
|
1298
|
+
codecs: context.representation.codecs,
|
|
1299
|
+
frameRate: context.representation.frameRate,
|
|
1300
|
+
pixelAspectRatio: context.representation.pixelAspectRatio,
|
|
1301
|
+
bandwidth: context.bandwidth,
|
|
1302
|
+
width: context.representation.width,
|
|
1303
|
+
height: context.representation.height,
|
|
1304
|
+
kind,
|
|
1305
|
+
encrypted: contentProtection.drmInfos.length > 0,
|
|
1306
|
+
drmInfos: contentProtection.drmInfos,
|
|
1307
|
+
keyIds,
|
|
1308
|
+
language,
|
|
1309
|
+
label,
|
|
1310
|
+
type: context.adaptationSet.contentType,
|
|
1311
|
+
primary: isPrimary,
|
|
1312
|
+
trickModeVideo: null,
|
|
1313
|
+
emsgSchemeIdUris:
|
|
1314
|
+
context.representation.emsgSchemeIdUris,
|
|
1315
|
+
roles,
|
|
1316
|
+
forced,
|
|
1317
|
+
channelsCount: context.representation.numChannels,
|
|
1318
|
+
audioSamplingRate: context.representation.audioSamplingRate,
|
|
1319
|
+
spatialAudio,
|
|
1320
|
+
closedCaptions,
|
|
1321
|
+
hdr,
|
|
1322
|
+
tilesLayout,
|
|
1323
|
+
matchedStreams: [],
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
stream.createSegmentIndex = async () => {
|
|
1328
|
+
if (!stream.segmentIndex) {
|
|
1329
|
+
stream.segmentIndex = await streamInfo.generateSegmentIndex();
|
|
1330
|
+
}
|
|
1330
1331
|
};
|
|
1331
1332
|
|
|
1332
1333
|
if (contextId && context.dynamic && !this.streamMap_[contextId]) {
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -2990,6 +2990,7 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
2990
2990
|
'cmfa': 'audio/mp4',
|
|
2991
2991
|
// MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
|
|
2992
2992
|
'ts': 'video/mp2t',
|
|
2993
|
+
'tsa': 'video/mp2t',
|
|
2993
2994
|
|
|
2994
2995
|
// Raw formats:
|
|
2995
2996
|
'aac': 'audio/aac',
|
|
@@ -3027,6 +3028,7 @@ shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
3027
3028
|
'm4f': 'video/mp4',
|
|
3028
3029
|
'cmfv': 'video/mp4',
|
|
3029
3030
|
'ts': 'video/mp2t',
|
|
3031
|
+
'tsv': 'video/mp2t',
|
|
3030
3032
|
};
|
|
3031
3033
|
|
|
3032
3034
|
|
|
@@ -182,6 +182,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
182
182
|
'audio/mp4; codecs="ec-3"',
|
|
183
183
|
'audio/mp4; codecs="opus"',
|
|
184
184
|
'audio/mp4; codecs="flac"',
|
|
185
|
+
'audio/mp4; codecs="dtsc"', // DTS Digital Surround
|
|
186
|
+
'audio/mp4; codecs="dtse"', // DTS Express
|
|
187
|
+
'audio/mp4; codecs="dtsx"', // DTS:X
|
|
185
188
|
// WebM types
|
|
186
189
|
'video/webm; codecs="vp8"',
|
|
187
190
|
'video/webm; codecs="vp9"',
|
|
@@ -515,7 +518,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
515
518
|
// For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
|
|
516
519
|
// the video stream, so textEngine may not have been initialized.
|
|
517
520
|
if (!this.textEngine_) {
|
|
518
|
-
this.reinitText(
|
|
521
|
+
this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE);
|
|
519
522
|
}
|
|
520
523
|
|
|
521
524
|
if (transmuxedData.metadata) {
|
|
@@ -542,7 +545,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
542
545
|
contentType, () => this.append_(contentType, transmuxedSegment));
|
|
543
546
|
} else if (hasClosedCaptions) {
|
|
544
547
|
if (!this.textEngine_) {
|
|
545
|
-
this.reinitText(
|
|
548
|
+
this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE);
|
|
546
549
|
}
|
|
547
550
|
// If it is the init segment for closed captions, initialize the closed
|
|
548
551
|
// caption parser.
|
|
@@ -711,8 +714,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
711
714
|
async endOfStream(reason) {
|
|
712
715
|
await this.enqueueBlockingOperation_(() => {
|
|
713
716
|
// If endOfStream() has already been called on the media source,
|
|
714
|
-
// don't call it again.
|
|
715
|
-
|
|
717
|
+
// don't call it again. Also do not call if readyState is
|
|
718
|
+
// 'closed' (not attached to video element) since it is not a
|
|
719
|
+
// valid operation.
|
|
720
|
+
if (this.ended() || this.mediaSource_.readyState === 'closed') {
|
|
716
721
|
return;
|
|
717
722
|
}
|
|
718
723
|
// Tizen won't let us pass undefined, but it will let us omit the
|
|
@@ -77,6 +77,9 @@ shaka.media.StreamingEngine = class {
|
|
|
77
77
|
/** @private {?shaka.extern.Stream} */
|
|
78
78
|
this.currentTextStream_ = null;
|
|
79
79
|
|
|
80
|
+
/** @private {number} */
|
|
81
|
+
this.textStreamSequenceId_ = 0;
|
|
82
|
+
|
|
80
83
|
/**
|
|
81
84
|
* Maps a content type, e.g., 'audio', 'video', or 'text', to a MediaState.
|
|
82
85
|
*
|
|
@@ -220,6 +223,8 @@ shaka.media.StreamingEngine = class {
|
|
|
220
223
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
221
224
|
goog.asserts.assert(!this.mediaStates_.has(ContentType.TEXT),
|
|
222
225
|
'Should not call loadNewTextStream_ while streaming text!');
|
|
226
|
+
this.textStreamSequenceId_++;
|
|
227
|
+
const currentSequenceId = this.textStreamSequenceId_;
|
|
223
228
|
|
|
224
229
|
try {
|
|
225
230
|
// Clear MediaSource's buffered text, so that the new text stream will
|
|
@@ -241,7 +246,7 @@ shaka.media.StreamingEngine = class {
|
|
|
241
246
|
const streamText =
|
|
242
247
|
textDisplayer.isTextVisible() || this.config_.alwaysStreamText;
|
|
243
248
|
|
|
244
|
-
if (streamText) {
|
|
249
|
+
if (streamText && (this.textStreamSequenceId_ == currentSequenceId)) {
|
|
245
250
|
const state = this.createMediaState_(stream);
|
|
246
251
|
this.mediaStates_.set(ContentType.TEXT, state);
|
|
247
252
|
this.scheduleUpdate_(state, 0);
|
|
@@ -1488,10 +1493,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1488
1493
|
'waiting for another stream to recover...');
|
|
1489
1494
|
}
|
|
1490
1495
|
|
|
1491
|
-
// QuotaExceededError gets thrown if
|
|
1496
|
+
// QuotaExceededError gets thrown if eviction didn't help to make room
|
|
1492
1497
|
// for a segment. We want to wait for a while (4 seconds is just an
|
|
1493
1498
|
// arbitrary number) before updating to give the playhead a chance to
|
|
1494
|
-
// advance, so we don't
|
|
1499
|
+
// advance, so we don't immediately throw again.
|
|
1495
1500
|
this.scheduleUpdate_(mediaState, 4);
|
|
1496
1501
|
}
|
|
1497
1502
|
|
package/lib/media/transmuxer.js
CHANGED
|
@@ -29,9 +29,7 @@ shaka.media.Transmuxer = class {
|
|
|
29
29
|
this.muxjs_ = shaka.dependencies.muxjs();
|
|
30
30
|
|
|
31
31
|
/** @private {muxjs.mp4.Transmuxer} */
|
|
32
|
-
this.muxTransmuxer_ =
|
|
33
|
-
'keepOriginalTimestamps': true,
|
|
34
|
-
});
|
|
32
|
+
this.muxTransmuxer_ = null;
|
|
35
33
|
|
|
36
34
|
/** @private {shaka.util.PublicPromise} */
|
|
37
35
|
this.transmuxPromise_ = null;
|
|
@@ -48,16 +46,22 @@ shaka.media.Transmuxer = class {
|
|
|
48
46
|
/** @private {boolean} */
|
|
49
47
|
this.isTransmuxing_ = false;
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
if (this.muxjs_) {
|
|
50
|
+
this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
|
|
51
|
+
'keepOriginalTimestamps': true,
|
|
52
|
+
});
|
|
53
|
+
this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
|
|
54
|
+
this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
|
|
55
|
+
}
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
57
59
|
* @override
|
|
58
60
|
*/
|
|
59
61
|
destroy() {
|
|
60
|
-
this.muxTransmuxer_
|
|
62
|
+
if (this.muxTransmuxer_) {
|
|
63
|
+
this.muxTransmuxer_.dispose();
|
|
64
|
+
}
|
|
61
65
|
this.muxTransmuxer_ = null;
|
|
62
66
|
return Promise.resolve();
|
|
63
67
|
}
|
|
@@ -156,6 +160,8 @@ shaka.media.Transmuxer = class {
|
|
|
156
160
|
* metadata: !Array.<!Object>}>}
|
|
157
161
|
*/
|
|
158
162
|
transmux(data) {
|
|
163
|
+
goog.asserts.assert(this.muxTransmuxer_,
|
|
164
|
+
'mux.js should be available.');
|
|
159
165
|
goog.asserts.assert(!this.isTransmuxing_,
|
|
160
166
|
'No transmuxing should be in progress.');
|
|
161
167
|
this.isTransmuxing_ = true;
|
|
@@ -135,15 +135,15 @@ shaka.media.VideoWrapper = class {
|
|
|
135
135
|
// calling |Player.load|, meaning that |currentTime| is more meaningful than
|
|
136
136
|
// |startTime|.
|
|
137
137
|
//
|
|
138
|
-
// Seeking to the current time is a work around for Issue 1298
|
|
139
|
-
// do this, the video may get stuck and not play.
|
|
138
|
+
// Seeking to the current time is a work around for Issue 1298 and 4888.
|
|
139
|
+
// If we don't do this, the video may get stuck and not play.
|
|
140
140
|
//
|
|
141
141
|
// TODO: Need further investigation why it happens. Before and after
|
|
142
142
|
// setting the current time, video.readyState is 1, video.paused is true,
|
|
143
143
|
// and video.buffered's TimeRanges length is 0.
|
|
144
144
|
// See: https://github.com/shaka-project/shaka-player/issues/1298
|
|
145
145
|
this.mover_.moveTo(
|
|
146
|
-
this.video_.currentTime == 0 ?
|
|
146
|
+
(!this.video_.currentTime || this.video_.currentTime == 0) ?
|
|
147
147
|
startTime :
|
|
148
148
|
this.video_.currentTime);
|
|
149
149
|
}
|