shaka-player 4.6.7 → 4.6.9
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 +39 -0
- package/README.md +3 -1
- package/dist/locales.js +31 -31
- package/dist/shaka-player.compiled.d.ts +4 -0
- package/dist/shaka-player.compiled.debug.d.ts +4 -0
- package/dist/shaka-player.compiled.debug.externs.js +5 -0
- package/dist/shaka-player.compiled.debug.js +117 -114
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +5 -0
- package/dist/shaka-player.compiled.js +677 -675
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +4 -0
- package/dist/shaka-player.ui.debug.d.ts +4 -0
- package/dist/shaka-player.ui.debug.externs.js +5 -0
- package/dist/shaka-player.ui.debug.js +141 -136
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +5 -0
- package/dist/shaka-player.ui.js +765 -763
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/text-displayer.md +15 -0
- package/lib/ads/client_side_ad.js +4 -2
- package/lib/ads/client_side_ad_manager.js +5 -3
- package/lib/cast/cast_proxy.js +18 -0
- package/lib/cea/cea608_memory.js +1 -1
- package/lib/cea/cea_decoder.js +1 -1
- package/lib/hls/hls_parser.js +16 -0
- package/lib/hls/hls_utils.js +5 -5
- package/lib/media/media_source_engine.js +4 -4
- package/lib/media/play_rate_controller.js +1 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/offline/session_deleter.js +0 -8
- package/lib/player.js +41 -6
- package/lib/polyfill/patchedmediakeys_apple.js +0 -6
- package/lib/text/ttml_text_parser.js +5 -0
- package/lib/text/vtt_text_parser.js +5 -0
- package/lib/util/language_utils.js +7 -8
- package/package.json +1 -1
- package/test/cea/cea_decoder_unit.js +36 -0
- package/test/media/media_source_engine_unit.js +26 -3
- package/test/player_load_graph_integration.js +5 -0
- package/test/player_unit.js +17 -0
- package/test/text/ttml_text_parser_unit.js +16 -16
- package/ui/play_button.js +17 -0
- package/ui/playback_rate_selection.js +4 -0
- package/ui/small_play_button.js +2 -2
|
@@ -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
|
+
{ "text-displayer": { "title": "Configuring text displayer" } },
|
|
12
13
|
{ "errors": { "title": "Error Handling" } },
|
|
13
14
|
{ "a11y": { "title": "Creating accessible buttons" } },
|
|
14
15
|
{ "ad_monetization": { "title": "Serving ads with our IMA SDK integration" } },
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Configuring text displayer`
|
|
2
|
+
|
|
3
|
+
Shaka Player supports two types of Text Displayer.
|
|
4
|
+
1) Using the browser's native API, Shaka Player creates a textTrack along
|
|
5
|
+
with the AUDIO or VIDEO Element.
|
|
6
|
+
2) Rendering the subtitles in a container in the DOM.
|
|
7
|
+
3) Providing a custom text displayer factory would allow a developer to make
|
|
8
|
+
their own custom text displayer that fits neither category.
|
|
9
|
+
|
|
10
|
+
By default, if not configured otherwise, the player will use type 1.
|
|
11
|
+
To configure type 2, you have to call `setVideoContainer` function before
|
|
12
|
+
making `load` call in the player.
|
|
13
|
+
|
|
14
|
+
Note: The UI calls setVideoContainer for you if the video's controls are
|
|
15
|
+
disabled, so this isn't always necessary when using the UI.
|
|
@@ -155,6 +155,7 @@ shaka.ads.ClientSideAd = class {
|
|
|
155
155
|
* @export
|
|
156
156
|
*/
|
|
157
157
|
setVolume(volume) {
|
|
158
|
+
this.video_.volume = volume;
|
|
158
159
|
return this.manager_.setVolume(volume);
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -197,13 +198,14 @@ shaka.ads.ClientSideAd = class {
|
|
|
197
198
|
* @export
|
|
198
199
|
*/
|
|
199
200
|
setMuted(muted) {
|
|
201
|
+
this.video_.muted = muted;
|
|
200
202
|
// Emulate the "mute" functionality, where current, pre-mute
|
|
201
203
|
// volume is saved and can be restored on unmute.
|
|
202
204
|
if (muted) {
|
|
203
205
|
this.volume_ = this.getVolume();
|
|
204
|
-
this.setVolume(0);
|
|
206
|
+
this.manager_.setVolume(0);
|
|
205
207
|
} else {
|
|
206
|
-
this.setVolume(this.volume_);
|
|
208
|
+
this.manager_.setVolume(this.volume_);
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
|
|
@@ -234,10 +234,12 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
234
234
|
videoPlayHead.currentTime = this.video_.currentTime;
|
|
235
235
|
});
|
|
236
236
|
this.eventManager_.listen(this.video_, 'volumechange', () => {
|
|
237
|
+
if (!this.ad_) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
this.ad_.setVolume(this.video_.volume);
|
|
237
241
|
if (this.video_.muted) {
|
|
238
|
-
this.
|
|
239
|
-
} else {
|
|
240
|
-
this.imaAdsManager_.setVolume(this.video_.volume);
|
|
242
|
+
this.ad_.setMuted(true);
|
|
241
243
|
}
|
|
242
244
|
});
|
|
243
245
|
}
|
package/lib/cast/cast_proxy.js
CHANGED
|
@@ -496,6 +496,16 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
|
|
|
496
496
|
startTime = this.sender_.get('video', 'currentTime');
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
+
let activeTextTrack;
|
|
500
|
+
const textTracks = this.sender_.get('player', 'getTextTracks')();
|
|
501
|
+
|
|
502
|
+
if (textTracks && textTracks.length) {
|
|
503
|
+
activeTextTrack = textTracks.find((t) => t.active);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const isTextTrackVisible =
|
|
507
|
+
this.sender_.get('player', 'isTextTrackVisible')();
|
|
508
|
+
|
|
499
509
|
// Now load the manifest, if present.
|
|
500
510
|
if (assetUri) {
|
|
501
511
|
// Don't autoplay the content until we finish setting up initial state.
|
|
@@ -527,6 +537,14 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
|
|
|
527
537
|
/** @type {Object} */(this.localPlayer_)[setter](value);
|
|
528
538
|
}
|
|
529
539
|
|
|
540
|
+
this.localPlayer_.setTextTrackVisibility(isTextTrackVisible);
|
|
541
|
+
if (activeTextTrack) {
|
|
542
|
+
this.localPlayer_.selectTextLanguage(
|
|
543
|
+
activeTextTrack.language,
|
|
544
|
+
activeTextTrack.roles,
|
|
545
|
+
activeTextTrack.forced);
|
|
546
|
+
}
|
|
547
|
+
|
|
530
548
|
// Restore the original autoplay setting.
|
|
531
549
|
this.localVideo_.autoplay = autoplay;
|
|
532
550
|
if (assetUri) {
|
package/lib/cea/cea608_memory.js
CHANGED
|
@@ -79,7 +79,7 @@ shaka.cea.Cea608Memory = class {
|
|
|
79
79
|
* @return {?shaka.extern.ICaptionDecoder.ClosedCaption}
|
|
80
80
|
*/
|
|
81
81
|
forceEmit(startTime, endTime) {
|
|
82
|
-
const stream = `CC${(this.fieldNum_<< 1) | this.channelNum_ +1}`;
|
|
82
|
+
const stream = `CC${((this.fieldNum_<< 1) | this.channelNum_) + 1}`;
|
|
83
83
|
const topLevelCue = new shaka.text.Cue(
|
|
84
84
|
startTime, endTime, /* payload= */ '');
|
|
85
85
|
topLevelCue.lineInterpretation =
|
package/lib/cea/cea_decoder.js
CHANGED
|
@@ -283,7 +283,7 @@ shaka.cea.CeaDecoder = class {
|
|
|
283
283
|
// Get the correct stream for this caption packet (CC1, ..., CC4)
|
|
284
284
|
const selectedChannel = fieldNum ?
|
|
285
285
|
this.currentField2Channel_ : this.currentField1Channel_;
|
|
286
|
-
const selectedMode = `CC${(fieldNum << 1) | selectedChannel + 1}`;
|
|
286
|
+
const selectedMode = `CC${((fieldNum << 1) | selectedChannel) + 1}`;
|
|
287
287
|
const selectedStream = this.cea608ModeToStream_.get(selectedMode);
|
|
288
288
|
|
|
289
289
|
// Check for bad frames (bad pairs). This can be two 0xff, two 0x00, or any
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -979,6 +979,22 @@ shaka.hls.HlsParser = class {
|
|
|
979
979
|
this.presentationTimeline_.setSegmentAvailabilityDuration(
|
|
980
980
|
segmentAvailabilityDuration);
|
|
981
981
|
}
|
|
982
|
+
|
|
983
|
+
if (!this.presentationTimeline_.isStartTimeLocked()) {
|
|
984
|
+
const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
|
|
985
|
+
const activeStreamInfos =
|
|
986
|
+
streamInfos.filter((s) => s.stream.segmentIndex);
|
|
987
|
+
for (const streamInfo of activeStreamInfos) {
|
|
988
|
+
if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
const firstReference = streamInfo.stream.segmentIndex.get(0);
|
|
992
|
+
if (firstReference && firstReference.syncTime) {
|
|
993
|
+
const syncTime = firstReference.syncTime;
|
|
994
|
+
this.presentationTimeline_.setPresentationStartTime(syncTime);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
982
998
|
} else {
|
|
983
999
|
// Use the minimum duration as the presentation duration.
|
|
984
1000
|
this.presentationTimeline_.setDuration(this.getMinDuration_());
|
package/lib/hls/hls_utils.js
CHANGED
|
@@ -47,12 +47,12 @@ shaka.hls.Utils = class {
|
|
|
47
47
|
* @return {?shaka.hls.Tag}
|
|
48
48
|
*/
|
|
49
49
|
static getFirstTagWithName(tags, name) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
for (const tag of tags) {
|
|
51
|
+
if (tag.name === name) {
|
|
52
|
+
return tag;
|
|
53
|
+
}
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
return tagsWithName[0];
|
|
55
|
+
return null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -165,9 +165,6 @@ shaka.media.MediaSourceEngine = class {
|
|
|
165
165
|
|
|
166
166
|
const mediaSource = new ManagedMediaSource();
|
|
167
167
|
|
|
168
|
-
this.eventManager_.listenOnce(
|
|
169
|
-
mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
|
|
170
|
-
|
|
171
168
|
this.eventManager_.listen(
|
|
172
169
|
mediaSource, 'startstreaming', () => {
|
|
173
170
|
this.streamingAllowed_ = true;
|
|
@@ -178,6 +175,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
178
175
|
this.streamingAllowed_ = false;
|
|
179
176
|
});
|
|
180
177
|
|
|
178
|
+
this.eventManager_.listenOnce(
|
|
179
|
+
mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
|
|
180
|
+
|
|
181
181
|
// Correctly set when playback has begun.
|
|
182
182
|
this.eventManager_.listenOnce(this.video_, 'playing', () => {
|
|
183
183
|
this.playbackHasBegun_ = true;
|
|
@@ -1793,7 +1793,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1793
1793
|
|
|
1794
1794
|
this.destroyer_.ensureNotDestroyed();
|
|
1795
1795
|
|
|
1796
|
-
this.eventManager_.listenOnce(this.video_, '
|
|
1796
|
+
this.eventManager_.listenOnce(this.video_, 'canplaythrough', () => {
|
|
1797
1797
|
this.destroyer_.ensureNotDestroyed();
|
|
1798
1798
|
|
|
1799
1799
|
this.video_.autoplay = previousAutoPlayState;
|
|
@@ -328,6 +328,17 @@ shaka.media.PresentationTimeline = class {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Returns if the presentation timeline's start time is locked.
|
|
333
|
+
*
|
|
334
|
+
* @return {boolean}
|
|
335
|
+
* @export
|
|
336
|
+
*/
|
|
337
|
+
isStartTimeLocked() {
|
|
338
|
+
return this.startTimeLocked_;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
|
|
331
342
|
/**
|
|
332
343
|
* Gives PresentationTimeline a Stream's minimum segment start time.
|
|
333
344
|
*
|
|
@@ -71,14 +71,6 @@ shaka.offline.SessionDeleter = class {
|
|
|
71
71
|
return [];
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
try {
|
|
75
|
-
await drmEngine.setServerCertificate();
|
|
76
|
-
} catch (e) {
|
|
77
|
-
shaka.log.warning('Error setting server certificate', e);
|
|
78
|
-
await drmEngine.destroy();
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
74
|
/** @type {!Array.<string>} */
|
|
83
75
|
const sessionIds = [];
|
|
84
76
|
await Promise.all(bucket.sessionIds.map(async (sessionId) => {
|
package/lib/player.js
CHANGED
|
@@ -549,6 +549,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
549
549
|
*/
|
|
550
550
|
this.loadEventManager_ = new shaka.util.EventManager();
|
|
551
551
|
|
|
552
|
+
/**
|
|
553
|
+
* For listeners scoped to the lifetime of the loaded content.
|
|
554
|
+
* @private {shaka.util.EventManager}
|
|
555
|
+
*/
|
|
556
|
+
this.trickPlayEventManager_ = new shaka.util.EventManager();
|
|
557
|
+
|
|
552
558
|
/** @private {shaka.net.NetworkingEngine} */
|
|
553
559
|
this.networkingEngine_ = null;
|
|
554
560
|
|
|
@@ -840,6 +846,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
840
846
|
this.loadEventManager_.release();
|
|
841
847
|
this.loadEventManager_ = null;
|
|
842
848
|
}
|
|
849
|
+
if (this.trickPlayEventManager_) {
|
|
850
|
+
this.trickPlayEventManager_.release();
|
|
851
|
+
this.trickPlayEventManager_ = null;
|
|
852
|
+
}
|
|
843
853
|
|
|
844
854
|
this.abrManagerFactory_ = null;
|
|
845
855
|
this.config_ = null;
|
|
@@ -1157,6 +1167,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1157
1167
|
// element.
|
|
1158
1168
|
if (this.video_) {
|
|
1159
1169
|
this.loadEventManager_.removeAll();
|
|
1170
|
+
this.trickPlayEventManager_.removeAll();
|
|
1160
1171
|
}
|
|
1161
1172
|
|
|
1162
1173
|
// Stop the variant checker timer
|
|
@@ -3504,6 +3515,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3504
3515
|
shaka.log.alwaysWarn('A trick play rate of 0 is unsupported!');
|
|
3505
3516
|
return;
|
|
3506
3517
|
}
|
|
3518
|
+
this.trickPlayEventManager_.removeAll();
|
|
3507
3519
|
|
|
3508
3520
|
if (this.video_.paused) {
|
|
3509
3521
|
// Our fast forward is implemented with playbackRate and needs the video
|
|
@@ -3517,6 +3529,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3517
3529
|
this.abrManager_.playbackRateChanged(rate);
|
|
3518
3530
|
this.streamingEngine_.setTrickPlay(Math.abs(rate) > 1);
|
|
3519
3531
|
}
|
|
3532
|
+
if (this.isLive()) {
|
|
3533
|
+
this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
|
|
3534
|
+
const currentTime = this.video_.currentTime;
|
|
3535
|
+
const seekRange = this.seekRange();
|
|
3536
|
+
const safeSeekOffset = this.config_.streaming.safeSeekOffset;
|
|
3537
|
+
if (rate > 0) {
|
|
3538
|
+
if (currentTime >= seekRange.end) {
|
|
3539
|
+
this.cancelTrickPlay();
|
|
3540
|
+
}
|
|
3541
|
+
} else {
|
|
3542
|
+
if (currentTime <= (seekRange.start + safeSeekOffset)) {
|
|
3543
|
+
this.cancelTrickPlay();
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
});
|
|
3547
|
+
}
|
|
3520
3548
|
}
|
|
3521
3549
|
|
|
3522
3550
|
/**
|
|
@@ -3536,6 +3564,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3536
3564
|
this.abrManager_.playbackRateChanged(defaultPlaybackRate);
|
|
3537
3565
|
this.streamingEngine_.setTrickPlay(false);
|
|
3538
3566
|
}
|
|
3567
|
+
this.trickPlayEventManager_.removeAll();
|
|
3539
3568
|
}
|
|
3540
3569
|
|
|
3541
3570
|
/**
|
|
@@ -4768,12 +4797,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4768
4797
|
|
|
4769
4798
|
const references = [];
|
|
4770
4799
|
for (const cue of cues) {
|
|
4771
|
-
|
|
4772
|
-
|
|
4800
|
+
let uris = null;
|
|
4801
|
+
const getUris = () => {
|
|
4802
|
+
if (uris == null) {
|
|
4803
|
+
uris = shaka.util.ManifestParserUtils.resolveUris(
|
|
4804
|
+
[uri], [cue.payload]);
|
|
4805
|
+
}
|
|
4806
|
+
return uris || [];
|
|
4807
|
+
};
|
|
4773
4808
|
const reference = new shaka.media.SegmentReference(
|
|
4774
4809
|
cue.startTime,
|
|
4775
4810
|
cue.endTime,
|
|
4776
|
-
|
|
4811
|
+
getUris,
|
|
4777
4812
|
/* startByte= */ 0,
|
|
4778
4813
|
/* endByte= */ null,
|
|
4779
4814
|
/* initSegmentReference= */ null,
|
|
@@ -4781,8 +4816,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4781
4816
|
/* appendWindowStart= */ 0,
|
|
4782
4817
|
/* appendWindowEnd= */ Infinity,
|
|
4783
4818
|
);
|
|
4784
|
-
if (
|
|
4785
|
-
const spriteInfo =
|
|
4819
|
+
if (cue.payload.includes('#xywh')) {
|
|
4820
|
+
const spriteInfo = cue.payload.split('#xywh=')[1].split(',');
|
|
4786
4821
|
if (spriteInfo.length === 4) {
|
|
4787
4822
|
reference.setThumbnailSprite({
|
|
4788
4823
|
height: parseInt(spriteInfo[3], 10),
|
|
@@ -6779,7 +6814,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6779
6814
|
* @export
|
|
6780
6815
|
*/
|
|
6781
6816
|
// eslint-disable-next-line no-useless-concat
|
|
6782
|
-
shaka.Player.version = 'v4.6.
|
|
6817
|
+
shaka.Player.version = 'v4.6.9' + '-uncompiled'; // x-release-please-version
|
|
6783
6818
|
|
|
6784
6819
|
// Initialize the deprecation system using the version string we just set
|
|
6785
6820
|
// on the player.
|
|
@@ -791,9 +791,3 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
|
|
|
791
791
|
goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
|
|
792
792
|
}
|
|
793
793
|
};
|
|
794
|
-
|
|
795
|
-
if (!navigator.requestMediaKeySystemAccess ||
|
|
796
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
797
|
-
!MediaKeySystemAccess.prototype.getConfiguration) {
|
|
798
|
-
shaka.polyfill.register(shaka.polyfill.PatchedMediaKeysApple.install);
|
|
799
|
-
}
|
|
@@ -491,6 +491,11 @@ shaka.text.TtmlTextParser = class {
|
|
|
491
491
|
region.viewportAnchorX = region.viewportAnchorX * 100 /
|
|
492
492
|
globalWidth;
|
|
493
493
|
}
|
|
494
|
+
} else if (!extent) {
|
|
495
|
+
region.width = 100 - region.viewportAnchorX;
|
|
496
|
+
region.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
497
|
+
region.height = 100 - region.viewportAnchorY;
|
|
498
|
+
region.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
494
499
|
}
|
|
495
500
|
|
|
496
501
|
region.viewportAnchorUnits = percentage || globalWidth != null ?
|
|
@@ -451,6 +451,11 @@ shaka.text.VttTextParser = class {
|
|
|
451
451
|
*/
|
|
452
452
|
static parseCueStyles(payload, rootCue, styles) {
|
|
453
453
|
const VttTextParser = shaka.text.VttTextParser;
|
|
454
|
+
// Optimization for unstyled payloads.
|
|
455
|
+
if (!payload.includes('<')) {
|
|
456
|
+
rootCue.payload = VttTextParser.htmlUnescape_(payload);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
454
459
|
if (styles.size === 0) {
|
|
455
460
|
VttTextParser.addDefaultTextColor_(styles);
|
|
456
461
|
}
|
|
@@ -192,25 +192,24 @@ shaka.util.LanguageUtils = class {
|
|
|
192
192
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
193
193
|
|
|
194
194
|
const privateusePrefix = 'x-';
|
|
195
|
-
const
|
|
196
|
-
|
|
195
|
+
const [languageRegion = '', privateuseSuffix = ''] =
|
|
196
|
+
locale.split(`-${privateusePrefix}`);
|
|
197
|
+
const [languageCode = '', regionCode = ''] = languageRegion.split('-');
|
|
197
198
|
|
|
198
199
|
// We are only going to use the language, the region and the "privateuse" part (as per https://datatracker.ietf.org/doc/html/rfc5646).
|
|
199
200
|
// Anything else is thrown away.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const privateuse = components[1] ?
|
|
203
|
-
`${privateusePrefix}${components[1]}` : '';
|
|
201
|
+
const privateuse = privateuseSuffix ?
|
|
202
|
+
`${privateusePrefix}${privateuseSuffix}` : '';
|
|
204
203
|
|
|
205
204
|
// Convert the language to lower case. It is standard for the language code
|
|
206
205
|
// to be in lower case, but it will also make the map look-up easier.
|
|
207
|
-
language =
|
|
206
|
+
let language = languageCode.toLowerCase();
|
|
208
207
|
language = LanguageUtils.isoMap_.get(language) || language;
|
|
209
208
|
|
|
210
209
|
// Convert the region to upper case. It is standard for the region to be in
|
|
211
210
|
// upper case. If there is no upper code, then it will be an empty string
|
|
212
211
|
// and this will be a no-op.
|
|
213
|
-
region =
|
|
212
|
+
const region = regionCode.toUpperCase();
|
|
214
213
|
|
|
215
214
|
return `${region ? `${language}-${region}` : language}${
|
|
216
215
|
privateuse ? `-${privateuse}` : ''}`;
|
package/package.json
CHANGED
|
@@ -44,6 +44,42 @@ describe('CeaDecoder', () => {
|
|
|
44
44
|
decoder.clear();
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
it('painton captions on CC4', () => {
|
|
48
|
+
const controlCount = 0x03;
|
|
49
|
+
const captionData = 0xc0 | controlCount;
|
|
50
|
+
const paintonCaptionCC4Packet = new Uint8Array([
|
|
51
|
+
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
52
|
+
0xfd, 0x9d, 0x29, // Paint-on mode (RDC control code).
|
|
53
|
+
0xfd, 0xf4, 0xe5, // t, e
|
|
54
|
+
0xfd, 0x73, 0xf4, // s, t
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const startTimeCaption1 = 1;
|
|
58
|
+
const startTimeCaption2 = 2;
|
|
59
|
+
const expectedText = 'test';
|
|
60
|
+
|
|
61
|
+
const topLevelCue = new shaka.text.Cue(startTimeCaption1,
|
|
62
|
+
startTimeCaption2, '');
|
|
63
|
+
topLevelCue.line = 10;
|
|
64
|
+
topLevelCue.lineInterpretation =
|
|
65
|
+
shaka.text.Cue.lineInterpretation.PERCENTAGE;
|
|
66
|
+
topLevelCue.nestedCues = [
|
|
67
|
+
CeaUtils.createDefaultCue(
|
|
68
|
+
startTimeCaption1, startTimeCaption2, expectedText),
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const expectedCaptions = [{
|
|
72
|
+
stream: 'CC4',
|
|
73
|
+
cue: topLevelCue,
|
|
74
|
+
}];
|
|
75
|
+
|
|
76
|
+
decoder.extract(paintonCaptionCC4Packet, startTimeCaption1);
|
|
77
|
+
decoder.extract(eraseDisplayedMemory, startTimeCaption2);
|
|
78
|
+
const captions = decoder.decode();
|
|
79
|
+
|
|
80
|
+
expect(captions).toEqual(expectedCaptions);
|
|
81
|
+
});
|
|
82
|
+
|
|
47
83
|
it('green and underlined popon caption data on CC3', () => {
|
|
48
84
|
const controlCount = 0x08;
|
|
49
85
|
const captionData = 0xc0 | controlCount;
|
|
@@ -38,6 +38,10 @@ describe('MediaSourceEngine', () => {
|
|
|
38
38
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
39
39
|
|
|
40
40
|
const originalIsTypeSupported = window.MediaSource.isTypeSupported;
|
|
41
|
+
let originalIsTypeSupportedManaged;
|
|
42
|
+
if (window.ManagedMediaSource) {
|
|
43
|
+
originalIsTypeSupportedManaged = window.ManagedMediaSource.isTypeSupported;
|
|
44
|
+
}
|
|
41
45
|
const originalTextEngine = shaka.text.TextEngine;
|
|
42
46
|
const originalCreateMediaSource =
|
|
43
47
|
// eslint-disable-next-line no-restricted-syntax
|
|
@@ -98,10 +102,20 @@ describe('MediaSourceEngine', () => {
|
|
|
98
102
|
const type = mimeType.split('/')[0];
|
|
99
103
|
return type == 'video' || type == 'audio';
|
|
100
104
|
};
|
|
105
|
+
if (window.ManagedMediaSource) {
|
|
106
|
+
window.ManagedMediaSource.isTypeSupported = (mimeType) => {
|
|
107
|
+
const type = mimeType.split('/')[0];
|
|
108
|
+
return type == 'video' || type == 'audio';
|
|
109
|
+
};
|
|
110
|
+
}
|
|
101
111
|
});
|
|
102
112
|
|
|
103
113
|
afterAll(() => {
|
|
104
114
|
window.MediaSource.isTypeSupported = originalIsTypeSupported;
|
|
115
|
+
if (window.ManagedMediaSource) {
|
|
116
|
+
window.ManagedMediaSource.isTypeSupported =
|
|
117
|
+
originalIsTypeSupportedManaged;
|
|
118
|
+
}
|
|
105
119
|
shaka.transmuxer.TransmuxerEngine.findTransmuxer =
|
|
106
120
|
originalFindTransmuxer;
|
|
107
121
|
shaka.transmuxer.TransmuxerEngine.convertCodecs =
|
|
@@ -206,6 +220,7 @@ describe('MediaSourceEngine', () => {
|
|
|
206
220
|
shaka.media.MediaSourceEngine.createObjectURL;
|
|
207
221
|
const originalRevokeObjectURL = window.URL.revokeObjectURL;
|
|
208
222
|
const originalMediaSource = window.MediaSource;
|
|
223
|
+
const originalManagedMediaSource = window.ManagedMediaSource;
|
|
209
224
|
/** @type {jasmine.Spy} */
|
|
210
225
|
let createObjectURLSpy;
|
|
211
226
|
/** @type {jasmine.Spy} */
|
|
@@ -237,6 +252,9 @@ describe('MediaSourceEngine', () => {
|
|
|
237
252
|
return mockMediaSource;
|
|
238
253
|
});
|
|
239
254
|
window.MediaSource = Util.spyFunc(mediaSourceSpy);
|
|
255
|
+
if (window.ManagedMediaSource) {
|
|
256
|
+
window.ManagedMediaSource = Util.spyFunc(mediaSourceSpy);
|
|
257
|
+
}
|
|
240
258
|
|
|
241
259
|
await mediaSourceEngine.destroy();
|
|
242
260
|
});
|
|
@@ -244,6 +262,7 @@ describe('MediaSourceEngine', () => {
|
|
|
244
262
|
afterAll(() => {
|
|
245
263
|
shaka.media.MediaSourceEngine.createObjectURL = originalCreateObjectURL;
|
|
246
264
|
window.MediaSource = originalMediaSource;
|
|
265
|
+
window.ManagedMediaSource = originalManagedMediaSource;
|
|
247
266
|
window.URL.revokeObjectURL = originalRevokeObjectURL;
|
|
248
267
|
});
|
|
249
268
|
|
|
@@ -270,7 +289,11 @@ describe('MediaSourceEngine', () => {
|
|
|
270
289
|
video,
|
|
271
290
|
new shaka.test.FakeTextDisplayer());
|
|
272
291
|
|
|
273
|
-
|
|
292
|
+
if (window.ManagedMediaSource) {
|
|
293
|
+
expect(mockMediaSource.addEventListener).toHaveBeenCalledTimes(3);
|
|
294
|
+
} else {
|
|
295
|
+
expect(mockMediaSource.addEventListener).toHaveBeenCalledTimes(1);
|
|
296
|
+
}
|
|
274
297
|
expect(mockMediaSource.addEventListener.calls.mostRecent().args[0])
|
|
275
298
|
.toBe('sourceopen');
|
|
276
299
|
expect(typeof onSourceOpenListener).toBe(typeof Function);
|
|
@@ -1223,7 +1246,7 @@ describe('MediaSourceEngine', () => {
|
|
|
1223
1246
|
const callback = addListenOnceSpy.calls.argsFor(0)[2];
|
|
1224
1247
|
callback();
|
|
1225
1248
|
expect(initSourceBufferSpy).toHaveBeenCalled();
|
|
1226
|
-
expect(addListenOnceSpy.calls.argsFor(0)[1]).toBe('
|
|
1249
|
+
expect(addListenOnceSpy.calls.argsFor(0)[1]).toBe('canplaythrough');
|
|
1227
1250
|
expect(video.autoplay).toBe(true);
|
|
1228
1251
|
expect(playSpy).not.toHaveBeenCalled();
|
|
1229
1252
|
} finally {
|
|
@@ -1290,7 +1313,7 @@ describe('MediaSourceEngine', () => {
|
|
|
1290
1313
|
const callback = addListenOnceSpy.calls.argsFor(0)[2];
|
|
1291
1314
|
callback();
|
|
1292
1315
|
expect(initSourceBufferSpy).toHaveBeenCalled();
|
|
1293
|
-
expect(addListenOnceSpy.calls.argsFor(0)[1]).toBe('
|
|
1316
|
+
expect(addListenOnceSpy.calls.argsFor(0)[1]).toBe('canplaythrough');
|
|
1294
1317
|
expect(video.autoplay).toBe(false);
|
|
1295
1318
|
expect(playSpy).toHaveBeenCalled();
|
|
1296
1319
|
} finally {
|
|
@@ -394,12 +394,16 @@ describe('Player Load Graph', () => {
|
|
|
394
394
|
// that the player will behave as expected when media source is missing.
|
|
395
395
|
describe('without media source', () => {
|
|
396
396
|
let mediaSource;
|
|
397
|
+
let managedMediaSource;
|
|
397
398
|
|
|
398
399
|
beforeEach(async () => {
|
|
399
400
|
// Remove our media source support. In order to remove it, we need to set
|
|
400
401
|
// it via [] notation or else closure will stop us.
|
|
401
402
|
mediaSource = window.MediaSource;
|
|
402
403
|
window['MediaSource'] = undefined;
|
|
404
|
+
managedMediaSource = window.ManagedMediaSource;
|
|
405
|
+
window['ManagedMediaSource'] = undefined;
|
|
406
|
+
|
|
403
407
|
|
|
404
408
|
createPlayer();
|
|
405
409
|
await shaka.test.Util.delay(/* seconds= */ 0.25);
|
|
@@ -409,6 +413,7 @@ describe('Player Load Graph', () => {
|
|
|
409
413
|
// Restore our media source support to what it was before. If we did not
|
|
410
414
|
// have support before, this will do nothing.
|
|
411
415
|
window['MediaSource'] = mediaSource;
|
|
416
|
+
window['ManagedMediaSource'] = managedMediaSource;
|
|
412
417
|
});
|
|
413
418
|
|
|
414
419
|
it('attaching ignores init media source flag', async () => {
|
package/test/player_unit.js
CHANGED
|
@@ -12,6 +12,10 @@ describe('Player', () => {
|
|
|
12
12
|
const originalLogWarn = shaka.log.warning;
|
|
13
13
|
const originalLogAlwaysWarn = shaka.log.alwaysWarn;
|
|
14
14
|
const originalIsTypeSupported = window.MediaSource.isTypeSupported;
|
|
15
|
+
let originalIsTypeSupportedManaged;
|
|
16
|
+
if (window.ManagedMediaSource) {
|
|
17
|
+
originalIsTypeSupportedManaged = window.ManagedMediaSource.isTypeSupported;
|
|
18
|
+
}
|
|
15
19
|
const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
|
|
16
20
|
|
|
17
21
|
const fakeManifestUri = 'fake-manifest-uri';
|
|
@@ -67,6 +71,12 @@ describe('Player', () => {
|
|
|
67
71
|
const type = mimeType.split('/')[0];
|
|
68
72
|
return type == 'video' || type == 'audio';
|
|
69
73
|
};
|
|
74
|
+
if (window.ManagedMediaSource) {
|
|
75
|
+
window.ManagedMediaSource.isTypeSupported = (mimeType) => {
|
|
76
|
+
const type = mimeType.split('/')[0];
|
|
77
|
+
return type == 'video' || type == 'audio';
|
|
78
|
+
};
|
|
79
|
+
}
|
|
70
80
|
|
|
71
81
|
// Since this is not an integration test, we don't want MediaCapabilities to
|
|
72
82
|
// fail assertions based on browser support for types. Pretend that all
|
|
@@ -178,6 +188,10 @@ describe('Player', () => {
|
|
|
178
188
|
shaka.log.warning = originalLogWarn;
|
|
179
189
|
shaka.log.alwaysWarn = originalLogAlwaysWarn;
|
|
180
190
|
window.MediaSource.isTypeSupported = originalIsTypeSupported;
|
|
191
|
+
if (window.ManagedMediaSource) {
|
|
192
|
+
window.ManagedMediaSource.isTypeSupported =
|
|
193
|
+
originalIsTypeSupportedManaged;
|
|
194
|
+
}
|
|
181
195
|
shaka.media.ManifestParser.unregisterParserByMime(fakeMimeType);
|
|
182
196
|
navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
|
|
183
197
|
onError.calls.reset();
|
|
@@ -2900,6 +2914,9 @@ describe('Player', () => {
|
|
|
2900
2914
|
describe('unplayable content', () => {
|
|
2901
2915
|
it('throws CONTENT_UNSUPPORTED_BY_BROWSER', async () => {
|
|
2902
2916
|
window.MediaSource.isTypeSupported = (mimeType) => false;
|
|
2917
|
+
if (window.ManagedMediaSource) {
|
|
2918
|
+
window.ManagedMediaSource.isTypeSupported = (mimeType) => false;
|
|
2919
|
+
}
|
|
2903
2920
|
|
|
2904
2921
|
navigator.mediaCapabilities.decodingInfo = async (config) => {
|
|
2905
2922
|
await Promise.resolve();
|