shaka-player 3.2.11 → 3.2.13
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 +31 -16
- package/.github/workflows/custom-actions/set-commit-status/action.yaml +1 -5
- package/.github/workflows/deflake.yaml +28 -0
- package/.github/workflows/selenium-lab-tests.yaml +168 -54
- package/AUTHORS +3 -0
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTORS +2 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/locales.js +29 -29
- package/dist/shaka-player.compiled.debug.js +159 -158
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +463 -462
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.debug.js +123 -122
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +690 -689
- package/dist/shaka-player.ui.map +3 -3
- package/docs/jsdoc-template/publish.js +33 -0
- package/docs/jsdoc-template/tmpl/container.tmpl +4 -1
- package/docs/jsdoc.conf.json +1 -0
- package/lib/cast/cast_sender.js +39 -14
- package/lib/cea/cea608_memory.js +4 -0
- package/lib/media/streaming_engine.js +41 -4
- package/lib/player.js +7 -1
- package/lib/text/ttml_text_parser.js +6 -0
- package/lib/text/ui_text_displayer.js +8 -0
- package/lib/util/platform.js +34 -0
- package/lib/util/stream_utils.js +2 -1
- package/package.json +1 -1
- package/test/cea/cea608_memory_unit.js +88 -0
- package/test/media/streaming_engine_integration.js +8 -7
- package/test/offline/offline_integration.js +3 -3
- package/test/player_src_equals_integration.js +2 -0
- package/test/test/assets/small.mp4 +0 -0
- package/test/test/util/ttml_utils.js +0 -2
- package/test/text/ttml_text_parser_unit.js +46 -0
- package/test/ui/text_displayer_layout_unit.js +17 -2
- package/ui/less/ad_controls.less +4 -0
|
@@ -440,6 +440,7 @@ function buildNav(members) {
|
|
|
440
440
|
[members.externals, 'Externals', seen, linktoExternal],
|
|
441
441
|
[members.namespaces, 'Namespaces', seen, linkto],
|
|
442
442
|
[members.classes, 'Classes', seen, linkto],
|
|
443
|
+
[members.enums, 'Enums', seen, linkto],
|
|
443
444
|
[members.interfaces, 'Interfaces', seen, linkto],
|
|
444
445
|
[members.events, 'Events', seen, linkto],
|
|
445
446
|
[members.mixins, 'Mixins', seen, linkto],
|
|
@@ -484,6 +485,7 @@ function fixUpType(type) {
|
|
|
484
485
|
exports.publish = (taffyData, opts, tutorials) => {
|
|
485
486
|
let classes;
|
|
486
487
|
let conf;
|
|
488
|
+
let enums;
|
|
487
489
|
let externals;
|
|
488
490
|
let files;
|
|
489
491
|
let fromDir;
|
|
@@ -733,6 +735,31 @@ exports.publish = (taffyData, opts, tutorials) => {
|
|
|
733
735
|
});
|
|
734
736
|
|
|
735
737
|
members = helper.getMembers(data);
|
|
738
|
+
members.enums = [];
|
|
739
|
+
|
|
740
|
+
data().each(doclet => {
|
|
741
|
+
if (doclet.isEnum) {
|
|
742
|
+
const hasParent = !!docletMap[doclet.memberof];
|
|
743
|
+
|
|
744
|
+
// Enums without parents (not attached to a class, but directly to a
|
|
745
|
+
// namespace) aren't recognized by jsdoc by default and need to be
|
|
746
|
+
// massaged into the docs separately.
|
|
747
|
+
if (!hasParent) {
|
|
748
|
+
// Temporarily call this a "class", so that we can fix the URL.
|
|
749
|
+
doclet.kind = 'class';
|
|
750
|
+
// Regenerate the URL, so that this enum gets its own page
|
|
751
|
+
// instead of a URL that links to an ID within a non-existent
|
|
752
|
+
// parent page.
|
|
753
|
+
delete helper.longnameToUrl[doclet.longname];
|
|
754
|
+
helper.createLink(doclet);
|
|
755
|
+
// Mark this as an enum (custom type), so that it doesn't get
|
|
756
|
+
// slotted with the classes.
|
|
757
|
+
doclet.kind = 'enum';
|
|
758
|
+
members.enums.push(doclet);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
736
763
|
members.tutorials = tutorials.children;
|
|
737
764
|
if (conf.default.sortTutorialsByConfigOrder) {
|
|
738
765
|
members.tutorials.sort((a, b) => {
|
|
@@ -778,6 +805,7 @@ exports.publish = (taffyData, opts, tutorials) => {
|
|
|
778
805
|
|
|
779
806
|
// set up the lists that we'll use to generate pages
|
|
780
807
|
classes = taffy(members.classes);
|
|
808
|
+
enums = taffy(members.enums);
|
|
781
809
|
modules = taffy(members.modules);
|
|
782
810
|
namespaces = taffy(members.namespaces);
|
|
783
811
|
mixins = taffy(members.mixins);
|
|
@@ -786,6 +814,7 @@ exports.publish = (taffyData, opts, tutorials) => {
|
|
|
786
814
|
|
|
787
815
|
Object.keys(helper.longnameToUrl).forEach(longname => {
|
|
788
816
|
const myClasses = helper.find(classes, {longname: longname});
|
|
817
|
+
const myEnums = helper.find(enums, {longname: longname});
|
|
789
818
|
const myExternals = helper.find(externals, {longname: longname});
|
|
790
819
|
const myInterfaces = helper.find(interfaces, {longname: longname});
|
|
791
820
|
const myMixins = helper.find(mixins, {longname: longname});
|
|
@@ -800,6 +829,10 @@ exports.publish = (taffyData, opts, tutorials) => {
|
|
|
800
829
|
generate(`Class: ${myClasses[0].longname}`, myClasses, helper.longnameToUrl[longname]);
|
|
801
830
|
}
|
|
802
831
|
|
|
832
|
+
if (myEnums.length) {
|
|
833
|
+
generate(`Enum: ${myEnums[0].longname}`, myEnums, helper.longnameToUrl[longname]);
|
|
834
|
+
}
|
|
835
|
+
|
|
803
836
|
if (myNamespaces.length) {
|
|
804
837
|
generate(`Namespace: ${myNamespaces[0].longname}`, myNamespaces, helper.longnameToUrl[longname]);
|
|
805
838
|
}
|
|
@@ -144,7 +144,10 @@
|
|
|
144
144
|
return m.longname && m.longname.indexOf('module:') !== 0;
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
|
|
148
|
+
// Skip enums, since they already have their values listed under
|
|
149
|
+
// "Properties".
|
|
150
|
+
if (members && members.length && members.forEach && doc.kind != 'enum') {
|
|
148
151
|
?>
|
|
149
152
|
<h3 class="subsection-title">Members</h3>
|
|
150
153
|
|
package/docs/jsdoc.conf.json
CHANGED
package/lib/cast/cast_sender.js
CHANGED
|
@@ -186,21 +186,14 @@ shaka.cast.CastSender = class {
|
|
|
186
186
|
// If the API becomes available before this instance dies, init will be
|
|
187
187
|
// called again.
|
|
188
188
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// be overwritten.
|
|
196
|
-
if (!window.__onGCastApiAvailable) {
|
|
197
|
-
window.__onGCastApiAvailable = shaka.cast.CastSender.onSdkLoaded_;
|
|
198
|
-
}
|
|
199
|
-
if (window.__onGCastApiAvailable != shaka.cast.CastSender.onSdkLoaded_) {
|
|
200
|
-
shaka.log.alwaysWarn('A global Cast SDK hook is already installed! ' +
|
|
201
|
-
'Shaka Player will be unable to receive a notification when the ' +
|
|
202
|
-
'Cast SDK is ready.');
|
|
189
|
+
// Check if our callback is already installed.
|
|
190
|
+
if (window.__onGCastApiAvailable !== CastSender.onGCastApiAvailable_) {
|
|
191
|
+
// Save pre-existing __onGCastApiAvailable in order to restore later.
|
|
192
|
+
CastSender.__onGCastApiAvailable_ =
|
|
193
|
+
window.__onGCastApiAvailable || null;
|
|
194
|
+
window.__onGCastApiAvailable = CastSender.onGCastApiAvailable_;
|
|
203
195
|
}
|
|
196
|
+
|
|
204
197
|
return;
|
|
205
198
|
}
|
|
206
199
|
|
|
@@ -747,6 +740,9 @@ shaka.cast.CastSender.hasReceivers_ = false;
|
|
|
747
740
|
/** @private {chrome.cast.Session} */
|
|
748
741
|
shaka.cast.CastSender.session_ = null;
|
|
749
742
|
|
|
743
|
+
/** @private {?function(boolean)} */
|
|
744
|
+
shaka.cast.CastSender.__onGCastApiAvailable_ = null;
|
|
745
|
+
|
|
750
746
|
/**
|
|
751
747
|
* A set of all living CastSender instances. The constructor and destroy
|
|
752
748
|
* methods will add and remove instances from this set.
|
|
@@ -774,3 +770,32 @@ shaka.cast.CastSender.onSdkLoaded_ = (loaded) => {
|
|
|
774
770
|
}
|
|
775
771
|
}
|
|
776
772
|
};
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* @param {boolean} available
|
|
776
|
+
* @private
|
|
777
|
+
*/
|
|
778
|
+
shaka.cast.CastSender.onGCastApiAvailable_ = (available) => {
|
|
779
|
+
// Restore callback from saved.
|
|
780
|
+
if (shaka.cast.CastSender.__onGCastApiAvailable_) {
|
|
781
|
+
window.__onGCastApiAvailable =
|
|
782
|
+
shaka.cast.CastSender.__onGCastApiAvailable_;
|
|
783
|
+
} else {
|
|
784
|
+
delete window.__onGCastApiAvailable;
|
|
785
|
+
}
|
|
786
|
+
shaka.cast.CastSender.__onGCastApiAvailable_ = null;
|
|
787
|
+
|
|
788
|
+
// A note about this value: In our testing environment, we load both
|
|
789
|
+
// uncompiled and compiled code. This global callback in uncompiled mode
|
|
790
|
+
// can be overwritten by the same in compiled mode. The two versions will
|
|
791
|
+
// each have their own instances_ map. Therefore the callback must have a
|
|
792
|
+
// name, as opposed to being anonymous. This way, the CastSender tests
|
|
793
|
+
// can invoke the named static method instead of using a global that could
|
|
794
|
+
// be overwritten.
|
|
795
|
+
shaka.cast.CastSender.onSdkLoaded_(available);
|
|
796
|
+
|
|
797
|
+
// call restored callback (if any)
|
|
798
|
+
if (typeof window.__onGCastApiAvailable === 'function') {
|
|
799
|
+
window.__onGCastApiAvailable(available);
|
|
800
|
+
}
|
|
801
|
+
};
|
package/lib/cea/cea608_memory.js
CHANGED
|
@@ -184,6 +184,10 @@ shaka.cea.Cea608Memory = class {
|
|
|
184
184
|
* @param {number} count Count of rows to move.
|
|
185
185
|
*/
|
|
186
186
|
moveRows(dst, src, count) {
|
|
187
|
+
if (src < 0 || dst < 0) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
187
191
|
if (dst >= src) {
|
|
188
192
|
for (let i = count-1; i >= 0; i--) {
|
|
189
193
|
this.rows_[dst + i] = this.rows_[src + i].map((e) => e);
|
|
@@ -980,7 +980,11 @@ shaka.media.StreamingEngine = class {
|
|
|
980
980
|
const timeUntilEnd =
|
|
981
981
|
this.manifest_.presentationTimeline.getDuration() - timeNeeded;
|
|
982
982
|
const oneMicrosecond = 1e-6;
|
|
983
|
-
|
|
983
|
+
|
|
984
|
+
const bufferEnd =
|
|
985
|
+
this.playerInterface_.mediaSourceEngine.bufferEnd(mediaState.type);
|
|
986
|
+
|
|
987
|
+
if (timeUntilEnd < oneMicrosecond && !!bufferEnd) {
|
|
984
988
|
// We shouldn't rebuffer if the playhead is close to the end of the
|
|
985
989
|
// presentation.
|
|
986
990
|
shaka.log.debug(logPrefix, 'buffered to end of presentation');
|
|
@@ -1010,8 +1014,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1010
1014
|
return this.config_.updateIntervalSeconds / 2;
|
|
1011
1015
|
}
|
|
1012
1016
|
|
|
1013
|
-
const bufferEnd =
|
|
1014
|
-
this.playerInterface_.mediaSourceEngine.bufferEnd(mediaState.type);
|
|
1015
1017
|
const reference = this.getSegmentReferenceNeeded_(
|
|
1016
1018
|
mediaState, presentationTime, bufferEnd);
|
|
1017
1019
|
if (!reference) {
|
|
@@ -1207,7 +1209,16 @@ shaka.media.StreamingEngine = class {
|
|
|
1207
1209
|
// Enable MP4 low latency streaming with ReadableStream chunked data.
|
|
1208
1210
|
if (this.config_.lowLatencyMode && isReadableStreamSupported && isMP4) {
|
|
1209
1211
|
let remaining = new Uint8Array(0);
|
|
1212
|
+
let processingResult = false;
|
|
1213
|
+
let callbackCalled = false;
|
|
1210
1214
|
const streamDataCallback = async (data) => {
|
|
1215
|
+
if (processingResult) {
|
|
1216
|
+
// If the fallback result processing was triggered, don't also
|
|
1217
|
+
// append the buffer here. In theory this should never happen,
|
|
1218
|
+
// but it does on some older TVs.
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
callbackCalled = true;
|
|
1211
1222
|
this.destroyer_.ensureNotDestroyed();
|
|
1212
1223
|
if (this.fatalError_) {
|
|
1213
1224
|
return;
|
|
@@ -1238,7 +1249,33 @@ shaka.media.StreamingEngine = class {
|
|
|
1238
1249
|
}
|
|
1239
1250
|
};
|
|
1240
1251
|
|
|
1241
|
-
|
|
1252
|
+
const result =
|
|
1253
|
+
await this.fetch_(mediaState, reference, streamDataCallback);
|
|
1254
|
+
if (!callbackCalled) {
|
|
1255
|
+
// In some environments, we might be forced to use network plugins
|
|
1256
|
+
// that don't support streamDataCallback. In those cases, as a
|
|
1257
|
+
// fallback, append the buffer here.
|
|
1258
|
+
processingResult = true;
|
|
1259
|
+
this.destroyer_.ensureNotDestroyed();
|
|
1260
|
+
if (this.fatalError_) {
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// If the text stream gets switched between fetch_() and append_(),
|
|
1265
|
+
// the new text parser is initialized, but the new init segment is
|
|
1266
|
+
// not fetched yet. That would cause an error in
|
|
1267
|
+
// TextParser.parseMedia().
|
|
1268
|
+
// See http://b/168253400
|
|
1269
|
+
if (mediaState.waitingToClearBuffer) {
|
|
1270
|
+
shaka.log.info(logPrefix, 'waitingToClearBuffer, skip append');
|
|
1271
|
+
mediaState.performingUpdate = false;
|
|
1272
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
await this.append_(
|
|
1277
|
+
mediaState, presentationTime, stream, reference, result);
|
|
1278
|
+
}
|
|
1242
1279
|
} else {
|
|
1243
1280
|
if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
|
|
1244
1281
|
shaka.log.warning('Low latency streaming mode is enabled, but ' +
|
package/lib/player.js
CHANGED
|
@@ -4269,6 +4269,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4269
4269
|
this.stats_.setBandwidthEstimate(estimate);
|
|
4270
4270
|
}
|
|
4271
4271
|
|
|
4272
|
+
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
|
|
4273
|
+
this.stats_.setResolution(
|
|
4274
|
+
/* width= */ element.videoWidth || NaN,
|
|
4275
|
+
/* height= */ element.videoHeight || NaN);
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4272
4278
|
return this.stats_.getBlob();
|
|
4273
4279
|
}
|
|
4274
4280
|
|
|
@@ -6425,7 +6431,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6425
6431
|
* @define {string} A version number taken from git at compile time.
|
|
6426
6432
|
* @export
|
|
6427
6433
|
*/
|
|
6428
|
-
shaka.Player.version = 'v3.2.
|
|
6434
|
+
shaka.Player.version = 'v3.2.13-uncompiled';
|
|
6429
6435
|
|
|
6430
6436
|
// Initialize the deprecation system using the version string we just set
|
|
6431
6437
|
// on the player.
|
|
@@ -156,6 +156,12 @@ shaka.text.TtmlTextParser = class {
|
|
|
156
156
|
cellResolutionInfo, /* parentCueElement= */ null,
|
|
157
157
|
/* isContent= */ false);
|
|
158
158
|
if (cue) {
|
|
159
|
+
// According to the TTML spec, backgrounds default to transparent.
|
|
160
|
+
// So default the background of the top-level element to transparent.
|
|
161
|
+
// Nested elements may override that background color already.
|
|
162
|
+
if (!cue.backgroundColor) {
|
|
163
|
+
cue.backgroundColor = 'transparent';
|
|
164
|
+
}
|
|
159
165
|
cues.push(cue);
|
|
160
166
|
}
|
|
161
167
|
}
|
|
@@ -498,6 +498,14 @@ shaka.text.UITextDisplayer = class {
|
|
|
498
498
|
style.backgroundRepeat = 'no-repeat';
|
|
499
499
|
style.backgroundSize = 'contain';
|
|
500
500
|
style.backgroundPosition = 'center';
|
|
501
|
+
|
|
502
|
+
// Quoting https://www.w3.org/TR/ttml-imsc1.2/:
|
|
503
|
+
// "The width and height (in pixels) of the image resource referenced by
|
|
504
|
+
// smpte:backgroundImage SHALL be equal to the width and height expressed
|
|
505
|
+
// by the tts:extent attribute of the region in which the div element is
|
|
506
|
+
// presented".
|
|
507
|
+
style.width = '100%';
|
|
508
|
+
style.height = '100%';
|
|
501
509
|
} else {
|
|
502
510
|
// If we have both text and nested cues, then style everything; otherwise
|
|
503
511
|
// place the text in its own <span> so the background doesn't fill the
|
package/lib/util/platform.js
CHANGED
|
@@ -163,6 +163,40 @@ shaka.util.Platform = class {
|
|
|
163
163
|
return shaka.util.Platform.userAgentContains_('CrKey');
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Check if the current platform is a Android-based Cast devices.
|
|
168
|
+
*
|
|
169
|
+
* @return {boolean}
|
|
170
|
+
*/
|
|
171
|
+
static isAndroidCastDevice() {
|
|
172
|
+
return shaka.util.Platform.isChromecast() &&
|
|
173
|
+
shaka.util.Platform.userAgentContains_('Android');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Returns a major version number for Chrome, or Chromium-based browsers.
|
|
178
|
+
*
|
|
179
|
+
* For example:
|
|
180
|
+
* - Chrome 106.0.5249.61 returns 106.
|
|
181
|
+
* - Edge 106.0.1370.34 returns 106 (since this is based on Chromium).
|
|
182
|
+
* - Safari returns null (since this is independent of Chromium).
|
|
183
|
+
*
|
|
184
|
+
* @return {?number} A major version number or null if not Chromium-based.
|
|
185
|
+
*/
|
|
186
|
+
static chromeVersion() {
|
|
187
|
+
if (!shaka.util.Platform.userAgentContains_('Chrome')) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Looking for something like "Chrome/106.0.0.0".
|
|
192
|
+
const match = navigator.userAgent.match(/Chrome\/(\d+)/);
|
|
193
|
+
if (match) {
|
|
194
|
+
return parseInt(match[1], /* base= */ 10);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
166
200
|
/**
|
|
167
201
|
* Check if the current platform is Google Chrome.
|
|
168
202
|
*
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -467,7 +467,8 @@ shaka.util.StreamUtils = class {
|
|
|
467
467
|
if (shaka.util.Platform.isXboxOne() && video &&
|
|
468
468
|
((video.width && video.width > 1920) ||
|
|
469
469
|
(video.height && video.height > 1080)) &&
|
|
470
|
-
video.codecs.includes('avc1.')
|
|
470
|
+
(video.codecs.includes('avc1.') ||
|
|
471
|
+
video.codecs.includes('avc3.'))) {
|
|
471
472
|
shaka.log.debug('Dropping variant - not compatible with platform',
|
|
472
473
|
shaka.util.StreamUtils.getVariantSummaryString_(variant));
|
|
473
474
|
return false;
|
package/package.json
CHANGED
|
@@ -295,5 +295,93 @@ describe('Cea608Memory', () => {
|
|
|
295
295
|
const caption = memory.forceEmit(startTime, endTime);
|
|
296
296
|
expect(caption).toEqual(expectedCaption);
|
|
297
297
|
});
|
|
298
|
+
|
|
299
|
+
it('does not move rows if source row index is negative', () => {
|
|
300
|
+
const startTime = 1;
|
|
301
|
+
const endTime = 2;
|
|
302
|
+
const text = 'test';
|
|
303
|
+
|
|
304
|
+
// Add the text to the buffer, each character on separate rows.
|
|
305
|
+
// At this point, the memory looks like:
|
|
306
|
+
// [1]: t
|
|
307
|
+
// [2]: e
|
|
308
|
+
// [3]: s
|
|
309
|
+
// [4]: t
|
|
310
|
+
for (const c of text) {
|
|
311
|
+
memory.addChar(CharSet.BASIC_NORTH_AMERICAN,
|
|
312
|
+
c.charCodeAt(0));
|
|
313
|
+
memory.setRow(memory.getRow() + 1); // increment row
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const srcRowIdx = -1;
|
|
317
|
+
const dstRowIdx = 2;
|
|
318
|
+
const rowsToMove = 3;
|
|
319
|
+
memory.moveRows(dstRowIdx, srcRowIdx, rowsToMove);
|
|
320
|
+
|
|
321
|
+
// Expected text is 't\ne\ns\nt'
|
|
322
|
+
const topLevelCue = new shaka.text.Cue(startTime, endTime, '');
|
|
323
|
+
topLevelCue.nestedCues = [
|
|
324
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
325
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
326
|
+
CeaUtils.createDefaultCue(startTime, endTime, 'e'),
|
|
327
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
328
|
+
CeaUtils.createDefaultCue(startTime, endTime, 's'),
|
|
329
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
330
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const expectedCaption = {
|
|
334
|
+
stream,
|
|
335
|
+
cue: topLevelCue,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Force out the new memory.
|
|
339
|
+
const caption = memory.forceEmit(startTime, endTime);
|
|
340
|
+
expect(caption).toEqual(expectedCaption);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('does not move rows if destination row index is negative', () => {
|
|
344
|
+
const startTime = 1;
|
|
345
|
+
const endTime = 2;
|
|
346
|
+
const text = 'test';
|
|
347
|
+
|
|
348
|
+
// Add the text to the buffer, each character on separate rows.
|
|
349
|
+
// At this point, the memory looks like:
|
|
350
|
+
// [1]: t
|
|
351
|
+
// [2]: e
|
|
352
|
+
// [3]: s
|
|
353
|
+
// [4]: t
|
|
354
|
+
for (const c of text) {
|
|
355
|
+
memory.addChar(CharSet.BASIC_NORTH_AMERICAN,
|
|
356
|
+
c.charCodeAt(0));
|
|
357
|
+
memory.setRow(memory.getRow() + 1); // increment row
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const srcRowIdx = 1;
|
|
361
|
+
const dstRowIdx = -2;
|
|
362
|
+
const rowsToMove = 3;
|
|
363
|
+
memory.moveRows(dstRowIdx, srcRowIdx, rowsToMove);
|
|
364
|
+
|
|
365
|
+
// Expected text is 't\ne\ns\nt'
|
|
366
|
+
const topLevelCue = new shaka.text.Cue(startTime, endTime, '');
|
|
367
|
+
topLevelCue.nestedCues = [
|
|
368
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
369
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
370
|
+
CeaUtils.createDefaultCue(startTime, endTime, 'e'),
|
|
371
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
372
|
+
CeaUtils.createDefaultCue(startTime, endTime, 's'),
|
|
373
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
374
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
const expectedCaption = {
|
|
378
|
+
stream,
|
|
379
|
+
cue: topLevelCue,
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Force out the new memory.
|
|
383
|
+
const caption = memory.forceEmit(startTime, endTime);
|
|
384
|
+
expect(caption).toEqual(expectedCaption);
|
|
385
|
+
});
|
|
298
386
|
});
|
|
299
387
|
});
|
|
@@ -91,17 +91,17 @@ describe('StreamingEngine', () => {
|
|
|
91
91
|
|
|
92
92
|
segmentAvailability = {
|
|
93
93
|
start: 0,
|
|
94
|
-
end:
|
|
94
|
+
end: 40,
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
timeline = shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
|
|
98
98
|
segmentAvailability,
|
|
99
|
-
/* presentationDuration= */
|
|
99
|
+
/* presentationDuration= */ 40,
|
|
100
100
|
/* maxSegmentDuration= */ metadata.video.segmentDuration,
|
|
101
101
|
/* isLive= */ false);
|
|
102
102
|
|
|
103
103
|
setupNetworkingEngine(
|
|
104
|
-
/* presentationDuration= */
|
|
104
|
+
/* presentationDuration= */ 40,
|
|
105
105
|
{
|
|
106
106
|
audio: metadata.audio.segmentDuration,
|
|
107
107
|
video: metadata.video.segmentDuration,
|
|
@@ -109,8 +109,8 @@ describe('StreamingEngine', () => {
|
|
|
109
109
|
|
|
110
110
|
setupManifest(
|
|
111
111
|
/* firstPeriodStartTime= */ 0,
|
|
112
|
-
/* secondPeriodStartTime= */
|
|
113
|
-
/* presentationDuration= */
|
|
112
|
+
/* secondPeriodStartTime= */ 20,
|
|
113
|
+
/* presentationDuration= */ 40);
|
|
114
114
|
|
|
115
115
|
setupPlayhead();
|
|
116
116
|
|
|
@@ -262,10 +262,11 @@ describe('StreamingEngine', () => {
|
|
|
262
262
|
streamingEngine.switchVariant(variant);
|
|
263
263
|
await streamingEngine.start();
|
|
264
264
|
video.play();
|
|
265
|
-
// The overall test timeout is 120 seconds, and the content is
|
|
265
|
+
// The overall test timeout is 120 seconds, and the content is 40
|
|
266
266
|
// seconds. It should be possible to complete this test in 100 seconds,
|
|
267
267
|
// and if not, we want the error thrown to be within the overall test's
|
|
268
|
-
// timeout window.
|
|
268
|
+
// timeout window. Note that we have seen some devices fail to play at
|
|
269
|
+
// full speed for reasons beyond our control, so we plan for >= 0.5x.
|
|
269
270
|
await waiter.timeoutAfter(100).waitForEnd(video);
|
|
270
271
|
});
|
|
271
272
|
|
|
@@ -68,7 +68,7 @@ filterDescribe('Offline', supportsStorage, () => {
|
|
|
68
68
|
await player.load(contentUri);
|
|
69
69
|
|
|
70
70
|
video.play();
|
|
71
|
-
await playTo(/* end= */ 3, /* timeout= */
|
|
71
|
+
await playTo(/* end= */ 3, /* timeout= */ 20);
|
|
72
72
|
await player.unload();
|
|
73
73
|
await storage.remove(contentUri);
|
|
74
74
|
});
|
|
@@ -104,7 +104,7 @@ filterDescribe('Offline', supportsStorage, () => {
|
|
|
104
104
|
await player.load(contentUri);
|
|
105
105
|
|
|
106
106
|
video.play();
|
|
107
|
-
await playTo(/* end= */ 3, /* timeout= */
|
|
107
|
+
await playTo(/* end= */ 3, /* timeout= */ 20);
|
|
108
108
|
await player.unload();
|
|
109
109
|
await storage.remove(contentUri);
|
|
110
110
|
});
|
|
@@ -147,7 +147,7 @@ filterDescribe('Offline', supportsStorage, () => {
|
|
|
147
147
|
await player.load(contentUri);
|
|
148
148
|
|
|
149
149
|
video.play();
|
|
150
|
-
await playTo(/* end= */ 3, /* timeout= */
|
|
150
|
+
await playTo(/* end= */ 3, /* timeout= */ 20);
|
|
151
151
|
await player.unload();
|
|
152
152
|
await storage.remove(contentUri);
|
|
153
153
|
});
|
|
@@ -305,6 +305,8 @@ describe('Player Src Equals', () => {
|
|
|
305
305
|
expect(stats.loadLatency).toBeGreaterThan(0);
|
|
306
306
|
expect(stats.manifestTimeSeconds).toBeNaN(); // There's no manifest.
|
|
307
307
|
expect(stats.drmTimeSeconds).toBeNaN(); // There's no DRM.
|
|
308
|
+
expect(stats.height).toBe(110);
|
|
309
|
+
expect(stats.width).toBe(256);
|
|
308
310
|
});
|
|
309
311
|
|
|
310
312
|
it('plays with external text tracks', async () => {
|
|
Binary file
|
|
@@ -1486,6 +1486,52 @@ describe('TtmlTextParser', () => {
|
|
|
1486
1486
|
{startTime: 1, endTime: 2});
|
|
1487
1487
|
});
|
|
1488
1488
|
|
|
1489
|
+
// Regression test for #4468
|
|
1490
|
+
it('defaults the body background to transparent', () => {
|
|
1491
|
+
verifyHelper(
|
|
1492
|
+
// One cue, don't care about the details
|
|
1493
|
+
[{}],
|
|
1494
|
+
'<tt xmlns:tts="http://www.w3.org/ns/ttml#styling">' +
|
|
1495
|
+
'<head>' +
|
|
1496
|
+
'<styling>' +
|
|
1497
|
+
'<style xml:id="s1" tts:backgroundColor="black" />' +
|
|
1498
|
+
'</styling>' +
|
|
1499
|
+
'</head>' +
|
|
1500
|
+
'<body><div>' +
|
|
1501
|
+
'<p begin="00:01.00" end="00:02.00">' +
|
|
1502
|
+
'<span style="s1">Test</span>' +
|
|
1503
|
+
'</p>' +
|
|
1504
|
+
'</div></body></tt>',
|
|
1505
|
+
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0},
|
|
1506
|
+
// The body must have these properties:
|
|
1507
|
+
{backgroundColor: 'transparent'},
|
|
1508
|
+
// The div must have these properties:
|
|
1509
|
+
{}); // don't care
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
// Regression test for #4468
|
|
1513
|
+
it('allows the body background color to be set', () => {
|
|
1514
|
+
verifyHelper(
|
|
1515
|
+
// One cue, don't care about the details
|
|
1516
|
+
[{}],
|
|
1517
|
+
'<tt xmlns:tts="http://www.w3.org/ns/ttml#styling">' +
|
|
1518
|
+
'<head>' +
|
|
1519
|
+
'<styling>' +
|
|
1520
|
+
'<style xml:id="s1" tts:backgroundColor="black" />' +
|
|
1521
|
+
'</styling>' +
|
|
1522
|
+
'</head>' +
|
|
1523
|
+
'<body style="s1"><div>' +
|
|
1524
|
+
'<p begin="00:01.00" end="00:02.00">' +
|
|
1525
|
+
'<span>Test</span>' +
|
|
1526
|
+
'</p>' +
|
|
1527
|
+
'</div></body></tt>',
|
|
1528
|
+
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0},
|
|
1529
|
+
// The body must have these properties:
|
|
1530
|
+
{backgroundColor: 'black'},
|
|
1531
|
+
// The div must have these properties:
|
|
1532
|
+
{}); // don't care
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1489
1535
|
it('parses wrapping option', () => {
|
|
1490
1536
|
verifyHelper(
|
|
1491
1537
|
[
|
|
@@ -219,6 +219,21 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
219
219
|
|
|
220
220
|
/** @param {string} prefix Prepended to screenshot names */
|
|
221
221
|
function defineTests(prefix) {
|
|
222
|
+
// Due to a Safari implementation bug, the browser only does the correct
|
|
223
|
+
// thing for "end-time-edge-case" on Safari 16+. Skip the tests on earlier
|
|
224
|
+
// versions.
|
|
225
|
+
const safariVersion = shaka.util.Platform.safariVersion();
|
|
226
|
+
if (prefix == 'native' && safariVersion && safariVersion < 16) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Due to updates in the rendering and/or default styles in Chrome, the
|
|
231
|
+
// screenshots for native rendering only match in Chrome 106+.
|
|
232
|
+
const chromeVersion = shaka.util.Platform.chromeVersion();
|
|
233
|
+
if (prefix == 'native' && chromeVersion && chromeVersion < 106) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
222
237
|
it('basic cue', async () => {
|
|
223
238
|
textDisplayer.append([
|
|
224
239
|
new shaka.text.Cue(0, 1, 'Captain\'s log, stardate 41636.9'),
|
|
@@ -262,8 +277,8 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
262
277
|
|
|
263
278
|
// Regression test for #3151
|
|
264
279
|
// Only one cue should be displayed. Note, however, that we don't control
|
|
265
|
-
// this in a browser's native display. As of
|
|
266
|
-
// the right thing in native text display. Chrome
|
|
280
|
+
// this in a browser's native display. As of Sep 2022, both Firefox and
|
|
281
|
+
// Safari>=16 do the right thing in native text display. Chrome and Edge
|
|
267
282
|
// show both cues in this edge case. When we control the display of text
|
|
268
283
|
// through the UI & DOM, we can always get the timing right.
|
|
269
284
|
it('cues ending exactly now', async () => {
|
package/ui/less/ad_controls.less
CHANGED
|
@@ -71,6 +71,10 @@
|
|
|
71
71
|
.ad-text-shadow();
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
.shaka-ad-controls .shaka-ad-position:not(.shaka-hidden) + .shaka-ad-counter {
|
|
75
|
+
margin-left: 0.75em;
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
.shaka-skip-ad-container {
|
|
75
79
|
/* Skip button is positioned at the very right edge of the
|
|
76
80
|
* video container unlike the rest of the bottom controls. */
|