shaka-player 4.9.32 → 4.9.34
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/dist/deps.js +6 -5
- package/dist/locales.js +36 -36
- package/dist/shaka-player.compiled.d.ts +11 -3
- package/dist/shaka-player.compiled.debug.d.ts +11 -3
- package/dist/shaka-player.compiled.debug.externs.js +8 -5
- package/dist/shaka-player.compiled.debug.js +453 -440
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +8 -5
- package/dist/shaka-player.compiled.js +1281 -1271
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +11 -3
- package/dist/shaka-player.ui.debug.d.ts +11 -3
- package/dist/shaka-player.ui.debug.externs.js +8 -5
- package/dist/shaka-player.ui.debug.js +483 -470
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +8 -5
- package/dist/shaka-player.ui.js +1478 -1468
- package/dist/shaka-player.ui.map +4 -4
- package/externs/managedmediasource.js +6 -0
- package/externs/shaka/codecs.js +21 -0
- package/externs/shaka/offline.js +4 -0
- package/lib/cast/cast_utils.js +1 -0
- package/lib/hls/hls_parser.js +26 -5
- package/lib/media/drm_engine.js +78 -18
- package/lib/media/media_source_capabilities.js +3 -2
- package/lib/media/media_source_engine.js +117 -36
- package/lib/media/segment_utils.js +57 -47
- package/lib/offline/download_info.js +5 -0
- package/lib/offline/download_manager.js +54 -0
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/offline/storage.js +11 -2
- package/lib/player.js +89 -53
- package/lib/polyfill/media_capabilities.js +1 -1
- package/lib/text/simple_text_displayer.js +9 -1
- package/lib/text/ui_text_displayer.js +2 -14
- package/lib/text/vtt_text_parser.js +9 -4
- package/lib/transmuxer/aac_transmuxer.js +11 -3
- package/lib/transmuxer/ac3_transmuxer.js +11 -3
- package/lib/transmuxer/ec3_transmuxer.js +11 -3
- package/lib/transmuxer/h264.js +143 -70
- package/lib/transmuxer/mp3_transmuxer.js +11 -3
- package/lib/transmuxer/ts_transmuxer.js +68 -28
- package/lib/util/abortable_operation.js +3 -0
- package/lib/util/drm_utils.js +20 -0
- package/lib/util/functional.js +27 -0
- package/lib/util/mime_utils.js +2 -0
- package/lib/util/number_utils.js +37 -0
- package/lib/util/object_utils.js +83 -0
- package/lib/util/platform.js +48 -40
- package/lib/util/stream_utils.js +27 -106
- package/lib/util/ts_parser.js +111 -85
- package/package.json +2 -2
- package/ui/controls.js +4 -0
- package/ui/fast_forward_button.js +7 -0
- package/ui/play_button.js +1 -1
- package/ui/rewind_button.js +7 -0
package/externs/shaka/codecs.js
CHANGED
|
@@ -27,6 +27,27 @@
|
|
|
27
27
|
shaka.extern.MPEG_PES;
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {{
|
|
32
|
+
* data: !Uint8Array,
|
|
33
|
+
* frame: boolean,
|
|
34
|
+
* isKeyframe: boolean,
|
|
35
|
+
* pts: ?number,
|
|
36
|
+
* dts: ?number,
|
|
37
|
+
* nalus: !Array.<!shaka.extern.VideoNalu>
|
|
38
|
+
* }}
|
|
39
|
+
*
|
|
40
|
+
* @summary VideoSample.
|
|
41
|
+
* @property {!Uint8Array} data
|
|
42
|
+
* @property {boolean} frame
|
|
43
|
+
* @property {boolean} isKeyframe
|
|
44
|
+
* @property {?number} pts
|
|
45
|
+
* @property {?number} dts
|
|
46
|
+
* @property {!Array.<!shaka.extern.VideoNalu>} nalus
|
|
47
|
+
*/
|
|
48
|
+
shaka.extern.VideoSample;
|
|
49
|
+
|
|
50
|
+
|
|
30
51
|
/**
|
|
31
52
|
* @typedef {{
|
|
32
53
|
* data: !Uint8Array,
|
package/externs/shaka/offline.js
CHANGED
|
@@ -141,6 +141,7 @@ shaka.extern.ManifestDB;
|
|
|
141
141
|
* spatialAudio: boolean,
|
|
142
142
|
* closedCaptions: Map.<string, string>,
|
|
143
143
|
* tilesLayout: (string|undefined),
|
|
144
|
+
* mssPrivateData: (shaka.extern.MssPrivateData|undefined),
|
|
144
145
|
* external: boolean,
|
|
145
146
|
* fastSwitching: boolean
|
|
146
147
|
* }}
|
|
@@ -213,6 +214,9 @@ shaka.extern.ManifestDB;
|
|
|
213
214
|
* The value is a grid-item-dimension consisting of two positive decimal
|
|
214
215
|
* integers in the format: column-x-row ('4x3'). It describes the arrangement
|
|
215
216
|
* of Images in a Grid. The minimum valid LAYOUT is '1x1'.
|
|
217
|
+
* @property {(shaka.extern.MssPrivateData|undefined)} mssPrivateData
|
|
218
|
+
* <i>Microsoft Smooth Streaming only.</i> <br>
|
|
219
|
+
* Private MSS data that is necessary to be able to do transmuxing.
|
|
216
220
|
* @property {boolean} external
|
|
217
221
|
* Indicate if the stream was added externally.
|
|
218
222
|
* Eg: external text tracks.
|
package/lib/cast/cast_utils.js
CHANGED
package/lib/hls/hls_parser.js
CHANGED
|
@@ -1012,8 +1012,8 @@ shaka.hls.HlsParser = class {
|
|
|
1012
1012
|
}
|
|
1013
1013
|
|
|
1014
1014
|
if (extension == 'ts' || contentMimeType == 'video/mp2t') {
|
|
1015
|
-
const basicInfo =
|
|
1016
|
-
|
|
1015
|
+
const basicInfo = shaka.media.SegmentUtils.getBasicInfoFromTs(
|
|
1016
|
+
response.data, this.config_.disableAudio, this.config_.disableVideo);
|
|
1017
1017
|
if (basicInfo) {
|
|
1018
1018
|
return basicInfo;
|
|
1019
1019
|
}
|
|
@@ -2582,12 +2582,23 @@ shaka.hls.HlsParser = class {
|
|
|
2582
2582
|
}
|
|
2583
2583
|
}
|
|
2584
2584
|
this.notifySegmentsForStreams_(streamInfos.map((s) => s.stream));
|
|
2585
|
-
|
|
2585
|
+
|
|
2586
|
+
const activeStreamInfos = Array.from(this.uriToStreamInfosMap_.values())
|
|
2587
|
+
.filter((s) => s.stream.segmentIndex);
|
|
2588
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2589
|
+
const hasAudio =
|
|
2590
|
+
activeStreamInfos.some((s) => s.stream.type == ContentType.AUDIO);
|
|
2591
|
+
const hasVideo =
|
|
2592
|
+
activeStreamInfos.some((s) => s.stream.type == ContentType.VIDEO);
|
|
2593
|
+
|
|
2594
|
+
const liveWithNoProgramDateTime =
|
|
2586
2595
|
this.isLive_() && !this.usesProgramDateTime_;
|
|
2596
|
+
const vodWithOnlyAudioOrVideo = !this.isLive_() &&
|
|
2597
|
+
this.usesProgramDateTime_ && !(hasAudio && hasVideo);
|
|
2587
2598
|
if (this.config_.hls.ignoreManifestProgramDateTime ||
|
|
2588
|
-
|
|
2599
|
+
liveWithNoProgramDateTime || vodWithOnlyAudioOrVideo) {
|
|
2589
2600
|
this.syncStreamsWithSequenceNumber_(
|
|
2590
|
-
streamInfos,
|
|
2601
|
+
streamInfos, liveWithNoProgramDateTime);
|
|
2591
2602
|
} else {
|
|
2592
2603
|
this.syncStreamsWithProgramDateTime_(streamInfos);
|
|
2593
2604
|
if (this.config_.hls.ignoreManifestProgramDateTimeForTypes.length > 0) {
|
|
@@ -2682,6 +2693,16 @@ shaka.hls.HlsParser = class {
|
|
|
2682
2693
|
|
|
2683
2694
|
if (!mimeType) {
|
|
2684
2695
|
mimeType = await this.guessMimeType_(type, codecs, segments);
|
|
2696
|
+
|
|
2697
|
+
// Some manifests don't say what text codec they use, this is a problem
|
|
2698
|
+
// if the cmft extension is used because we identify the mimeType as
|
|
2699
|
+
// application/mp4. In this case if we don't detect initialization
|
|
2700
|
+
// segments, we assume that the mimeType is text/vtt.
|
|
2701
|
+
if (type == shaka.util.ManifestParserUtils.ContentType.TEXT &&
|
|
2702
|
+
!codecs && mimeType == 'application/mp4' &&
|
|
2703
|
+
segments[0] && !segments[0].initSegmentReference) {
|
|
2704
|
+
mimeType = 'text/vtt';
|
|
2705
|
+
}
|
|
2685
2706
|
}
|
|
2686
2707
|
|
|
2687
2708
|
const {drmInfos, keyIds, encrypted, aesEncrypted} =
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -15,6 +15,7 @@ goog.require('shaka.util.DrmUtils');
|
|
|
15
15
|
goog.require('shaka.util.Error');
|
|
16
16
|
goog.require('shaka.util.EventManager');
|
|
17
17
|
goog.require('shaka.util.FakeEvent');
|
|
18
|
+
goog.require('shaka.util.Functional');
|
|
18
19
|
goog.require('shaka.util.IDestroyable');
|
|
19
20
|
goog.require('shaka.util.Iterables');
|
|
20
21
|
goog.require('shaka.util.ManifestParserUtils');
|
|
@@ -1419,6 +1420,11 @@ shaka.media.DrmEngine = class {
|
|
|
1419
1420
|
}
|
|
1420
1421
|
// NOTE: allowCrossSiteCredentials can be set in a request filter.
|
|
1421
1422
|
|
|
1423
|
+
if (shaka.util.DrmUtils.isClearKeySystem(
|
|
1424
|
+
this.currentDrmInfo_.keySystem)) {
|
|
1425
|
+
this.fixClearKeyRequest_(request, this.currentDrmInfo_);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1422
1428
|
if (shaka.util.DrmUtils.isPlayReadyKeySystem(
|
|
1423
1429
|
this.currentDrmInfo_.keySystem)) {
|
|
1424
1430
|
this.unpackPlayReadyRequest_(request);
|
|
@@ -1569,6 +1575,31 @@ shaka.media.DrmEngine = class {
|
|
|
1569
1575
|
/** @type{string} */(shaka.util.TXml.getTextContents(challenge)));
|
|
1570
1576
|
}
|
|
1571
1577
|
|
|
1578
|
+
/**
|
|
1579
|
+
* Some old ClearKey CDMs don't include the type in the body request.
|
|
1580
|
+
*
|
|
1581
|
+
* @param {shaka.extern.Request} request
|
|
1582
|
+
* @param {shaka.extern.DrmInfo} drmInfo
|
|
1583
|
+
* @private
|
|
1584
|
+
*/
|
|
1585
|
+
fixClearKeyRequest_(request, drmInfo) {
|
|
1586
|
+
try {
|
|
1587
|
+
const body = shaka.util.StringUtils.fromBytesAutoDetect(request.body);
|
|
1588
|
+
if (body) {
|
|
1589
|
+
const licenseBody =
|
|
1590
|
+
/** @type {shaka.media.DrmEngine.ClearKeyLicenceRequestFormat} */ (
|
|
1591
|
+
JSON.parse(body));
|
|
1592
|
+
if (!licenseBody.type) {
|
|
1593
|
+
licenseBody.type = drmInfo.sessionType;
|
|
1594
|
+
request.body =
|
|
1595
|
+
shaka.util.StringUtils.toUTF8(JSON.stringify(licenseBody));
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
} catch (e) {
|
|
1599
|
+
shaka.log.info('Error unpacking ClearKey license', e);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1572
1603
|
/**
|
|
1573
1604
|
* @param {!Event} event
|
|
1574
1605
|
* @private
|
|
@@ -1849,9 +1880,16 @@ shaka.media.DrmEngine = class {
|
|
|
1849
1880
|
offlineConfig.sessionTypes = ['persistent-license'];
|
|
1850
1881
|
|
|
1851
1882
|
const configs = [offlineConfig, basicConfig];
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1883
|
+
// On some (Android) WebView environments,
|
|
1884
|
+
// requestMediaKeySystemAccess will
|
|
1885
|
+
// not resolve or reject, at least if RESOURCE_PROTECTED_MEDIA_ID
|
|
1886
|
+
// is not set. This is a workaround for that issue.
|
|
1887
|
+
const TIMEOUT_FOR_CHECKACCESS_IN_SECONDS = 1;
|
|
1888
|
+
const access =
|
|
1889
|
+
await shaka.util.Functional.promiseWithTimeout(
|
|
1890
|
+
TIMEOUT_FOR_CHECKACCESS_IN_SECONDS,
|
|
1891
|
+
navigator.requestMediaKeySystemAccess(keySystem, configs),
|
|
1892
|
+
);
|
|
1855
1893
|
await processMediaKeySystemAccess(keySystem, access);
|
|
1856
1894
|
} catch (error) {} // Ignore errors.
|
|
1857
1895
|
};
|
|
@@ -1886,13 +1924,22 @@ shaka.media.DrmEngine = class {
|
|
|
1886
1924
|
},
|
|
1887
1925
|
},
|
|
1888
1926
|
};
|
|
1889
|
-
|
|
1927
|
+
// On some (Android) WebView environments, decodingInfo will
|
|
1928
|
+
// not resolve or reject, at least if RESOURCE_PROTECTED_MEDIA_ID
|
|
1929
|
+
// is not set. This is a workaround for that issue.
|
|
1930
|
+
const TIMEOUT_FOR_DECODING_INFO_IN_SECONDS = 1;
|
|
1890
1931
|
const decodingInfo =
|
|
1891
|
-
|
|
1932
|
+
await shaka.util.Functional.promiseWithTimeout(
|
|
1933
|
+
TIMEOUT_FOR_DECODING_INFO_IN_SECONDS,
|
|
1934
|
+
navigator.mediaCapabilities.decodingInfo(decodingConfig),
|
|
1935
|
+
);
|
|
1892
1936
|
|
|
1893
1937
|
const access = decodingInfo.keySystemAccess;
|
|
1894
1938
|
await processMediaKeySystemAccess(keySystem, access);
|
|
1895
|
-
} catch (error) {
|
|
1939
|
+
} catch (error) {
|
|
1940
|
+
// Ignore errors.
|
|
1941
|
+
shaka.log.v2('Failed to probe support for', keySystem, error);
|
|
1942
|
+
}
|
|
1896
1943
|
};
|
|
1897
1944
|
|
|
1898
1945
|
// Initialize the support structure for each key system.
|
|
@@ -2388,26 +2435,24 @@ shaka.media.DrmEngine = class {
|
|
|
2388
2435
|
// The order of preference for drmInfo:
|
|
2389
2436
|
// 1. Clear Key config, used for debugging, should override everything else.
|
|
2390
2437
|
// (The application can still specify a clearkey license server.)
|
|
2391
|
-
// 2. Application-configured servers, if
|
|
2392
|
-
// anything from the manifest.
|
|
2393
|
-
// manifest and key system B is in the player config, only B will be
|
|
2394
|
-
// used, not A.
|
|
2438
|
+
// 2. Application-configured servers, if present, override
|
|
2439
|
+
// anything from the manifest.
|
|
2395
2440
|
// 3. Manifest-provided license servers are only used if nothing else is
|
|
2396
2441
|
// specified.
|
|
2397
2442
|
// This is important because it allows the application a clear way to
|
|
2398
|
-
// indicate which DRM systems should be
|
|
2399
|
-
// systems.
|
|
2400
|
-
//
|
|
2401
|
-
//
|
|
2443
|
+
// indicate which DRM systems should be ignored on platforms with multiple
|
|
2444
|
+
// DRM systems.
|
|
2445
|
+
// Alternatively, use config.preferredKeySystems to specify the preferred
|
|
2446
|
+
// key system.
|
|
2402
2447
|
|
|
2403
2448
|
if (originalKeySystem == 'org.w3.clearkey' && drmInfo.licenseServerUri) {
|
|
2404
2449
|
// Preference 1: Clear Key with pre-configured keys will have a data URI
|
|
2405
2450
|
// assigned as its license server. Don't change anything.
|
|
2406
2451
|
return;
|
|
2407
|
-
} else if (servers.size) {
|
|
2408
|
-
// Preference 2: If
|
|
2409
|
-
// override whatever was in the manifest.
|
|
2410
|
-
const server = servers.get(originalKeySystem)
|
|
2452
|
+
} else if (servers.size && servers.get(originalKeySystem)) {
|
|
2453
|
+
// Preference 2: If a license server for this keySystem is configured at
|
|
2454
|
+
// the application level, override whatever was in the manifest.
|
|
2455
|
+
const server = servers.get(originalKeySystem);
|
|
2411
2456
|
|
|
2412
2457
|
drmInfo.licenseServerUri = server;
|
|
2413
2458
|
} else {
|
|
@@ -2568,6 +2613,21 @@ shaka.media.DrmEngine.SessionMetaData;
|
|
|
2568
2613
|
*/
|
|
2569
2614
|
shaka.media.DrmEngine.PlayerInterface;
|
|
2570
2615
|
|
|
2616
|
+
/**
|
|
2617
|
+
* @typedef {{
|
|
2618
|
+
* kids: !Array.<string>,
|
|
2619
|
+
* type: string
|
|
2620
|
+
* }}
|
|
2621
|
+
*
|
|
2622
|
+
* @property {!Array.<string>} kids
|
|
2623
|
+
* An array of key IDs. Each element of the array is the base64url encoding of
|
|
2624
|
+
* the octet sequence containing the key ID value.
|
|
2625
|
+
* @property {string} type
|
|
2626
|
+
* The requested MediaKeySessionType.
|
|
2627
|
+
* @see https://www.w3.org/TR/encrypted-media/#clear-key-request-format
|
|
2628
|
+
*/
|
|
2629
|
+
shaka.media.DrmEngine.ClearKeyLicenceRequestFormat;
|
|
2630
|
+
|
|
2571
2631
|
/**
|
|
2572
2632
|
* The amount of time, in seconds, we wait to consider a session closed.
|
|
2573
2633
|
* This allows us to work around Chrome bug https://crbug.com/1108158.
|
|
@@ -40,9 +40,10 @@ shaka.media.Capabilities = class {
|
|
|
40
40
|
* @return {boolean}
|
|
41
41
|
*/
|
|
42
42
|
static isChangeTypeSupported() {
|
|
43
|
-
|
|
43
|
+
const sourceBuffer = window.ManagedSourceBuffer || window.SourceBuffer;
|
|
44
|
+
return !!sourceBuffer &&
|
|
44
45
|
// eslint-disable-next-line no-restricted-syntax
|
|
45
|
-
!!
|
|
46
|
+
!!sourceBuffer.prototype && !!sourceBuffer.prototype.changeType;
|
|
46
47
|
}
|
|
47
48
|
};
|
|
48
49
|
|
|
@@ -270,6 +270,11 @@ shaka.media.MediaSourceEngine = class {
|
|
|
270
270
|
for (const ref of stream.segmentIndex) {
|
|
271
271
|
const mimeType = ref.mimeType || stream.mimeType || '';
|
|
272
272
|
let codecs = ref.codecs || stream.codecs || '';
|
|
273
|
+
// Optimization for the case where the codecs and mimetype of the stream
|
|
274
|
+
// match the reference.
|
|
275
|
+
if (mimeType == stream.mimeType && codecs == stream.codecs) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
273
278
|
// Don't check the same combination of mimetype + codecs twice.
|
|
274
279
|
const combo = mimeType + ':' + codecs;
|
|
275
280
|
if (seenCombos.has(combo)) {
|
|
@@ -484,6 +489,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
484
489
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
485
490
|
|
|
486
491
|
await this.mediaSourceOpen_;
|
|
492
|
+
if (this.ended() || this.closed()) {
|
|
493
|
+
shaka.log.alwaysError('Expected MediaSource to be open during init(); ' +
|
|
494
|
+
'reopening the media source.');
|
|
495
|
+
this.mediaSourceOpen_ = new shaka.util.PublicPromise();
|
|
496
|
+
this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
|
|
497
|
+
await this.mediaSourceOpen_;
|
|
498
|
+
}
|
|
487
499
|
|
|
488
500
|
this.sequenceMode_ = sequenceMode;
|
|
489
501
|
this.manifestType_ = manifestType;
|
|
@@ -652,6 +664,17 @@ shaka.media.MediaSourceEngine = class {
|
|
|
652
664
|
return this.mediaSource_ ? this.mediaSource_.readyState == 'ended' : true;
|
|
653
665
|
}
|
|
654
666
|
|
|
667
|
+
/**
|
|
668
|
+
* @return {boolean} True if the MediaSource is in an "closed" state, or if
|
|
669
|
+
* the object has been destroyed.
|
|
670
|
+
*/
|
|
671
|
+
closed() {
|
|
672
|
+
if (this.reloadingMediaSource_) {
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
return this.mediaSource_ ? this.mediaSource_.readyState == 'closed' : true;
|
|
676
|
+
}
|
|
677
|
+
|
|
655
678
|
/**
|
|
656
679
|
* Gets the first timestamp in buffer for the given content type.
|
|
657
680
|
*
|
|
@@ -808,7 +831,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
808
831
|
return frame.description ===
|
|
809
832
|
'com.apple.streaming.transportStreamTimestamp';
|
|
810
833
|
});
|
|
811
|
-
if (metadataTimestamp
|
|
834
|
+
if (metadataTimestamp) {
|
|
812
835
|
timestamp = Math.round(metadataTimestamp.data) / 1000;
|
|
813
836
|
}
|
|
814
837
|
/** @private {shaka.extern.ID3Metadata} */
|
|
@@ -891,8 +914,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
891
914
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
892
915
|
|
|
893
916
|
if (contentType == ContentType.TEXT) {
|
|
894
|
-
if (this.manifestType_ == shaka.media.ManifestParser.HLS
|
|
895
|
-
reference.discontinuitySequence >= 0) {
|
|
917
|
+
if (this.manifestType_ == shaka.media.ManifestParser.HLS) {
|
|
896
918
|
// This won't be known until the first video segment is appended.
|
|
897
919
|
const offset = await this.textSequenceModeOffset_;
|
|
898
920
|
this.textEngine_.setTimestampOffset(offset);
|
|
@@ -962,7 +984,16 @@ shaka.media.MediaSourceEngine = class {
|
|
|
962
984
|
this.audioCompensation_.resolve(compensation);
|
|
963
985
|
}
|
|
964
986
|
}
|
|
965
|
-
|
|
987
|
+
let realTimestamp = timestamp;
|
|
988
|
+
const RAW_FORMATS = shaka.util.MimeUtils.RAW_FORMATS;
|
|
989
|
+
// For formats without containers and using segments mode, we need to
|
|
990
|
+
// adjust TimestampOffset relative to 0 because segments do not have
|
|
991
|
+
// any timestamp information.
|
|
992
|
+
if (!this.sequenceMode_ &&
|
|
993
|
+
RAW_FORMATS.includes(this.sourceBufferTypes_[contentType])) {
|
|
994
|
+
realTimestamp = 0;
|
|
995
|
+
}
|
|
996
|
+
const calculatedTimestampOffset = reference.startTime - realTimestamp;
|
|
966
997
|
const timestampOffsetDifference =
|
|
967
998
|
Math.abs(timestampOffset - calculatedTimestampOffset);
|
|
968
999
|
if ((timestampOffsetDifference >= 0.001 || seeked || adaptation) &&
|
|
@@ -1319,6 +1350,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1319
1350
|
return;
|
|
1320
1351
|
}
|
|
1321
1352
|
|
|
1353
|
+
// Avoid changing timestampOffset when the difference is less than 100 ms
|
|
1354
|
+
// from the end of the current buffer.
|
|
1355
|
+
const bufferEnd = this.bufferEnd(contentType);
|
|
1356
|
+
if (bufferEnd && Math.abs(bufferEnd - timestampOffset) < 0.1) {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1322
1360
|
// Queue an abort() to help MSE splice together overlapping segments.
|
|
1323
1361
|
// We set appendWindowEnd when we change periods in DASH content, and the
|
|
1324
1362
|
// period transition may result in overlap.
|
|
@@ -1360,7 +1398,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1360
1398
|
// don't call it again. Also do not call if readyState is
|
|
1361
1399
|
// 'closed' (not attached to video element) since it is not a
|
|
1362
1400
|
// valid operation.
|
|
1363
|
-
if (this.ended() || this.
|
|
1401
|
+
if (this.ended() || this.closed()) {
|
|
1364
1402
|
return;
|
|
1365
1403
|
}
|
|
1366
1404
|
// Tizen won't let us pass undefined, but it will let us omit the
|
|
@@ -2040,32 +2078,32 @@ shaka.media.MediaSourceEngine = class {
|
|
|
2040
2078
|
}
|
|
2041
2079
|
|
|
2042
2080
|
/**
|
|
2043
|
-
* Codec switch if necessary, this will not resolve until the codec
|
|
2044
|
-
* switch is over.
|
|
2045
2081
|
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
2046
2082
|
* @param {string} mimeType
|
|
2047
2083
|
* @param {string} codecs
|
|
2048
|
-
* @
|
|
2049
|
-
*
|
|
2050
|
-
*
|
|
2051
|
-
* false otherwise.
|
|
2084
|
+
* @return {{transmuxer: ?shaka.extern.Transmuxer,
|
|
2085
|
+
* transmuxerMuxed: boolean, basicType: string, codec: string,
|
|
2086
|
+
* mimeType: string}}
|
|
2052
2087
|
* @private
|
|
2053
2088
|
*/
|
|
2054
|
-
|
|
2089
|
+
getRealInfo_(contentType, mimeType, codecs) {
|
|
2055
2090
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2056
|
-
if (contentType == ContentType.TEXT) {
|
|
2057
|
-
return false;
|
|
2058
|
-
}
|
|
2059
2091
|
const MimeUtils = shaka.util.MimeUtils;
|
|
2060
|
-
const currentCodec = MimeUtils.getNormalizedCodec(
|
|
2061
|
-
MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
|
|
2062
|
-
const currentBasicType = MimeUtils.getBasicType(
|
|
2063
|
-
this.sourceBufferTypes_[contentType]);
|
|
2064
|
-
|
|
2065
2092
|
/** @type {?shaka.extern.Transmuxer} */
|
|
2066
2093
|
let transmuxer;
|
|
2067
2094
|
let transmuxerMuxed = false;
|
|
2068
|
-
|
|
2095
|
+
const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
|
|
2096
|
+
ContentType.AUDIO, (codecs || '').split(','));
|
|
2097
|
+
const videoCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
|
|
2098
|
+
ContentType.VIDEO, (codecs || '').split(','));
|
|
2099
|
+
let codec = videoCodec;
|
|
2100
|
+
if (contentType == ContentType.AUDIO) {
|
|
2101
|
+
codec = audioCodec;
|
|
2102
|
+
}
|
|
2103
|
+
if (!codec) {
|
|
2104
|
+
codec = codecs;
|
|
2105
|
+
}
|
|
2106
|
+
let newMimeType = shaka.util.MimeUtils.getFullType(mimeType, codec);
|
|
2069
2107
|
let needTransmux = this.config_.forceTransmux;
|
|
2070
2108
|
if (!shaka.media.Capabilities.isTypeSupported(newMimeType) ||
|
|
2071
2109
|
(!this.sequenceMode_ &&
|
|
@@ -2075,36 +2113,79 @@ shaka.media.MediaSourceEngine = class {
|
|
|
2075
2113
|
const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
|
|
2076
2114
|
if (needTransmux) {
|
|
2077
2115
|
const newMimeTypeWithAllCodecs =
|
|
2078
|
-
shaka.util.MimeUtils.getFullTypeWithAllCodecs(mimeType,
|
|
2116
|
+
shaka.util.MimeUtils.getFullTypeWithAllCodecs(mimeType, codec);
|
|
2079
2117
|
const transmuxerPlugin =
|
|
2080
2118
|
TransmuxerEngine.findTransmuxer(newMimeTypeWithAllCodecs);
|
|
2081
2119
|
if (transmuxerPlugin) {
|
|
2082
2120
|
transmuxer = transmuxerPlugin();
|
|
2083
|
-
const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
|
|
2084
|
-
ContentType.AUDIO, (codecs || '').split(','));
|
|
2085
|
-
const videoCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
|
|
2086
|
-
ContentType.VIDEO, (codecs || '').split(','));
|
|
2087
2121
|
if (audioCodec && videoCodec) {
|
|
2088
2122
|
transmuxerMuxed = true;
|
|
2089
|
-
let codec = videoCodec;
|
|
2090
|
-
if (contentType == ContentType.AUDIO) {
|
|
2091
|
-
codec = audioCodec;
|
|
2092
|
-
}
|
|
2093
|
-
newMimeType = transmuxer.convertCodecs(contentType,
|
|
2094
|
-
shaka.util.MimeUtils.getFullTypeWithAllCodecs(mimeType, codec));
|
|
2095
|
-
} else {
|
|
2096
|
-
newMimeType =
|
|
2097
|
-
transmuxer.convertCodecs(contentType, newMimeTypeWithAllCodecs);
|
|
2098
2123
|
}
|
|
2124
|
+
newMimeType =
|
|
2125
|
+
transmuxer.convertCodecs(contentType, newMimeTypeWithAllCodecs);
|
|
2099
2126
|
}
|
|
2100
2127
|
}
|
|
2101
2128
|
|
|
2102
2129
|
const newCodec = MimeUtils.getNormalizedCodec(
|
|
2103
2130
|
MimeUtils.getCodecs(newMimeType));
|
|
2104
2131
|
const newBasicType = MimeUtils.getBasicType(newMimeType);
|
|
2132
|
+
return {
|
|
2133
|
+
transmuxer,
|
|
2134
|
+
transmuxerMuxed,
|
|
2135
|
+
basicType: newBasicType,
|
|
2136
|
+
codec: newCodec,
|
|
2137
|
+
mimeType: newMimeType,
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
/**
|
|
2142
|
+
* Codec switch if necessary, this will not resolve until the codec
|
|
2143
|
+
* switch is over.
|
|
2144
|
+
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
2145
|
+
* @param {string} mimeType
|
|
2146
|
+
* @param {string} codecs
|
|
2147
|
+
* @param {!Map.<shaka.util.ManifestParserUtils.ContentType,
|
|
2148
|
+
* shaka.extern.Stream>} streamsByType
|
|
2149
|
+
* @return {!Promise.<boolean>} true if there was a codec switch,
|
|
2150
|
+
* false otherwise.
|
|
2151
|
+
* @private
|
|
2152
|
+
*/
|
|
2153
|
+
async codecSwitchIfNecessary_(contentType, mimeType, codecs, streamsByType) {
|
|
2154
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2155
|
+
if (contentType == ContentType.TEXT) {
|
|
2156
|
+
return false;
|
|
2157
|
+
}
|
|
2158
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
2159
|
+
const currentCodec = MimeUtils.getNormalizedCodec(
|
|
2160
|
+
MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
|
|
2161
|
+
const currentBasicType = MimeUtils.getBasicType(
|
|
2162
|
+
this.sourceBufferTypes_[contentType]);
|
|
2163
|
+
|
|
2164
|
+
const realInfo = this.getRealInfo_(contentType, mimeType, codecs);
|
|
2165
|
+
const transmuxer = realInfo.transmuxer;
|
|
2166
|
+
const transmuxerMuxed = realInfo.transmuxerMuxed;
|
|
2167
|
+
const newBasicType = realInfo.basicType;
|
|
2168
|
+
const newCodec = realInfo.codec;
|
|
2169
|
+
const newMimeType = realInfo.mimeType;
|
|
2170
|
+
|
|
2171
|
+
let muxedContentCheck = true;
|
|
2172
|
+
if (transmuxerMuxed) {
|
|
2173
|
+
const muxedRealInfo =
|
|
2174
|
+
this.getRealInfo_(ContentType.AUDIO, mimeType, codecs);
|
|
2175
|
+
const muxedCurrentCodec = MimeUtils.getNormalizedCodec(
|
|
2176
|
+
MimeUtils.getCodecs(this.sourceBufferTypes_[ContentType.AUDIO]));
|
|
2177
|
+
const muxedCurrentBasicType = MimeUtils.getBasicType(
|
|
2178
|
+
this.sourceBufferTypes_[ContentType.AUDIO]);
|
|
2179
|
+
muxedContentCheck = muxedCurrentCodec == muxedRealInfo.codec &&
|
|
2180
|
+
muxedCurrentBasicType == muxedRealInfo.basicType;
|
|
2181
|
+
if (muxedRealInfo.transmuxer) {
|
|
2182
|
+
muxedRealInfo.transmuxer.destroy();
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2105
2185
|
|
|
2106
2186
|
// Current/new codecs base and basic type match then no need to switch
|
|
2107
|
-
if (currentCodec === newCodec && currentBasicType === newBasicType
|
|
2187
|
+
if (currentCodec === newCodec && currentBasicType === newBasicType &&
|
|
2188
|
+
muxedContentCheck) {
|
|
2108
2189
|
if (this.transmuxers_[contentType] && !transmuxer) {
|
|
2109
2190
|
this.transmuxers_[contentType].destroy();
|
|
2110
2191
|
delete this.transmuxers_[contentType];
|
|
@@ -47,9 +47,11 @@ shaka.media.SegmentUtils = class {
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* @param {!BufferSource} data
|
|
50
|
+
* @param {boolean=} disableAudio
|
|
51
|
+
* @param {boolean=} disableVideo
|
|
50
52
|
* @return {?shaka.media.SegmentUtils.BasicInfo}
|
|
51
53
|
*/
|
|
52
|
-
static getBasicInfoFromTs(data) {
|
|
54
|
+
static getBasicInfoFromTs(data, disableAudio = false, disableVideo = false) {
|
|
53
55
|
const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
|
|
54
56
|
const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
|
|
55
57
|
const tsCodecs = tsParser.getCodecs();
|
|
@@ -57,50 +59,54 @@ shaka.media.SegmentUtils = class {
|
|
|
57
59
|
const codecs = [];
|
|
58
60
|
let hasAudio = false;
|
|
59
61
|
let hasVideo = false;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
62
|
+
if (!disableAudio) {
|
|
63
|
+
switch (tsCodecs.audio) {
|
|
64
|
+
case 'aac':
|
|
65
|
+
case 'aac-loas':
|
|
66
|
+
codecs.push('mp4a.40.2');
|
|
67
|
+
hasAudio = true;
|
|
68
|
+
break;
|
|
69
|
+
case 'mp3':
|
|
70
|
+
codecs.push('mp4a.40.34');
|
|
71
|
+
hasAudio = true;
|
|
72
|
+
break;
|
|
73
|
+
case 'ac3':
|
|
74
|
+
codecs.push('ac-3');
|
|
75
|
+
hasAudio = true;
|
|
76
|
+
break;
|
|
77
|
+
case 'ec3':
|
|
78
|
+
codecs.push('ec-3');
|
|
79
|
+
hasAudio = true;
|
|
80
|
+
break;
|
|
81
|
+
case 'opus':
|
|
82
|
+
codecs.push('opus');
|
|
83
|
+
hasAudio = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
82
86
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
87
|
+
if (!disableVideo) {
|
|
88
|
+
switch (tsCodecs.video) {
|
|
89
|
+
case 'avc':
|
|
90
|
+
if (videoInfo.codec) {
|
|
91
|
+
codecs.push(videoInfo.codec);
|
|
92
|
+
} else {
|
|
93
|
+
codecs.push('avc1.42E01E');
|
|
94
|
+
}
|
|
95
|
+
hasVideo = true;
|
|
96
|
+
break;
|
|
97
|
+
case 'hvc':
|
|
98
|
+
if (videoInfo.codec) {
|
|
99
|
+
codecs.push(videoInfo.codec);
|
|
100
|
+
} else {
|
|
101
|
+
codecs.push('hvc1.1.6.L93.90');
|
|
102
|
+
}
|
|
103
|
+
hasVideo = true;
|
|
104
|
+
break;
|
|
105
|
+
case 'av1':
|
|
106
|
+
codecs.push('av01.0.01M.08');
|
|
107
|
+
hasVideo = true;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
104
110
|
}
|
|
105
111
|
if (!codecs.length) {
|
|
106
112
|
return null;
|
|
@@ -381,9 +387,13 @@ shaka.media.SegmentUtils = class {
|
|
|
381
387
|
if (initData) {
|
|
382
388
|
captionParser.init(initData);
|
|
383
389
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
390
|
+
try {
|
|
391
|
+
captionParser.parseFrom(data);
|
|
392
|
+
for (const stream of captionParser.getStreams()) {
|
|
393
|
+
closedCaptions.set(stream, stream);
|
|
394
|
+
}
|
|
395
|
+
} catch (e) {
|
|
396
|
+
shaka.log.debug('Error detecting CC streams', e);
|
|
387
397
|
}
|
|
388
398
|
captionParser.reset();
|
|
389
399
|
}
|