shaka-player 4.6.2 → 4.6.4
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 +22 -0
- package/CONTRIBUTORS +1 -0
- package/demo/common/asset.js +0 -1
- package/demo/common/assets.js +0 -1
- package/demo/custom.js +2 -0
- package/dist/demo.compiled.debug.js +3 -3
- package/dist/demo.compiled.debug.map +1 -1
- package/dist/demo.compiled.js +3 -3
- package/dist/demo.compiled.map +1 -1
- package/dist/deps.js +4 -4
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.map +1 -1
- package/dist/receiver.compiled.map +1 -1
- package/dist/shaka-player.compiled.d.ts +1 -1
- package/dist/shaka-player.compiled.debug.d.ts +1 -1
- package/dist/shaka-player.compiled.debug.externs.js +5 -2
- package/dist/shaka-player.compiled.debug.js +286 -282
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +5 -2
- package/dist/shaka-player.compiled.js +423 -419
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +1 -1
- package/dist/shaka-player.ui.debug.d.ts +1 -1
- package/dist/shaka-player.ui.debug.externs.js +5 -2
- package/dist/shaka-player.ui.debug.js +237 -234
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +5 -2
- package/dist/shaka-player.ui.js +517 -512
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/ui.md +3 -1
- package/externs/lcevc.js +6 -0
- package/externs/shaka/codecs.js +6 -0
- package/externs/shaka/transmuxer.js +6 -0
- package/lib/ads/client_side_ad_manager.js +4 -0
- package/lib/ads/media_tailor_ad_manager.js +5 -0
- package/lib/ads/server_side_ad_manager.js +4 -0
- package/lib/dash/dash_parser.js +2 -2
- package/lib/hls/hls_parser.js +19 -6
- package/lib/media/drm_engine.js +0 -3
- package/lib/media/media_source_engine.js +11 -5
- package/lib/media/streaming_engine.js +3 -2
- package/lib/media/video_wrapper.js +4 -3
- package/lib/player.js +4 -6
- package/lib/text/simple_text_displayer.js +8 -7
- package/lib/text/text_utils.js +39 -13
- package/lib/text/ui_text_displayer.js +70 -15
- package/lib/text/web_vtt_generator.js +1 -1
- package/lib/util/mp4_box_parsers.js +39 -5
- package/package.json +1 -1
- package/test/media/drm_engine_unit.js +27 -0
- package/test/test/assets/audio-xhe-aac.mp4 +0 -0
- package/test/test/util/layout_tests.js +2 -1
- package/test/text/simple_text_displayer_unit.js +1 -1
- package/test/text/web_vtt_generator_unit.js +3 -3
- package/test/transmuxer/transmuxer_integration.js +3 -3
- package/test/util/mp4_box_parsers_unit.js +36 -0
package/docs/tutorials/ui.md
CHANGED
|
@@ -204,11 +204,13 @@ constructor.
|
|
|
204
204
|
// "local" because it is for local playback only, as opposed to the player proxy
|
|
205
205
|
// object, which will route your calls to the ChromeCast receiver as necessary.
|
|
206
206
|
const localPlayer = new shaka.Player();
|
|
207
|
-
await localPlayer.attach(videoElement);
|
|
208
207
|
// "Overlay" because the UI will add DOM elements inside the container,
|
|
209
208
|
// to visually overlay the video element
|
|
210
209
|
const ui = new shaka.ui.Overlay(localPlayer, videoContainerElement,
|
|
211
210
|
videoElement);
|
|
211
|
+
// Now that the player has been configured to be part of a UI, attach it to the
|
|
212
|
+
// video.
|
|
213
|
+
await localPlayer.attach(videoElement);
|
|
212
214
|
|
|
213
215
|
// As with DOM-based setup, get access to the UI controls and player from the
|
|
214
216
|
// UI.
|
package/externs/lcevc.js
CHANGED
package/externs/shaka/codecs.js
CHANGED
|
@@ -13,6 +13,10 @@ goog.provide('shaka.ads.ClientSideAdManager');
|
|
|
13
13
|
|
|
14
14
|
goog.require('goog.asserts');
|
|
15
15
|
goog.require('shaka.ads.ClientSideAd');
|
|
16
|
+
goog.require('shaka.log');
|
|
17
|
+
goog.require('shaka.util.Dom');
|
|
18
|
+
goog.require('shaka.util.EventManager');
|
|
19
|
+
goog.require('shaka.util.FakeEvent');
|
|
16
20
|
goog.require('shaka.util.IReleasable');
|
|
17
21
|
|
|
18
22
|
/**
|
|
@@ -15,7 +15,12 @@ goog.require('goog.asserts');
|
|
|
15
15
|
goog.require('shaka.ads.MediaTailorAd');
|
|
16
16
|
goog.require('shaka.log');
|
|
17
17
|
goog.require('shaka.net.NetworkingEngine');
|
|
18
|
+
goog.require('goog.Uri');
|
|
19
|
+
goog.require('shaka.util.EventManager');
|
|
20
|
+
goog.require('shaka.util.Error');
|
|
21
|
+
goog.require('shaka.util.FakeEvent');
|
|
18
22
|
goog.require('shaka.util.IReleasable');
|
|
23
|
+
goog.require('shaka.util.PublicPromise');
|
|
19
24
|
goog.require('shaka.util.StringUtils');
|
|
20
25
|
|
|
21
26
|
|
|
@@ -14,7 +14,11 @@ goog.provide('shaka.ads.ServerSideAdManager');
|
|
|
14
14
|
goog.require('goog.asserts');
|
|
15
15
|
goog.require('shaka.ads.ServerSideAd');
|
|
16
16
|
goog.require('shaka.log');
|
|
17
|
+
goog.require('shaka.util.EventManager');
|
|
18
|
+
goog.require('shaka.util.Error');
|
|
19
|
+
goog.require('shaka.util.FakeEvent');
|
|
17
20
|
goog.require('shaka.util.IReleasable');
|
|
21
|
+
goog.require('shaka.util.PublicPromise');
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
/**
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -1433,11 +1433,11 @@ shaka.dash.DashParser = class {
|
|
|
1433
1433
|
'Must have Segment* with non-text streams.');
|
|
1434
1434
|
|
|
1435
1435
|
const duration = context.periodInfo.duration || 0;
|
|
1436
|
+
const getBaseUris = context.representation.getBaseUris;
|
|
1436
1437
|
streamInfo = {
|
|
1437
1438
|
generateSegmentIndex: () => {
|
|
1438
|
-
const baseUris = context.representation.getBaseUris();
|
|
1439
1439
|
return Promise.resolve(shaka.media.SegmentIndex.forSingleSegment(
|
|
1440
|
-
periodStart, duration,
|
|
1440
|
+
periodStart, duration, getBaseUris()));
|
|
1441
1441
|
},
|
|
1442
1442
|
};
|
|
1443
1443
|
}
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -934,7 +934,8 @@ shaka.hls.HlsParser = class {
|
|
|
934
934
|
return basicInfo;
|
|
935
935
|
}
|
|
936
936
|
} else if (extension == 'mp4' || extension == 'cmfv' ||
|
|
937
|
-
|
|
937
|
+
extension == 'm4s' || contentMimeType == 'video/mp4' ||
|
|
938
|
+
contentMimeType == 'audio/mp4' ||
|
|
938
939
|
contentMimeType == 'video/iso.segment') {
|
|
939
940
|
const basicInfo = shaka.media.SegmentUtils.getBasicInfoFromMp4(
|
|
940
941
|
initData, response.data);
|
|
@@ -3752,14 +3753,26 @@ shaka.hls.HlsParser = class {
|
|
|
3752
3753
|
|
|
3753
3754
|
// If unable to guess mime type, request a segment and try getting it
|
|
3754
3755
|
// from the response.
|
|
3756
|
+
let contentMimeType;
|
|
3757
|
+
const type = shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
|
|
3755
3758
|
const headRequest = shaka.net.NetworkingEngine.makeRequest(
|
|
3756
3759
|
middleSegmentUris, this.config_.retryParameters);
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3760
|
+
try {
|
|
3761
|
+
headRequest.method = 'HEAD';
|
|
3762
|
+
const response = await this.makeNetworkRequest_(
|
|
3763
|
+
headRequest, requestType, {type});
|
|
3764
|
+
|
|
3765
|
+
contentMimeType = response.headers['content-type'];
|
|
3766
|
+
} catch (error) {
|
|
3767
|
+
if (error && error.code == shaka.util.Error.Code.HTTP_ERROR) {
|
|
3768
|
+
headRequest.method = 'GET';
|
|
3769
|
+
const response = await this.makeNetworkRequest_(
|
|
3770
|
+
headRequest, requestType, {type});
|
|
3771
|
+
|
|
3772
|
+
contentMimeType = response.headers['content-type'];
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3761
3775
|
|
|
3762
|
-
const contentMimeType = response.headers['content-type'];
|
|
3763
3776
|
if (contentMimeType) {
|
|
3764
3777
|
// Split the MIME type in case the server sent additional parameters.
|
|
3765
3778
|
return contentMimeType.split(';')[0];
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -2038,9 +2038,6 @@ shaka.media.DrmEngine = class {
|
|
|
2038
2038
|
|
|
2039
2039
|
for (const drm1 of drms1) {
|
|
2040
2040
|
for (const drm2 of drms2) {
|
|
2041
|
-
// This method is only called to compare drmInfos of a video and an
|
|
2042
|
-
// audio adaptations, so we shouldn't have to worry about checking
|
|
2043
|
-
// robustness.
|
|
2044
2041
|
if (drm1.keySystem == drm2.keySystem) {
|
|
2045
2042
|
const initDataMap = new Map();
|
|
2046
2043
|
const bothInitDatas = (drm1.initData || [])
|
|
@@ -1661,6 +1661,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1661
1661
|
if (shaka.media.Capabilities.isChangeTypeSupported()) {
|
|
1662
1662
|
if (this.transmuxers_[contentType]) {
|
|
1663
1663
|
this.transmuxers_[contentType].destroy();
|
|
1664
|
+
delete this.transmuxers_[contentType];
|
|
1664
1665
|
}
|
|
1665
1666
|
if (transmuxer) {
|
|
1666
1667
|
this.transmuxers_[contentType] = transmuxer;
|
|
@@ -1859,8 +1860,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1859
1860
|
}
|
|
1860
1861
|
}
|
|
1861
1862
|
|
|
1862
|
-
const
|
|
1863
|
-
|
|
1863
|
+
const newAllCodecs = MimeUtils.getCodecs(newMimeType);
|
|
1864
|
+
const newCodec = MimeUtils.getCodecBase(newAllCodecs);
|
|
1864
1865
|
const newBasicType = MimeUtils.getBasicType(newMimeType);
|
|
1865
1866
|
|
|
1866
1867
|
// Current/new codecs base and basic type match then no need to switch
|
|
@@ -1868,10 +1869,15 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1868
1869
|
return false;
|
|
1869
1870
|
}
|
|
1870
1871
|
|
|
1871
|
-
|
|
1872
|
+
let allowChangeType = true;
|
|
1873
|
+
if (this.needSplitMuxedContent_ || (newAllCodecs.split(',').length &&
|
|
1874
|
+
transmuxer && !this.transmuxers_[contentType])) {
|
|
1875
|
+
allowChangeType = false;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
if (allowChangeType && this.config_.codecSwitchingStrategy ===
|
|
1872
1879
|
shaka.config.CodecSwitchingStrategy.SMOOTH &&
|
|
1873
|
-
shaka.media.Capabilities.isChangeTypeSupported()
|
|
1874
|
-
!this.needSplitMuxedContent_) {
|
|
1880
|
+
shaka.media.Capabilities.isChangeTypeSupported()) {
|
|
1875
1881
|
await this.changeType(contentType, newMimeType, transmuxer);
|
|
1876
1882
|
} else {
|
|
1877
1883
|
if (transmuxer) {
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @fileoverview
|
|
9
|
-
* @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
goog.provide('shaka.media.StreamingEngine');
|
|
@@ -14,12 +13,14 @@ goog.provide('shaka.media.StreamingEngine');
|
|
|
14
13
|
goog.require('goog.asserts');
|
|
15
14
|
goog.require('shaka.log');
|
|
16
15
|
goog.require('shaka.media.InitSegmentReference');
|
|
16
|
+
goog.require('shaka.media.ManifestParser');
|
|
17
17
|
goog.require('shaka.media.MediaSourceEngine');
|
|
18
18
|
goog.require('shaka.media.SegmentIterator');
|
|
19
19
|
goog.require('shaka.media.SegmentReference');
|
|
20
20
|
goog.require('shaka.media.SegmentPrefetch');
|
|
21
21
|
goog.require('shaka.net.Backoff');
|
|
22
22
|
goog.require('shaka.net.NetworkingEngine');
|
|
23
|
+
goog.require('shaka.util.BufferUtils');
|
|
23
24
|
goog.require('shaka.util.DelayedTick');
|
|
24
25
|
goog.require('shaka.util.Destroyer');
|
|
25
26
|
goog.require('shaka.util.Error');
|
|
@@ -28,6 +29,7 @@ goog.require('shaka.util.IDestroyable');
|
|
|
28
29
|
goog.require('shaka.util.Id3Utils');
|
|
29
30
|
goog.require('shaka.util.ManifestParserUtils');
|
|
30
31
|
goog.require('shaka.util.MimeUtils');
|
|
32
|
+
goog.require('shaka.util.Mp4BoxParsers');
|
|
31
33
|
goog.require('shaka.util.Mp4Parser');
|
|
32
34
|
goog.require('shaka.util.Networking');
|
|
33
35
|
|
|
@@ -2203,7 +2205,6 @@ shaka.media.StreamingEngine = class {
|
|
|
2203
2205
|
*
|
|
2204
2206
|
* @return {!Promise.<BufferSource>}
|
|
2205
2207
|
* @private
|
|
2206
|
-
* @suppress {strictMissingProperties}
|
|
2207
2208
|
*/
|
|
2208
2209
|
async fetch_(mediaState, reference, streamDataCallback) {
|
|
2209
2210
|
if (reference instanceof shaka.media.InitSegmentReference) {
|
|
@@ -183,8 +183,8 @@ shaka.media.VideoWrapper = class {
|
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* A class used to move the playhead away from its current time. Sometimes,
|
|
186
|
-
* Edge ignores re-seeks. After changing the current time, check every
|
|
187
|
-
* retrying if the change was not accepted.
|
|
186
|
+
* legacy Edge ignores re-seeks. After changing the current time, check every
|
|
187
|
+
* 100ms, retrying if the change was not accepted.
|
|
188
188
|
*
|
|
189
189
|
* Delay stats over 100 runs of a re-seeking integration test:
|
|
190
190
|
* Edge - 0ms - 2%
|
|
@@ -194,7 +194,8 @@ shaka.media.VideoWrapper = class {
|
|
|
194
194
|
* Edge - 400ms - 2%
|
|
195
195
|
* Chrome - 0ms - 100%
|
|
196
196
|
*
|
|
197
|
-
*
|
|
197
|
+
* Unfortunately, legacy Edge is not receiving updates anymore, but it still
|
|
198
|
+
* must be supported as it is used for web browsers on XBox.
|
|
198
199
|
*
|
|
199
200
|
* @implements {shaka.util.IReleasable}
|
|
200
201
|
* @final
|
package/lib/player.js
CHANGED
|
@@ -5144,7 +5144,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5144
5144
|
} else {
|
|
5145
5145
|
// eslint-disable-next-line no-restricted-syntax
|
|
5146
5146
|
if (HTMLMediaElement.prototype.addTextTrack) {
|
|
5147
|
-
return new shaka.text.SimpleTextDisplayer(
|
|
5147
|
+
return new shaka.text.SimpleTextDisplayer(
|
|
5148
|
+
this.video_, shaka.Player.TextTrackLabel);
|
|
5148
5149
|
} else {
|
|
5149
5150
|
shaka.log.warning('Text tracks are not supported by the ' +
|
|
5150
5151
|
'browser, disabling.');
|
|
@@ -6386,6 +6387,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6386
6387
|
} // if (keyIds.size)
|
|
6387
6388
|
|
|
6388
6389
|
if (tracksChanged) {
|
|
6390
|
+
this.onTracksChanged_();
|
|
6389
6391
|
const variantsUpdated = this.updateAbrManagerVariants_();
|
|
6390
6392
|
if (!variantsUpdated) {
|
|
6391
6393
|
return;
|
|
@@ -6397,10 +6399,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6397
6399
|
shaka.log.debug('Choosing new streams after key status changed');
|
|
6398
6400
|
this.chooseVariantAndSwitch_();
|
|
6399
6401
|
}
|
|
6400
|
-
|
|
6401
|
-
if (tracksChanged) {
|
|
6402
|
-
this.onTracksChanged_();
|
|
6403
|
-
}
|
|
6404
6402
|
}
|
|
6405
6403
|
|
|
6406
6404
|
/**
|
|
@@ -6781,7 +6779,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6781
6779
|
* @export
|
|
6782
6780
|
*/
|
|
6783
6781
|
// eslint-disable-next-line no-useless-concat
|
|
6784
|
-
shaka.Player.version = 'v4.6.
|
|
6782
|
+
shaka.Player.version = 'v4.6.4' + '-uncompiled'; // x-release-please-version
|
|
6785
6783
|
|
|
6786
6784
|
// Initialize the deprecation system using the version string we just set
|
|
6787
6785
|
// on the player.
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @fileoverview
|
|
9
|
-
* @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
goog.provide('shaka.text.SimpleTextDisplayer');
|
|
@@ -24,8 +23,11 @@ goog.require('shaka.text.Utils');
|
|
|
24
23
|
* @export
|
|
25
24
|
*/
|
|
26
25
|
shaka.text.SimpleTextDisplayer = class {
|
|
27
|
-
/**
|
|
28
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @param {HTMLMediaElement} video
|
|
28
|
+
* @param {string} label
|
|
29
|
+
*/
|
|
30
|
+
constructor(video, label) {
|
|
29
31
|
/** @private {TextTrack} */
|
|
30
32
|
this.textTrack_ = null;
|
|
31
33
|
|
|
@@ -39,7 +41,7 @@ shaka.text.SimpleTextDisplayer = class {
|
|
|
39
41
|
// element.
|
|
40
42
|
track.mode = 'disabled';
|
|
41
43
|
|
|
42
|
-
if (track.label ==
|
|
44
|
+
if (track.label == label) {
|
|
43
45
|
this.textTrack_ = track;
|
|
44
46
|
}
|
|
45
47
|
}
|
|
@@ -49,8 +51,7 @@ shaka.text.SimpleTextDisplayer = class {
|
|
|
49
51
|
// kind to 'subtitles' or 'captions' when creating the TextTrack object.
|
|
50
52
|
// The individual text tracks from the manifest will still have their own
|
|
51
53
|
// kinds which can be displayed in the app's UI.
|
|
52
|
-
this.textTrack_ = video.addTextTrack(
|
|
53
|
-
'subtitles', shaka.Player.TextTrackLabel);
|
|
54
|
+
this.textTrack_ = video.addTextTrack('subtitles', label);
|
|
54
55
|
}
|
|
55
56
|
this.textTrack_.mode = 'hidden';
|
|
56
57
|
}
|
|
@@ -80,7 +81,7 @@ shaka.text.SimpleTextDisplayer = class {
|
|
|
80
81
|
* @export
|
|
81
82
|
*/
|
|
82
83
|
append(cues) {
|
|
83
|
-
const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues
|
|
84
|
+
const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues);
|
|
84
85
|
|
|
85
86
|
// Convert cues.
|
|
86
87
|
const textTrackCues = [];
|
package/lib/text/text_utils.js
CHANGED
|
@@ -15,16 +15,19 @@ shaka.text.Utils = class {
|
|
|
15
15
|
* their contents should be combined and replace the payload of the parent.
|
|
16
16
|
*
|
|
17
17
|
* @param {!shaka.text.Cue} cue
|
|
18
|
+
* @param {?shaka.text.Cue=} parentCue
|
|
18
19
|
* @return {string}
|
|
19
20
|
* @private
|
|
20
21
|
*/
|
|
21
|
-
static flattenPayload_(cue) {
|
|
22
|
+
static flattenPayload_(cue, parentCue) {
|
|
22
23
|
if (cue.lineBreak) {
|
|
23
24
|
// This is a vertical lineBreak, so insert a newline.
|
|
24
25
|
return '\n';
|
|
25
26
|
}
|
|
26
27
|
if (cue.nestedCues.length) {
|
|
27
|
-
return cue.nestedCues.map(
|
|
28
|
+
return cue.nestedCues.map((nested) => {
|
|
29
|
+
return shaka.text.Utils.flattenPayload_(nested, cue);
|
|
30
|
+
}).join('');
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
// Handle bold, italics and underline
|
|
@@ -43,14 +46,22 @@ shaka.text.Utils = class {
|
|
|
43
46
|
openStyleTags.push(['u']);
|
|
44
47
|
}
|
|
45
48
|
// Handle color classes, if the value consists of letters
|
|
49
|
+
let color = cue.color;
|
|
50
|
+
if (color == '' && parentCue) {
|
|
51
|
+
color = parentCue.color;
|
|
52
|
+
}
|
|
46
53
|
let classes = '';
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
classes += `.${
|
|
54
|
+
const colorName = shaka.text.Utils.getColorName_(color);
|
|
55
|
+
if (colorName) {
|
|
56
|
+
classes += `.${colorName}`;
|
|
57
|
+
}
|
|
58
|
+
let bgColor = cue.backgroundColor;
|
|
59
|
+
if (bgColor == '' && parentCue) {
|
|
60
|
+
bgColor = parentCue.backgroundColor;
|
|
50
61
|
}
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
-
classes += `.bg_${
|
|
62
|
+
const bgColorName = shaka.text.Utils.getColorName_(bgColor);
|
|
63
|
+
if (bgColorName) {
|
|
64
|
+
classes += `.bg_${bgColorName}`;
|
|
54
65
|
}
|
|
55
66
|
if (classes) {
|
|
56
67
|
openStyleTags.push(['c', classes]);
|
|
@@ -69,7 +80,20 @@ shaka.text.Utils = class {
|
|
|
69
80
|
* @private
|
|
70
81
|
*/
|
|
71
82
|
static getColorName_(string) {
|
|
72
|
-
|
|
83
|
+
let colorString = string.toLowerCase();
|
|
84
|
+
const rgb = colorString.replace(/\s/g, '')
|
|
85
|
+
.match(/^rgba?\((\d+),(\d+),(\d+),?([^,\s)]+)?/i);
|
|
86
|
+
if (rgb) {
|
|
87
|
+
colorString = '#' +
|
|
88
|
+
(parseInt(rgb[1], 10) | (1 << 8)).toString(16).slice(1) +
|
|
89
|
+
(parseInt(rgb[2], 10) | (1 << 8)).toString(16).slice(1) +
|
|
90
|
+
(parseInt(rgb[3], 10) | (1 << 8)).toString(16).slice(1);
|
|
91
|
+
} else if (colorString.startsWith('#') && colorString.length > 7) {
|
|
92
|
+
// With this we lose the alpha of the color, but it is better than having
|
|
93
|
+
// no color.
|
|
94
|
+
colorString = colorString.slice(0, 7);
|
|
95
|
+
}
|
|
96
|
+
switch (colorString) {
|
|
73
97
|
case 'white':
|
|
74
98
|
case '#fff':
|
|
75
99
|
case '#ffffff':
|
|
@@ -92,6 +116,7 @@ shaka.text.Utils = class {
|
|
|
92
116
|
return 'yellow';
|
|
93
117
|
case 'magenta':
|
|
94
118
|
case '#f0f':
|
|
119
|
+
case '#ff00ff':
|
|
95
120
|
return 'magenta';
|
|
96
121
|
case 'blue':
|
|
97
122
|
case '#00f':
|
|
@@ -118,19 +143,20 @@ shaka.text.Utils = class {
|
|
|
118
143
|
* isContainer = false.
|
|
119
144
|
*
|
|
120
145
|
* @param {!Array.<!shaka.text.Cue>} cues
|
|
121
|
-
* @param {
|
|
146
|
+
* @param {?shaka.text.Cue=} parentCue
|
|
122
147
|
* @return {!Array.<!shaka.text.Cue>}
|
|
123
148
|
*/
|
|
124
|
-
static getCuesToFlatten(cues,
|
|
149
|
+
static getCuesToFlatten(cues, parentCue) {
|
|
150
|
+
const result = [];
|
|
125
151
|
for (const cue of cues) {
|
|
126
152
|
if (cue.isContainer) {
|
|
127
153
|
// Recurse to find the actual text payload cues.
|
|
128
|
-
shaka.text.Utils.getCuesToFlatten(cue.nestedCues,
|
|
154
|
+
result.push(...shaka.text.Utils.getCuesToFlatten(cue.nestedCues, cue));
|
|
129
155
|
} else {
|
|
130
156
|
// Flatten the payload.
|
|
131
157
|
const flatCue = cue.clone();
|
|
132
158
|
flatCue.nestedCues = [];
|
|
133
|
-
flatCue.payload = shaka.text.Utils.flattenPayload_(cue);
|
|
159
|
+
flatCue.payload = shaka.text.Utils.flattenPayload_(cue, parentCue);
|
|
134
160
|
result.push(flatCue);
|
|
135
161
|
}
|
|
136
162
|
}
|
|
@@ -44,6 +44,9 @@ shaka.text.UITextDisplayer = class {
|
|
|
44
44
|
/** @private {HTMLElement} */
|
|
45
45
|
this.videoContainer_ = videoContainer;
|
|
46
46
|
|
|
47
|
+
/** @private {?number} */
|
|
48
|
+
this.aspectRatio_ = null;
|
|
49
|
+
|
|
47
50
|
/** @type {HTMLElement} */
|
|
48
51
|
this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
|
|
49
52
|
this.textContainer_.classList.add('shaka-text-container');
|
|
@@ -90,6 +93,23 @@ shaka.text.UITextDisplayer = class {
|
|
|
90
93
|
this.updateCaptions_(/* forceUpdate= */ true);
|
|
91
94
|
});
|
|
92
95
|
|
|
96
|
+
// From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
|
|
97
|
+
// Whenever the natural width or natural height of the video changes
|
|
98
|
+
// (including, for example, because the selected video track was changed),
|
|
99
|
+
// if the element's readyState attribute is not HAVE_NOTHING, the user
|
|
100
|
+
// agent must queue a media element task given the media element to fire an
|
|
101
|
+
// event named resize at the media element.
|
|
102
|
+
this.eventManager_.listen(this.video_, 'resize', () => {
|
|
103
|
+
const element = /** @type {!HTMLVideoElement} */ (this.video_);
|
|
104
|
+
const width = element.videoWidth;
|
|
105
|
+
const height = element.videoHeight;
|
|
106
|
+
if (width && height) {
|
|
107
|
+
this.aspectRatio_ = width / height;
|
|
108
|
+
} else {
|
|
109
|
+
this.aspectRatio_ = null;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
93
113
|
/** @private {ResizeObserver} */
|
|
94
114
|
this.resizeObserver_ = null;
|
|
95
115
|
if ('ResizeObserver' in window) {
|
|
@@ -400,6 +420,10 @@ shaka.text.UITextDisplayer = class {
|
|
|
400
420
|
*/
|
|
401
421
|
getRegionElement_(cue) {
|
|
402
422
|
const region = cue.region;
|
|
423
|
+
// from https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#caption-window-size
|
|
424
|
+
// if aspect ratio is 4/3, use that value, otherwise, use the 16:9 value
|
|
425
|
+
const lineWidthMultiple = this.aspectRatio_ === 4/3 ? 2.5 : 1.9;
|
|
426
|
+
const lineHeightMultiple = 5.33;
|
|
403
427
|
|
|
404
428
|
const regionId = this.generateRegionId_(region);
|
|
405
429
|
if (this.regionElements_.has(regionId)) {
|
|
@@ -417,11 +441,33 @@ shaka.text.UITextDisplayer = class {
|
|
|
417
441
|
regionElement.id = 'shaka-text-region---' + regionId;
|
|
418
442
|
regionElement.classList.add('shaka-text-region');
|
|
419
443
|
|
|
420
|
-
regionElement.style.height = region.height + heightUnit;
|
|
421
|
-
regionElement.style.width = region.width + widthUnit;
|
|
422
444
|
regionElement.style.position = 'absolute';
|
|
423
|
-
|
|
424
|
-
|
|
445
|
+
|
|
446
|
+
const linesUnit = shaka.text.CueRegion.units.LINES;
|
|
447
|
+
if (region.heightUnits === linesUnit && region.widthUnits === linesUnit) {
|
|
448
|
+
regionElement.style.height = region.height * lineHeightMultiple + '%';
|
|
449
|
+
regionElement.style.width = region.width * lineWidthMultiple + '%';
|
|
450
|
+
} else {
|
|
451
|
+
regionElement.style.height = region.height + heightUnit;
|
|
452
|
+
regionElement.style.width = region.width + widthUnit;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (region.viewportAnchorUnits === linesUnit) {
|
|
456
|
+
// from https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-708
|
|
457
|
+
let top = region.viewportAnchorY / 75 * 100;
|
|
458
|
+
const windowWidth = this.aspectRatio_ === 4/3 ? 160 : 210;
|
|
459
|
+
let left = region.viewportAnchorX / windowWidth * 100;
|
|
460
|
+
// adjust top and left values based on the region anchor and window size
|
|
461
|
+
top -= region.regionAnchorY * region.height * lineHeightMultiple / 100;
|
|
462
|
+
left -= region.regionAnchorX * region.width * lineWidthMultiple / 100;
|
|
463
|
+
regionElement.style.top = top + '%';
|
|
464
|
+
regionElement.style.left = left + '%';
|
|
465
|
+
} else {
|
|
466
|
+
regionElement.style.top = region.viewportAnchorY -
|
|
467
|
+
region.regionAnchorY * region.height / 100 + viewportAnchorUnit;
|
|
468
|
+
regionElement.style.left = region.viewportAnchorX -
|
|
469
|
+
region.regionAnchorX * region.width / 100 + viewportAnchorUnit;
|
|
470
|
+
}
|
|
425
471
|
|
|
426
472
|
regionElement.style.display = 'flex';
|
|
427
473
|
regionElement.style.flexDirection = 'column';
|
|
@@ -661,37 +707,46 @@ shaka.text.UITextDisplayer = class {
|
|
|
661
707
|
// (for vertical growing left) is aligned at the line.
|
|
662
708
|
// TODO: Implement line alignment with line number.
|
|
663
709
|
// TODO: Implement lineAlignment of 'CENTER'.
|
|
664
|
-
|
|
710
|
+
let line = cue.line;
|
|
711
|
+
if (line != null) {
|
|
665
712
|
let lineInterpretation = cue.lineInterpretation;
|
|
666
713
|
// HACK: the current implementation of UITextDisplayer only handled
|
|
667
|
-
// PERCENTAGE,
|
|
668
|
-
|
|
669
|
-
if (cue.line == 0 &&
|
|
670
|
-
lineInterpretation == Cue.lineInterpretation.LINE_NUMBER) {
|
|
714
|
+
// PERCENTAGE, so we need convert LINE_NUMBER to PERCENTAGE
|
|
715
|
+
if (lineInterpretation == Cue.lineInterpretation.LINE_NUMBER) {
|
|
671
716
|
lineInterpretation = Cue.lineInterpretation.PERCENTAGE;
|
|
717
|
+
let maxLines = 16;
|
|
718
|
+
// The maximum number of lines is different if it is a vertical video.
|
|
719
|
+
if (this.aspectRatio_ && this.aspectRatio_ < 1) {
|
|
720
|
+
maxLines = 32;
|
|
721
|
+
}
|
|
722
|
+
if (line < 0) {
|
|
723
|
+
line = 100 + line / maxLines * 100;
|
|
724
|
+
} else {
|
|
725
|
+
line = line / maxLines * 100;
|
|
726
|
+
}
|
|
672
727
|
}
|
|
673
728
|
if (lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
|
|
674
729
|
style.position = 'absolute';
|
|
675
730
|
if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
|
|
676
731
|
style.width = '100%';
|
|
677
732
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
678
|
-
style.top =
|
|
733
|
+
style.top = line + '%';
|
|
679
734
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
680
|
-
style.bottom = (100 -
|
|
735
|
+
style.bottom = (100 - line) + '%';
|
|
681
736
|
}
|
|
682
737
|
} else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
|
|
683
738
|
style.height = '100%';
|
|
684
739
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
685
|
-
style.left =
|
|
740
|
+
style.left = line + '%';
|
|
686
741
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
687
|
-
style.right = (100 -
|
|
742
|
+
style.right = (100 - line) + '%';
|
|
688
743
|
}
|
|
689
744
|
} else {
|
|
690
745
|
style.height = '100%';
|
|
691
746
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
692
|
-
style.right =
|
|
747
|
+
style.right = line + '%';
|
|
693
748
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
694
|
-
style.left = (100 -
|
|
749
|
+
style.left = (100 - line) + '%';
|
|
695
750
|
}
|
|
696
751
|
}
|
|
697
752
|
}
|
|
@@ -40,7 +40,7 @@ shaka.text.WebVttGenerator = class {
|
|
|
40
40
|
milliseconds;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues
|
|
43
|
+
const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues);
|
|
44
44
|
|
|
45
45
|
let webvttString = 'WEBVTT\n\n';
|
|
46
46
|
for (const cue of flattenedCues) {
|
|
@@ -301,11 +301,45 @@ shaka.util.Mp4BoxParsers = class {
|
|
|
301
301
|
*/
|
|
302
302
|
static parseESDS(reader) {
|
|
303
303
|
let codec = 'mp4a';
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
304
|
+
let tag;
|
|
305
|
+
let oti;
|
|
306
|
+
while (reader.hasMoreData()) {
|
|
307
|
+
tag = reader.readUint8();
|
|
308
|
+
let byteRead = reader.readUint8();
|
|
309
|
+
while (byteRead & 0x80) {
|
|
310
|
+
byteRead = reader.readUint8();
|
|
311
|
+
}
|
|
312
|
+
if (tag == 0x03) {
|
|
313
|
+
reader.readUint16();
|
|
314
|
+
const flags = reader.readUint8();
|
|
315
|
+
if (flags & 0x80) {
|
|
316
|
+
reader.readUint16();
|
|
317
|
+
}
|
|
318
|
+
if (flags & 0x40) {
|
|
319
|
+
reader.skip(reader.readUint8());
|
|
320
|
+
}
|
|
321
|
+
if (flags & 0x20) {
|
|
322
|
+
reader.readUint16();
|
|
323
|
+
}
|
|
324
|
+
} else if (tag == 0x04) {
|
|
325
|
+
oti = reader.readUint8();
|
|
326
|
+
reader.skip(12);
|
|
327
|
+
} else if (tag == 0x05) {
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (oti) {
|
|
332
|
+
codec += '.' + this.toHex_(oti);
|
|
333
|
+
if (tag == 0x05 && reader.hasMoreData()) {
|
|
334
|
+
const firstData = reader.readUint8();
|
|
335
|
+
let audioObjectType = (firstData & 0xF8) >> 3;
|
|
336
|
+
if (audioObjectType === 31 && reader.hasMoreData()) {
|
|
337
|
+
audioObjectType = 32 + ((firstData & 0x7) << 3) +
|
|
338
|
+
((reader.readUint8() & 0xE0) >> 5);
|
|
339
|
+
}
|
|
340
|
+
codec += '.' + audioObjectType;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
309
343
|
return {codec};
|
|
310
344
|
}
|
|
311
345
|
|