shaka-player 3.2.15 → 3.2.17
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 +13 -0
- package/AUTHORS +2 -0
- package/CHANGELOG.md +28 -0
- package/CONTRIBUTORS +2 -0
- package/dist/deps.js +1 -1
- package/dist/locales.js +30 -30
- package/dist/shaka-player.compiled.debug.js +184 -176
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +707 -704
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.debug.js +221 -213
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +820 -818
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/welcome.md +2 -2
- package/externs/shaka/player.js +2 -1
- package/lib/ads/ad_manager.js +2 -2
- package/lib/cea/cea708_window.js +2 -2
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +8 -1
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/streaming_engine.js +20 -8
- package/lib/player.js +11 -2
- package/lib/polyfill/media_capabilities.js +59 -9
- package/lib/text/ttml_text_parser.js +32 -7
- package/lib/text/ui_text_displayer.js +22 -5
- package/lib/util/platform.js +21 -2
- package/lib/util/stream_utils.js +30 -10
- package/package.json +1 -1
- package/test/dash/dash_parser_manifest_unit.js +1 -0
- package/test/hls/hls_parser_unit.js +1 -0
- package/test/player_integration.js +10 -3
- package/test/polyfill/media_capabilities_unit.js +261 -0
- package/test/test/boot.js +29 -11
- package/test/test/util/test_scheme.js +2 -2
- package/test/text/mp4_ttml_parser_unit.js +4 -4
- package/test/text/ttml_text_parser_unit.js +117 -84
- package/test/text/ui_text_displayer_unit.js +76 -0
- package/test/ui/text_displayer_layout_unit.js +14 -0
- package/test/util/stream_utils_unit.js +18 -0
- package/test/util/xml_utils_unit.js +6 -1
- package/ui/controls.js +1 -1
|
@@ -29,8 +29,8 @@ You can build Shaka on Linux, Windows, or Mac.
|
|
|
29
29
|
To get the sources and compile the library, you will need:
|
|
30
30
|
* {@link https://git-scm.com/downloads Git v1.9+}
|
|
31
31
|
* {@link https://www.python.org/downloads/ Python v2.7 or v3.5+}
|
|
32
|
-
* {@link https://java.com/en/download/ Java Runtime Environment
|
|
33
|
-
* {@link https://nodejs.org/en/download/ NodeJS
|
|
32
|
+
* {@link https://java.com/en/download/ Java Runtime Environment v14+}
|
|
33
|
+
* {@link https://nodejs.org/en/download/ NodeJS v14+}
|
|
34
34
|
* A local web server, such as {@link https://httpd.apache.org/ Apache}
|
|
35
35
|
* _NOTE: A local web server is required because browsers place restrictions
|
|
36
36
|
on applications from file:/// URLs._
|
package/externs/shaka/player.js
CHANGED
|
@@ -123,7 +123,8 @@ shaka.extern.StateChange;
|
|
|
123
123
|
* @property {number} manifestTimeSeconds
|
|
124
124
|
* The amount of time it took to download and parse the manifest.
|
|
125
125
|
* @property {number} drmTimeSeconds
|
|
126
|
-
* The amount of time it took to download the first drm key
|
|
126
|
+
* The amount of time it took to download the first drm key, and load that key
|
|
127
|
+
* into the drm system.
|
|
127
128
|
* @property {number} playTime
|
|
128
129
|
* The total time spent in a playing state in seconds.
|
|
129
130
|
* @property {number} pauseTime
|
package/lib/ads/ad_manager.js
CHANGED
|
@@ -19,8 +19,8 @@ goog.require('shaka.util.IReleasable');
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* @event shaka.ads.AdManager.
|
|
23
|
-
* @description Fired when
|
|
22
|
+
* @event shaka.ads.AdManager.AdsLoadedEvent
|
|
23
|
+
* @description Fired when a sequence of ads has been loaded.
|
|
24
24
|
* @property {string} type
|
|
25
25
|
* 'ads-loaded'
|
|
26
26
|
* @property {number} loadTime
|
package/lib/cea/cea708_window.js
CHANGED
|
@@ -420,7 +420,7 @@ shaka.cea.Cea708Window.TextJustification = {
|
|
|
420
420
|
shaka.cea.Cea708Window.MAX_COLS = 42;
|
|
421
421
|
|
|
422
422
|
/**
|
|
423
|
-
* Maximum of
|
|
423
|
+
* Maximum of 16 rows that can be indexed from 0 to 15.
|
|
424
424
|
* @private @const {number}
|
|
425
425
|
*/
|
|
426
|
-
shaka.cea.Cea708Window.MAX_ROWS =
|
|
426
|
+
shaka.cea.Cea708Window.MAX_ROWS = 16;
|
|
@@ -67,7 +67,7 @@ shaka.dash.SegmentTemplate = class {
|
|
|
67
67
|
},
|
|
68
68
|
};
|
|
69
69
|
} else if (info.segmentDuration) {
|
|
70
|
-
if (!isUpdate) {
|
|
70
|
+
if (!isUpdate && context.adaptationSet.contentType !== 'image') {
|
|
71
71
|
context.presentationTimeline.notifyMaxSegmentDuration(
|
|
72
72
|
info.segmentDuration);
|
|
73
73
|
context.presentationTimeline.notifyMinSegmentStartTime(
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -863,7 +863,7 @@ shaka.hls.HlsParser = class {
|
|
|
863
863
|
shaka.log.debug('Guessing audio-only.');
|
|
864
864
|
type = ContentType.AUDIO;
|
|
865
865
|
ignoreStream = true;
|
|
866
|
-
} else if (res.video.length) {
|
|
866
|
+
} else if (res.video.length && !res.audio.length) {
|
|
867
867
|
// There are associated video streams. Assume this is audio.
|
|
868
868
|
shaka.log.debug('Guessing audio-only.');
|
|
869
869
|
type = ContentType.AUDIO;
|
|
@@ -1484,6 +1484,7 @@ shaka.hls.HlsParser = class {
|
|
|
1484
1484
|
*/
|
|
1485
1485
|
const discontinuityToMediaSequence = new Map();
|
|
1486
1486
|
|
|
1487
|
+
const prevLowLatencyMode = this.lowLatencyMode_;
|
|
1487
1488
|
let segments;
|
|
1488
1489
|
try {
|
|
1489
1490
|
segments = await this.createSegments_(verbatimMediaPlaylistUri,
|
|
@@ -1499,6 +1500,12 @@ shaka.hls.HlsParser = class {
|
|
|
1499
1500
|
throw error;
|
|
1500
1501
|
}
|
|
1501
1502
|
|
|
1503
|
+
// This happens when autoLowLatencyMode is true, so we need set the
|
|
1504
|
+
// correct lowLatencyPresentationDelay_
|
|
1505
|
+
if (prevLowLatencyMode != this.lowLatencyMode_) {
|
|
1506
|
+
this.determinePresentationType_(playlist);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1502
1509
|
const minTimestamp = segments[0].startTime;
|
|
1503
1510
|
const lastEndTime = segments[segments.length - 1].endTime;
|
|
1504
1511
|
/** @type {!shaka.media.SegmentIndex} */
|
|
@@ -69,6 +69,18 @@ shaka.media.ContentWorkarounds = class {
|
|
|
69
69
|
})
|
|
70
70
|
.fullBox('encv', onEncryptionMetadataBox)
|
|
71
71
|
.fullBox('enca', onEncryptionMetadataBox)
|
|
72
|
+
.fullBox('hev1', (box) => {
|
|
73
|
+
boxesToModify.push({
|
|
74
|
+
box,
|
|
75
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
76
|
+
});
|
|
77
|
+
})
|
|
78
|
+
.fullBox('hvc1', (box) => {
|
|
79
|
+
boxesToModify.push({
|
|
80
|
+
box,
|
|
81
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
82
|
+
});
|
|
83
|
+
})
|
|
72
84
|
.fullBox('avc1', (box) => {
|
|
73
85
|
boxesToModify.push({
|
|
74
86
|
box,
|
|
@@ -598,6 +598,11 @@ shaka.media.StreamingEngine = class {
|
|
|
598
598
|
* within the presentation timeline.
|
|
599
599
|
*/
|
|
600
600
|
seeked() {
|
|
601
|
+
if (!this.playerInterface_) {
|
|
602
|
+
// Already destroyed.
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
601
606
|
const presentationTime = this.playerInterface_.getPresentationTime();
|
|
602
607
|
const smallGapLimit = this.config_.smallGapLimit;
|
|
603
608
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
@@ -652,7 +657,7 @@ shaka.media.StreamingEngine = class {
|
|
|
652
657
|
|
|
653
658
|
/**
|
|
654
659
|
* Clear the buffer for a given stream. Unlike clearBuffer_, this will handle
|
|
655
|
-
* cases where a MediaState is performing an update. After this runs,
|
|
660
|
+
* cases where a MediaState is performing an update. After this runs, the
|
|
656
661
|
* MediaState will have a pending update.
|
|
657
662
|
* @param {!shaka.media.StreamingEngine.MediaState_} mediaState
|
|
658
663
|
* @private
|
|
@@ -767,9 +772,9 @@ shaka.media.StreamingEngine = class {
|
|
|
767
772
|
for (const type of streamsByType.keys()) {
|
|
768
773
|
const stream = streamsByType.get(type);
|
|
769
774
|
if (!this.mediaStates_.has(type)) {
|
|
770
|
-
const
|
|
771
|
-
this.mediaStates_.set(type,
|
|
772
|
-
this.scheduleUpdate_(
|
|
775
|
+
const mediaState = this.createMediaState_(stream);
|
|
776
|
+
this.mediaStates_.set(type, mediaState);
|
|
777
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
773
778
|
}
|
|
774
779
|
}
|
|
775
780
|
}
|
|
@@ -873,7 +878,7 @@ shaka.media.StreamingEngine = class {
|
|
|
873
878
|
// We switched streams while in the middle of this async call to
|
|
874
879
|
// createSegmentIndex. Abandon this update and schedule a new one if
|
|
875
880
|
// there's not already one pending.
|
|
876
|
-
if (mediaState.
|
|
881
|
+
if (!mediaState.performingUpdate && !mediaState.updateTimer) {
|
|
877
882
|
this.scheduleUpdate_(mediaState, 0);
|
|
878
883
|
}
|
|
879
884
|
return;
|
|
@@ -1341,7 +1346,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1341
1346
|
// If the network slows down, abort the current fetch request and start
|
|
1342
1347
|
// a new one, and ignore the error message.
|
|
1343
1348
|
mediaState.performingUpdate = false;
|
|
1344
|
-
mediaState
|
|
1349
|
+
this.cancelUpdate_(mediaState);
|
|
1345
1350
|
this.scheduleUpdate_(mediaState, 0);
|
|
1346
1351
|
} else if (mediaState.type == ContentType.TEXT &&
|
|
1347
1352
|
this.config_.ignoreTextStreamFailures) {
|
|
@@ -1386,7 +1391,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1386
1391
|
|
|
1387
1392
|
for (const mediaState of this.mediaStates_.values()) {
|
|
1388
1393
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
1389
|
-
if
|
|
1394
|
+
// Only schedule an update if it has an error, but it's not mid-update
|
|
1395
|
+
// and there is not already an update scheduled.
|
|
1396
|
+
if (mediaState.hasError && !mediaState.performingUpdate &&
|
|
1397
|
+
!mediaState.updateTimer) {
|
|
1390
1398
|
shaka.log.info(logPrefix, 'Retrying after failure...');
|
|
1391
1399
|
mediaState.hasError = false;
|
|
1392
1400
|
this.scheduleUpdate_(mediaState, delaySeconds);
|
|
@@ -1851,7 +1859,11 @@ shaka.media.StreamingEngine = class {
|
|
|
1851
1859
|
shaka.log.debug(logPrefix, 'cleared buffer');
|
|
1852
1860
|
mediaState.clearingBuffer = false;
|
|
1853
1861
|
mediaState.endOfStream = false;
|
|
1854
|
-
|
|
1862
|
+
// Since the clear operation was async, check to make sure we're not doing
|
|
1863
|
+
// another update and we don't have one scheduled yet.
|
|
1864
|
+
if (!mediaState.performingUpdate && !mediaState.updateTimer) {
|
|
1865
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
1866
|
+
}
|
|
1855
1867
|
}
|
|
1856
1868
|
|
|
1857
1869
|
|
package/lib/player.js
CHANGED
|
@@ -3952,16 +3952,25 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3952
3952
|
return [];
|
|
3953
3953
|
}
|
|
3954
3954
|
const chapters = [];
|
|
3955
|
+
const uniqueChapters = new Set();
|
|
3955
3956
|
for (const chaptersTrack of chaptersTracksWithLanguage) {
|
|
3956
3957
|
if (chaptersTrack && chaptersTrack.cues) {
|
|
3957
3958
|
for (const cue of chaptersTrack.cues) {
|
|
3959
|
+
let id = cue.id;
|
|
3960
|
+
if (!id || id == '') {
|
|
3961
|
+
id = cue.startTime + '-' + cue.endTime + '-' + cue.text;
|
|
3962
|
+
}
|
|
3958
3963
|
/** @type {shaka.extern.Chapter} */
|
|
3959
3964
|
const chapter = {
|
|
3965
|
+
id: id,
|
|
3960
3966
|
title: cue.text,
|
|
3961
3967
|
startTime: cue.startTime,
|
|
3962
3968
|
endTime: cue.endTime,
|
|
3963
3969
|
};
|
|
3964
|
-
|
|
3970
|
+
if (!uniqueChapters.has(id)) {
|
|
3971
|
+
chapters.push(chapter);
|
|
3972
|
+
uniqueChapters.add(id);
|
|
3973
|
+
}
|
|
3965
3974
|
}
|
|
3966
3975
|
}
|
|
3967
3976
|
}
|
|
@@ -6444,7 +6453,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6444
6453
|
* @define {string} A version number taken from git at compile time.
|
|
6445
6454
|
* @export
|
|
6446
6455
|
*/
|
|
6447
|
-
shaka.Player.version = 'v3.2.
|
|
6456
|
+
shaka.Player.version = 'v3.2.17-uncompiled';
|
|
6448
6457
|
|
|
6449
6458
|
// Initialize the deprecation system using the version string we just set
|
|
6450
6459
|
// on the player.
|
|
@@ -8,6 +8,7 @@ goog.provide('shaka.polyfill.MediaCapabilities');
|
|
|
8
8
|
|
|
9
9
|
goog.require('shaka.log');
|
|
10
10
|
goog.require('shaka.polyfill');
|
|
11
|
+
goog.require('shaka.util.Error');
|
|
11
12
|
goog.require('shaka.util.Platform');
|
|
12
13
|
|
|
13
14
|
|
|
@@ -84,37 +85,48 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
84
85
|
return res;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
const videoConfig = mediaDecodingConfig['video'];
|
|
89
|
+
const audioConfig = mediaDecodingConfig['audio'];
|
|
90
|
+
|
|
87
91
|
if (mediaDecodingConfig.type == 'media-source') {
|
|
88
92
|
if (!shaka.util.Platform.supportsMediaSource()) {
|
|
89
93
|
return res;
|
|
90
94
|
}
|
|
91
95
|
// Use 'MediaSource.isTypeSupported' to check if the stream is supported.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
// Cast platforms will additionally check canDisplayType(), which
|
|
97
|
+
// accepts extended MIME type parameters.
|
|
98
|
+
// See: https://github.com/shaka-project/shaka-player/issues/4726
|
|
99
|
+
if (videoConfig) {
|
|
100
|
+
let isSupported;
|
|
101
|
+
if (shaka.util.Platform.isChromecast()) {
|
|
102
|
+
isSupported =
|
|
103
|
+
shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
|
|
104
|
+
} else {
|
|
105
|
+
isSupported = MediaSource.isTypeSupported(videoConfig.contentType);
|
|
106
|
+
}
|
|
95
107
|
if (!isSupported) {
|
|
96
108
|
return res;
|
|
97
109
|
}
|
|
98
110
|
}
|
|
99
111
|
|
|
100
|
-
if (
|
|
101
|
-
const contentType =
|
|
112
|
+
if (audioConfig) {
|
|
113
|
+
const contentType = audioConfig.contentType;
|
|
102
114
|
const isSupported = MediaSource.isTypeSupported(contentType);
|
|
103
115
|
if (!isSupported) {
|
|
104
116
|
return res;
|
|
105
117
|
}
|
|
106
118
|
}
|
|
107
119
|
} else if (mediaDecodingConfig.type == 'file') {
|
|
108
|
-
if (
|
|
109
|
-
const contentType =
|
|
120
|
+
if (videoConfig) {
|
|
121
|
+
const contentType = videoConfig.contentType;
|
|
110
122
|
const isSupported = shaka.util.Platform.supportsMediaType(contentType);
|
|
111
123
|
if (!isSupported) {
|
|
112
124
|
return res;
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
127
|
|
|
116
|
-
if (
|
|
117
|
-
const contentType =
|
|
128
|
+
if (audioConfig) {
|
|
129
|
+
const contentType = audioConfig.contentType;
|
|
118
130
|
const isSupported = shaka.util.Platform.supportsMediaType(contentType);
|
|
119
131
|
if (!isSupported) {
|
|
120
132
|
return res;
|
|
@@ -189,6 +201,44 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
189
201
|
|
|
190
202
|
return res;
|
|
191
203
|
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Checks if the given media parameters of the video or audio streams are
|
|
207
|
+
* supported by the Cast platform.
|
|
208
|
+
* @param {!VideoConfiguration} videoConfig The 'video' field of the
|
|
209
|
+
* MediaDecodingConfiguration.
|
|
210
|
+
* @return {boolean} `true` when the stream can be displayed on a Cast device.
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
static canCastDisplayType_(videoConfig) {
|
|
214
|
+
if (!(window.cast)) {
|
|
215
|
+
shaka.log.error('Expected cast namespace to be available!');
|
|
216
|
+
throw new shaka.util.Error(
|
|
217
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
218
|
+
shaka.util.Error.Category.CAST,
|
|
219
|
+
shaka.util.Error.Code.CAST_API_UNAVAILABLE);
|
|
220
|
+
} else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
|
|
221
|
+
shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
|
|
222
|
+
'MediaSource.isTypeSupported() for type support.');
|
|
223
|
+
return MediaSource.isTypeSupported(videoConfig.contentType);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let displayType = videoConfig.contentType;
|
|
227
|
+
if (videoConfig.width && videoConfig.height) {
|
|
228
|
+
displayType +=
|
|
229
|
+
`; width=${videoConfig.width}; height=${videoConfig.height}`;
|
|
230
|
+
}
|
|
231
|
+
if (videoConfig.framerate) {
|
|
232
|
+
displayType += `; framerate=${videoConfig.framerate}`;
|
|
233
|
+
}
|
|
234
|
+
if (videoConfig.transferFunction === 'pq') {
|
|
235
|
+
// A "PQ" transfer function indicates this is an HDR-capable stream;
|
|
236
|
+
// "smpte2084" is the published standard. We need to inform the platform
|
|
237
|
+
// this query is specifically for HDR.
|
|
238
|
+
displayType += '; eotf=smpte2084';
|
|
239
|
+
}
|
|
240
|
+
return cast.__platform__.canDisplayType(displayType);
|
|
241
|
+
}
|
|
192
242
|
};
|
|
193
243
|
|
|
194
244
|
/**
|
|
@@ -128,7 +128,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
const cue = TtmlTextParser.parseCue_(
|
|
131
|
-
body, time
|
|
131
|
+
body, time, rateInfo, metadataElements, styles,
|
|
132
132
|
regionElements, cueRegions, whitespaceTrim,
|
|
133
133
|
cellResolutionInfo, /* parentCueElement= */ null,
|
|
134
134
|
/* isContent= */ false);
|
|
@@ -149,7 +149,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
149
149
|
* Parses a TTML node into a Cue.
|
|
150
150
|
*
|
|
151
151
|
* @param {!Node} cueNode
|
|
152
|
-
* @param {
|
|
152
|
+
* @param {shaka.extern.TextParser.TimeContext} timeContext
|
|
153
153
|
* @param {!shaka.text.TtmlTextParser.RateInfo_} rateInfo
|
|
154
154
|
* @param {!Array.<!Element>} metadataElements
|
|
155
155
|
* @param {!Array.<!Element>} styles
|
|
@@ -163,7 +163,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
163
163
|
* @private
|
|
164
164
|
*/
|
|
165
165
|
static parseCue_(
|
|
166
|
-
cueNode,
|
|
166
|
+
cueNode, timeContext, rateInfo, metadataElements, styles, regionElements,
|
|
167
167
|
cueRegions, whitespaceTrim, cellResolution, parentCueElement, isContent) {
|
|
168
168
|
/** @type {Element} */
|
|
169
169
|
let cueElement;
|
|
@@ -228,7 +228,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
228
228
|
for (const childNode of cueElement.childNodes) {
|
|
229
229
|
const nestedCue = shaka.text.TtmlTextParser.parseCue_(
|
|
230
230
|
childNode,
|
|
231
|
-
|
|
231
|
+
timeContext,
|
|
232
232
|
rateInfo,
|
|
233
233
|
metadataElements,
|
|
234
234
|
styles,
|
|
@@ -285,15 +285,20 @@ shaka.text.TtmlTextParser = class {
|
|
|
285
285
|
if (start == null) {
|
|
286
286
|
start = 0;
|
|
287
287
|
}
|
|
288
|
-
start +=
|
|
288
|
+
start += timeContext.periodStart;
|
|
289
289
|
|
|
290
290
|
// If end is null, that means the duration is effectively infinite.
|
|
291
291
|
if (end == null) {
|
|
292
292
|
end = Infinity;
|
|
293
293
|
} else {
|
|
294
|
-
end +=
|
|
294
|
+
end += timeContext.periodStart;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
// Clip times to segment boundaries.
|
|
298
|
+
// https://github.com/shaka-project/shaka-player/issues/4631
|
|
299
|
+
start = Math.max(start, timeContext.segmentStart);
|
|
300
|
+
end = Math.min(end, timeContext.segmentEnd);
|
|
301
|
+
|
|
297
302
|
if (!hasTimeAttributes && nestedCues.length > 0) {
|
|
298
303
|
// If no time is defined for this cue, base the timing information on
|
|
299
304
|
// the time of the nested cues. In the case of multiple nested cues with
|
|
@@ -562,8 +567,28 @@ shaka.text.TtmlTextParser = class {
|
|
|
562
567
|
|
|
563
568
|
const fontFamily = TtmlTextParser.getStyleAttribute_(
|
|
564
569
|
cueElement, region, styles, 'fontFamily', shouldInheritRegionStyles);
|
|
570
|
+
// See https://github.com/sandflow/imscJS/blob/1.1.3/src/main/js/html.js#L1384
|
|
565
571
|
if (fontFamily) {
|
|
566
|
-
|
|
572
|
+
switch (fontFamily) {
|
|
573
|
+
case 'monospaceSerif':
|
|
574
|
+
cue.fontFamily = 'Courier New,Liberation Mono,Courier,monospace';
|
|
575
|
+
break;
|
|
576
|
+
case 'proportionalSansSerif':
|
|
577
|
+
cue.fontFamily = 'Arial,Helvetica,Liberation Sans,sans-serif';
|
|
578
|
+
break;
|
|
579
|
+
case 'sansSerif':
|
|
580
|
+
cue.fontFamily = 'sans-serif';
|
|
581
|
+
break;
|
|
582
|
+
case 'monospaceSansSerif':
|
|
583
|
+
cue.fontFamily = 'Consolas,monospace';
|
|
584
|
+
break;
|
|
585
|
+
case 'proportionalSerif':
|
|
586
|
+
cue.fontFamily = 'serif';
|
|
587
|
+
break;
|
|
588
|
+
default:
|
|
589
|
+
cue.fontFamily = fontFamily;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
567
592
|
}
|
|
568
593
|
|
|
569
594
|
const fontWeight = TtmlTextParser.getStyleAttribute_(
|
|
@@ -200,6 +200,19 @@ shaka.text.UITextDisplayer = class {
|
|
|
200
200
|
this.isTextVisible_ = on;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
/**
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
isElementUnderTextContainer_(elemToCheck) {
|
|
207
|
+
while (elemToCheck != null) {
|
|
208
|
+
if (elemToCheck == this.textContainer_) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
elemToCheck = elemToCheck.parentElement;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
203
216
|
/**
|
|
204
217
|
* @param {!Array.<!shaka.extern.Cue>} cues
|
|
205
218
|
* @param {!HTMLElement} container
|
|
@@ -261,6 +274,9 @@ shaka.text.UITextDisplayer = class {
|
|
|
261
274
|
cueRegistry = this.currentCuesMap_.get(cue);
|
|
262
275
|
wrapper = cueRegistry.wrapper;
|
|
263
276
|
updateDOM = true;
|
|
277
|
+
} else if (!this.isElementUnderTextContainer_(wrapper)) {
|
|
278
|
+
// We found that the wrapper needs to be in the DOM
|
|
279
|
+
updateDOM = true;
|
|
264
280
|
}
|
|
265
281
|
}
|
|
266
282
|
|
|
@@ -277,6 +293,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
277
293
|
const topCue = parents.pop();
|
|
278
294
|
goog.asserts.assert(topCue == cue, 'Parent cues should be kept in order');
|
|
279
295
|
}
|
|
296
|
+
|
|
280
297
|
if (updateDOM) {
|
|
281
298
|
for (const element of toUproot) {
|
|
282
299
|
// NOTE: Because we uproot shared region elements, too, we might hit an
|
|
@@ -583,7 +600,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
583
600
|
// (for vertical growing left) is aligned at the line.
|
|
584
601
|
// TODO: Implement line alignment with line number.
|
|
585
602
|
// TODO: Implement lineAlignment of 'CENTER'.
|
|
586
|
-
if (cue.line) {
|
|
603
|
+
if (cue.line != null) {
|
|
587
604
|
if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
|
|
588
605
|
// When setting absolute positioning, you need to set x/y/width/height
|
|
589
606
|
// so the element is positioned correctly. Set these as default and
|
|
@@ -596,21 +613,21 @@ shaka.text.UITextDisplayer = class {
|
|
|
596
613
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
597
614
|
style.top = cue.line + '%';
|
|
598
615
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
599
|
-
style.bottom = cue.line + '%';
|
|
616
|
+
style.bottom = (100 - cue.line) + '%';
|
|
600
617
|
}
|
|
601
618
|
} else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
|
|
602
619
|
style.height = '100%';
|
|
603
620
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
604
621
|
style.left = cue.line + '%';
|
|
605
622
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
606
|
-
style.right = cue.line + '%';
|
|
623
|
+
style.right = (100 - cue.line) + '%';
|
|
607
624
|
}
|
|
608
625
|
} else {
|
|
609
626
|
style.height = '100%';
|
|
610
627
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
611
628
|
style.right = cue.line + '%';
|
|
612
629
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
613
|
-
style.left = cue.line + '%';
|
|
630
|
+
style.left = (100 - cue.line) + '%';
|
|
614
631
|
}
|
|
615
632
|
}
|
|
616
633
|
}
|
|
@@ -620,7 +637,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
620
637
|
|
|
621
638
|
// The position defines the indent of the text container in the
|
|
622
639
|
// direction defined by the writing direction.
|
|
623
|
-
if (cue.position) {
|
|
640
|
+
if (cue.position != null) {
|
|
624
641
|
if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
|
|
625
642
|
style.paddingLeft = cue.position;
|
|
626
643
|
} else {
|
package/lib/util/platform.js
CHANGED
|
@@ -184,7 +184,7 @@ shaka.util.Platform = class {
|
|
|
184
184
|
* @return {?number} A major version number or null if not Chromium-based.
|
|
185
185
|
*/
|
|
186
186
|
static chromeVersion() {
|
|
187
|
-
if (!shaka.util.Platform.
|
|
187
|
+
if (!shaka.util.Platform.isChrome()) {
|
|
188
188
|
return null;
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -223,7 +223,9 @@ shaka.util.Platform = class {
|
|
|
223
223
|
return !!navigator.vendor && navigator.vendor.includes('Apple') &&
|
|
224
224
|
!shaka.util.Platform.isTizen() &&
|
|
225
225
|
!shaka.util.Platform.isEOS() &&
|
|
226
|
-
!shaka.util.Platform.
|
|
226
|
+
!shaka.util.Platform.isVirginMedia() &&
|
|
227
|
+
!shaka.util.Platform.isPS4() &&
|
|
228
|
+
!shaka.util.Platform.isAmazonFireTV();
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
/**
|
|
@@ -246,6 +248,23 @@ shaka.util.Platform = class {
|
|
|
246
248
|
return shaka.util.Platform.userAgentContains_('PlayStation 4');
|
|
247
249
|
}
|
|
248
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Check if the current platform is Virgin Media device.
|
|
253
|
+
*/
|
|
254
|
+
static isVirginMedia() {
|
|
255
|
+
return shaka.util.Platform.userAgentContains_('VirginMedia');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if the current platform is Amazon Fire TV.
|
|
260
|
+
* https://developer.amazon.com/docs/fire-tv/identify-amazon-fire-tv-devices.html
|
|
261
|
+
*
|
|
262
|
+
* @return {boolean}
|
|
263
|
+
*/
|
|
264
|
+
static isAmazonFireTV() {
|
|
265
|
+
return shaka.util.Platform.userAgentContains_('AFT');
|
|
266
|
+
}
|
|
267
|
+
|
|
249
268
|
/**
|
|
250
269
|
* Returns a major version number for Safari, or Safari-based iOS browsers.
|
|
251
270
|
*
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -431,15 +431,20 @@ shaka.util.StreamUtils = class {
|
|
|
431
431
|
const video = variant.video;
|
|
432
432
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
433
433
|
if (video) {
|
|
434
|
-
let videoCodecs =
|
|
434
|
+
let videoCodecs =
|
|
435
|
+
shaka.util.StreamUtils.getCorrectVideoCodecs_(video.codecs);
|
|
435
436
|
// For multiplexed streams. Here we must check the audio of the
|
|
436
437
|
// stream to see if it is compatible.
|
|
437
438
|
if (video.codecs.includes(',')) {
|
|
438
439
|
const allCodecs = video.codecs.split(',');
|
|
439
440
|
videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
440
441
|
ContentType.VIDEO, allCodecs);
|
|
441
|
-
|
|
442
|
+
videoCodecs =
|
|
443
|
+
shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
|
|
444
|
+
let audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
442
445
|
ContentType.AUDIO, allCodecs);
|
|
446
|
+
audioCodecs =
|
|
447
|
+
shaka.util.StreamUtils.getCorrectAudioCodecs_(audioCodecs);
|
|
443
448
|
const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
444
449
|
video.mimeType, audioCodecs, ContentType.AUDIO);
|
|
445
450
|
if (!MediaSource.isTypeSupported(audioFullType)) {
|
|
@@ -559,7 +564,8 @@ shaka.util.StreamUtils = class {
|
|
|
559
564
|
const allCodecs = video.codecs.split(',');
|
|
560
565
|
videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
561
566
|
ContentType.VIDEO, allCodecs);
|
|
562
|
-
videoCodecs =
|
|
567
|
+
videoCodecs =
|
|
568
|
+
shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
|
|
563
569
|
const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
564
570
|
ContentType.AUDIO, allCodecs);
|
|
565
571
|
|
|
@@ -573,7 +579,7 @@ shaka.util.StreamUtils = class {
|
|
|
573
579
|
spatialRendering: false,
|
|
574
580
|
};
|
|
575
581
|
}
|
|
576
|
-
videoCodecs = shaka.util.StreamUtils.
|
|
582
|
+
videoCodecs = shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
|
|
577
583
|
const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
578
584
|
video.mimeType, videoCodecs, ContentType.VIDEO);
|
|
579
585
|
// VideoConfiguration
|
|
@@ -742,15 +748,29 @@ shaka.util.StreamUtils = class {
|
|
|
742
748
|
|
|
743
749
|
|
|
744
750
|
/**
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
* mediaCapabilities enabled.
|
|
748
|
-
*
|
|
751
|
+
* Generates the correct video codec for MediaDecodingConfiguration and
|
|
752
|
+
* for MediaSource.isTypeSupported.
|
|
749
753
|
* @param {string} codec
|
|
750
754
|
* @return {string}
|
|
755
|
+
* @private
|
|
751
756
|
*/
|
|
752
|
-
static
|
|
753
|
-
if (codec
|
|
757
|
+
static getCorrectVideoCodecs_(codec) {
|
|
758
|
+
if (codec.includes('avc1')) {
|
|
759
|
+
// Convert avc1 codec string from RFC-4281 to RFC-6381 for
|
|
760
|
+
// MediaSource.isTypeSupported
|
|
761
|
+
// Example, convert avc1.66.30 to avc1.42001e (0x42 == 66 and 0x1e == 30)
|
|
762
|
+
const avcdata = codec.split('.');
|
|
763
|
+
if (avcdata.length == 3) {
|
|
764
|
+
let result = avcdata.shift() + '.';
|
|
765
|
+
result += parseInt(avcdata.shift(), 10).toString(16);
|
|
766
|
+
result +=
|
|
767
|
+
('000' + parseInt(avcdata.shift(), 10).toString(16)).slice(-4);
|
|
768
|
+
return result;
|
|
769
|
+
}
|
|
770
|
+
} else if (codec == 'vp9') {
|
|
771
|
+
// MediaCapabilities supports 'vp09...' codecs, but not 'vp9'. Translate
|
|
772
|
+
// vp9 codec strings into 'vp09...', to allow such content to play with
|
|
773
|
+
// mediaCapabilities enabled.
|
|
754
774
|
// This means profile 0, level 4.1, 8-bit color. This supports 1080p @
|
|
755
775
|
// 60Hz. See https://en.wikipedia.org/wiki/VP9#Levels
|
|
756
776
|
//
|
package/package.json
CHANGED
|
@@ -2103,6 +2103,7 @@ describe('DashParser Manifest', () => {
|
|
|
2103
2103
|
/** @type {shaka.extern.Manifest} */
|
|
2104
2104
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
2105
2105
|
expect(manifest.imageStreams.length).toBe(1);
|
|
2106
|
+
expect(manifest.presentationTimeline.getMaxSegmentDuration()).toBe(1);
|
|
2106
2107
|
const imageStream = manifest.imageStreams[0];
|
|
2107
2108
|
expect(imageStream.width).toBe(1024);
|
|
2108
2109
|
expect(imageStream.height).toBe(1152);
|