shaka-player 3.2.0 → 3.2.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/.github/workflows/build_and_test.yaml +73 -0
- package/.github/workflows/release.yaml +53 -0
- package/AUTHORS +3 -0
- package/CHANGELOG.md +182 -1
- package/CONTRIBUTORS +3 -0
- package/README.md +4 -4
- package/build/checkversion.py +1 -1
- package/build/generateExterns.js +1 -1
- package/build/misspellings.txt +1 -0
- package/build/shakaBuildHelpers.py +6 -5
- package/build/subprocessWindowsPatch.py +103 -0
- package/build/test.py +6 -0
- package/demo/common/assets.js +1 -1
- package/demo/index.html +1 -1
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/deps.js +8 -8
- package/dist/locales.js +37 -37
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/shaka-player.compiled.d.ts +195 -5
- package/dist/shaka-player.compiled.debug.d.ts +195 -5
- package/dist/shaka-player.compiled.debug.externs.js +226 -14
- package/dist/shaka-player.compiled.debug.js +424 -399
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +226 -14
- package/dist/shaka-player.compiled.js +826 -812
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +197 -16
- package/dist/shaka-player.ui.debug.d.ts +197 -16
- package/dist/shaka-player.ui.debug.externs.js +230 -27
- package/dist/shaka-player.ui.debug.js +466 -442
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +230 -27
- package/dist/shaka-player.ui.js +946 -932
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/docs/tutorials/faq.md +14 -0
- package/docs/tutorials/ui.md +5 -5
- package/externs/ima.js +4 -0
- package/externs/shaka/ads.js +25 -0
- package/externs/shaka/player.js +8 -0
- package/externs/shaka/text.js +10 -0
- package/karma.conf.js +20 -15
- package/lib/ads/ad_manager.js +46 -26
- package/lib/ads/client_side_ad_manager.js +82 -88
- package/lib/ads/server_side_ad_manager.js +45 -13
- package/lib/cast/cast_proxy.js +6 -2
- package/lib/cast/cast_receiver.js +5 -1
- package/lib/cast/cast_sender.js +2 -2
- package/lib/cea/sei_processor.js +24 -17
- package/lib/dash/dash_parser.js +131 -24
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +74 -17
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/closed_caption_parser.js +3 -1
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/drm_engine.js +20 -1
- package/lib/media/gap_jumping_controller.js +8 -4
- package/lib/media/media_source_engine.js +1 -2
- package/lib/media/playhead.js +1 -1
- package/lib/media/region_observer.js +39 -33
- package/lib/media/region_timeline.js +17 -19
- package/lib/media/segment_index.js +2 -1
- package/lib/media/segment_reference.js +19 -1
- package/lib/media/streaming_engine.js +8 -4
- package/lib/net/networking_engine.js +6 -1
- package/lib/offline/download_manager.js +1 -2
- package/lib/offline/storage.js +8 -9
- package/lib/player.js +129 -59
- package/lib/polyfill/aria.js +12 -2
- package/lib/polyfill/encryption_scheme.js +2 -0
- package/lib/polyfill/fullscreen.js +2 -0
- package/lib/polyfill/mathround.js +2 -0
- package/lib/polyfill/media_capabilities.js +73 -21
- package/lib/polyfill/mediasource.js +8 -0
- package/lib/polyfill/orientation.js +3 -1
- package/lib/polyfill/patchedmediakeys_apple.js +6 -4
- package/lib/polyfill/patchedmediakeys_ms.js +6 -4
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
- package/lib/polyfill/pip_webkit.js +2 -0
- package/lib/polyfill/storage_estimate.js +2 -0
- package/lib/polyfill/video_play_promise.js +2 -0
- package/lib/polyfill/videoplaybackquality.js +2 -0
- package/lib/polyfill/vttcue.js +2 -0
- package/lib/text/cue.js +6 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/text_engine.js +14 -2
- package/lib/text/ttml_text_parser.js +135 -87
- package/lib/text/ui_text_displayer.js +213 -80
- package/lib/text/vtt_text_parser.js +4 -2
- package/lib/text/web_vtt_generator.js +21 -12
- package/lib/util/fake_event.js +53 -7
- package/lib/util/fake_event_target.js +21 -1
- package/lib/util/mp4_box_parsers.js +1 -1
- package/lib/util/periods.js +4 -1
- package/lib/util/platform.js +13 -1
- package/lib/util/stream_utils.js +105 -13
- package/lib/util/xml_utils.js +50 -12
- package/package.json +3 -6
- package/roadmap.md +24 -22
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_receiver_integration.js +1 -1
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/cea/mp4_cea_parser_unit.js +26 -0
- package/test/dash/dash_parser_live_unit.js +43 -0
- package/test/dash/dash_parser_manifest_unit.js +222 -3
- package/test/hls/hls_parser_unit.js +79 -1
- package/test/media/closed_caption_parser_unit.js +20 -0
- package/test/media/drm_engine_integration.js +86 -2
- package/test/media/drm_engine_unit.js +57 -0
- package/test/media/playhead_unit.js +27 -1
- package/test/media/region_observer_unit.js +40 -5
- package/test/media/region_timeline_unit.js +40 -22
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +145 -0
- package/test/player_integration.js +79 -8
- package/test/player_src_equals_integration.js +41 -8
- package/test/player_unit.js +14 -4
- package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
- package/test/test/assets/3675/dash_0.mpd +30 -0
- package/test/test/assets/3675/dash_1.mpd +30 -0
- package/test/test/assets/3675/dash_2.mpd +30 -0
- package/test/test/assets/3675/dash_3.mpd +30 -0
- package/test/test/assets/3675/dash_4.mpd +30 -0
- package/test/test/assets/3675/dash_5.mpd +26 -0
- package/test/test/assets/3675/init-stream0.m4s +0 -0
- package/test/test/assets/chapters.vtt +2 -2
- package/test/test/assets/chapters2.vtt +10 -0
- package/test/test/assets/empty_caption_video_init.mp4 +0 -0
- package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
- package/test/test/util/fake_ad_manager.js +11 -1
- package/test/test/util/ttml_utils.js +81 -0
- package/test/test/util/util.js +10 -7
- package/test/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +542 -220
- package/test/text/ui_text_displayer_unit.js +93 -20
- package/test/text/vtt_text_parser_unit.js +17 -1
- package/test/text/web_vtt_generator_unit.js +58 -3
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/text_displayer_layout_unit.js +21 -8
- package/test/util/mp4_box_parsers_unit.js +28 -0
- package/test/util/stream_utils_unit.js +60 -3
- package/ui/cast_button.js +2 -3
- package/ui/controls.js +11 -6
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +6 -7
- package/ui/locales/ru.json +6 -6
- package/ui/localization.js +40 -87
- package/ui/pip_button.js +2 -3
- package/ui/seek_bar.js +1 -2
- package/ui/text_selection.js +1 -0
|
@@ -73,7 +73,14 @@ shaka.text.UITextDisplayer = class {
|
|
|
73
73
|
this.updateCaptions_();
|
|
74
74
|
}).tickEvery(updatePeriod);
|
|
75
75
|
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Maps cues to cue elements. Specifically points out the wrapper element of
|
|
78
|
+
* the cue (e.g. the HTML element to put nested cues inside).
|
|
79
|
+
* @private {Map.<!shaka.extern.Cue, !{
|
|
80
|
+
* cueElement: !HTMLElement,
|
|
81
|
+
* wrapper: !HTMLElement
|
|
82
|
+
* }>}
|
|
83
|
+
*/
|
|
77
84
|
this.currentCuesMap_ = new Map();
|
|
78
85
|
|
|
79
86
|
/** @private {shaka.util.EventManager} */
|
|
@@ -160,9 +167,14 @@ shaka.text.UITextDisplayer = class {
|
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
// Remove the cues out of the time range.
|
|
170
|
+
const oldNumCues = this.cues_.length;
|
|
163
171
|
this.cues_ = this.cues_.filter(
|
|
164
172
|
(cue) => cue.startTime < start || cue.endTime >= end);
|
|
165
|
-
this
|
|
173
|
+
// If anything was actually removed in this process, force the captions to
|
|
174
|
+
// update. This makes sure that the currently-displayed cues will stop
|
|
175
|
+
// displaying if removed (say, due to the user changing languages).
|
|
176
|
+
const forceUpdate = oldNumCues > this.cues_.length;
|
|
177
|
+
this.updateCaptions_(forceUpdate);
|
|
166
178
|
|
|
167
179
|
return true;
|
|
168
180
|
}
|
|
@@ -185,68 +197,151 @@ shaka.text.UITextDisplayer = class {
|
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
/**
|
|
188
|
-
*
|
|
189
|
-
* @param {
|
|
200
|
+
* @param {!Array.<!shaka.extern.Cue>} cues
|
|
201
|
+
* @param {!HTMLElement} container
|
|
202
|
+
* @param {number} currentTime
|
|
203
|
+
* @param {!Array.<!shaka.extern.Cue>} parents
|
|
190
204
|
* @private
|
|
191
205
|
*/
|
|
192
|
-
|
|
193
|
-
|
|
206
|
+
updateCuesRecursive_(cues, container, currentTime, parents) {
|
|
207
|
+
// Set to true if the cues have changed in some way, which will require
|
|
208
|
+
// DOM changes. E.g. if a cue was added or removed.
|
|
209
|
+
let updateDOM = false;
|
|
210
|
+
/**
|
|
211
|
+
* The elements to remove from the DOM.
|
|
212
|
+
* Some of these elements may be added back again, if their corresponding
|
|
213
|
+
* cue is in toPlant.
|
|
214
|
+
* These elements are only removed if updateDOM is true.
|
|
215
|
+
* @type {!Array.<!HTMLElement>}
|
|
216
|
+
*/
|
|
217
|
+
const toUproot = [];
|
|
218
|
+
/**
|
|
219
|
+
* The cues whose corresponding elements should be in the DOM.
|
|
220
|
+
* Some of these might be new, some might have been displayed beforehand.
|
|
221
|
+
* These will only be added if updateDOM is true.
|
|
222
|
+
* @type {!Array.<!shaka.extern.Cue>}
|
|
223
|
+
*/
|
|
224
|
+
const toPlant = [];
|
|
225
|
+
for (const cue of cues) {
|
|
226
|
+
parents.push(cue);
|
|
227
|
+
|
|
228
|
+
let cueRegistry = this.currentCuesMap_.get(cue);
|
|
229
|
+
const shouldBeDisplayed =
|
|
230
|
+
cue.startTime <= currentTime && cue.endTime > currentTime;
|
|
231
|
+
let wrapper = cueRegistry ? cueRegistry.wrapper : null;
|
|
232
|
+
|
|
233
|
+
if (cueRegistry) {
|
|
234
|
+
// If the cues are replanted, all existing cues should be uprooted,
|
|
235
|
+
// even ones which are going to be planted again.
|
|
236
|
+
toUproot.push(cueRegistry.cueElement);
|
|
237
|
+
|
|
238
|
+
// If the cue should not be displayed, remove it entirely.
|
|
239
|
+
if (!shouldBeDisplayed) {
|
|
240
|
+
// Since something has to be removed, we will need to update the DOM.
|
|
241
|
+
updateDOM = true;
|
|
242
|
+
this.currentCuesMap_.delete(cue);
|
|
243
|
+
cueRegistry = null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
194
246
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
247
|
+
if (shouldBeDisplayed) {
|
|
248
|
+
toPlant.push(cue);
|
|
249
|
+
if (!cueRegistry) {
|
|
250
|
+
// The cue has to be made!
|
|
251
|
+
this.createCue_(cue, parents);
|
|
252
|
+
cueRegistry = this.currentCuesMap_.get(cue);
|
|
253
|
+
wrapper = cueRegistry.wrapper;
|
|
254
|
+
updateDOM = true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
200
257
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.
|
|
258
|
+
// Recursively check the nested cues, to see if they need to be added or
|
|
259
|
+
// removed.
|
|
260
|
+
// If wrapper is null, that means that the cue is not only not being
|
|
261
|
+
// displayed currently, it also was not removed this tick. So it's
|
|
262
|
+
// guaranteed that the children will neither need to be added nor removed.
|
|
263
|
+
if (cue.nestedCues.length > 0 && wrapper) {
|
|
264
|
+
this.updateCuesRecursive_(
|
|
265
|
+
cue.nestedCues, wrapper, currentTime, parents);
|
|
208
266
|
}
|
|
209
|
-
}
|
|
210
267
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
268
|
+
const topCue = parents.pop();
|
|
269
|
+
goog.asserts.assert(topCue == cue, 'Parent cues should be kept in order');
|
|
270
|
+
}
|
|
271
|
+
if (updateDOM) {
|
|
272
|
+
for (const cueElement of toUproot) {
|
|
273
|
+
container.removeChild(cueElement);
|
|
274
|
+
}
|
|
275
|
+
toPlant.sort((a, b) => {
|
|
276
|
+
if (a.startTime != b.startTime) {
|
|
277
|
+
return a.startTime - b.startTime;
|
|
278
|
+
} else {
|
|
279
|
+
return a.endTime - b.endTime;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
for (const cue of toPlant) {
|
|
283
|
+
const cueRegistry = this.currentCuesMap_.get(cue);
|
|
284
|
+
goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
|
|
285
|
+
container.appendChild(cueRegistry.cueElement);
|
|
217
286
|
}
|
|
218
287
|
}
|
|
288
|
+
}
|
|
219
289
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Display the current captions.
|
|
292
|
+
* @param {boolean=} forceUpdate
|
|
293
|
+
* @private
|
|
294
|
+
*/
|
|
295
|
+
updateCaptions_(forceUpdate = false) {
|
|
296
|
+
if (!this.textContainer_) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const currentTime = this.video_.currentTime;
|
|
301
|
+
if (!this.isTextVisible_ || forceUpdate) {
|
|
302
|
+
if (this.currentCuesMap_.size > 0) {
|
|
303
|
+
// Clear away any existing cues.
|
|
304
|
+
shaka.util.Dom.removeAllChildren(this.textContainer_);
|
|
305
|
+
this.currentCuesMap_.clear();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (this.isTextVisible_) {
|
|
309
|
+
// Log currently attached cue elements for verification, later.
|
|
310
|
+
const previousCuesMap = new Map();
|
|
311
|
+
for (const cue of this.currentCuesMap_.keys()) {
|
|
312
|
+
previousCuesMap.set(cue, this.currentCuesMap_.get(cue));
|
|
230
313
|
}
|
|
231
|
-
});
|
|
232
314
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
this.textContainer_,
|
|
236
|
-
|
|
315
|
+
// Update the cues.
|
|
316
|
+
this.updateCuesRecursive_(
|
|
317
|
+
this.cues_, this.textContainer_, currentTime, /* parents= */ []);
|
|
318
|
+
|
|
319
|
+
if (goog.DEBUG) {
|
|
320
|
+
// Previously, we had an issue (#2076) where cues sometimes were not
|
|
321
|
+
// properly removed from the DOM. It is not clear if this issue still
|
|
322
|
+
// happens, so the previous fix for it has been changed to an assert.
|
|
323
|
+
for (const cue of previousCuesMap.keys()) {
|
|
324
|
+
if (!this.currentCuesMap_.has(cue)) {
|
|
325
|
+
// TODO: If the problem does not appear again, then we should remove
|
|
326
|
+
// this assert (and the previousCuesMap code) in Shaka v4.
|
|
327
|
+
const cueElement = previousCuesMap.get(cue).cueElement;
|
|
328
|
+
goog.asserts.assert(
|
|
329
|
+
!cueElement.parentNode, 'Cue was not properly removed!');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
237
333
|
}
|
|
238
334
|
}
|
|
239
335
|
|
|
240
336
|
/**
|
|
241
|
-
*
|
|
337
|
+
* Creates the object for a cue.
|
|
242
338
|
*
|
|
243
|
-
* @param {Element} container
|
|
244
339
|
* @param {!shaka.extern.Cue} cue
|
|
245
|
-
* @param {
|
|
246
|
-
* @return {!Element} the created captions element
|
|
340
|
+
* @param {!Array.<!shaka.extern.Cue>} parents
|
|
247
341
|
* @private
|
|
248
342
|
*/
|
|
249
|
-
|
|
343
|
+
createCue_(cue, parents) {
|
|
344
|
+
const isNested = parents.length > 1;
|
|
250
345
|
let type = isNested ? 'span' : 'div';
|
|
251
346
|
if (cue.lineBreak || cue.spacer) {
|
|
252
347
|
if (cue.spacer) {
|
|
@@ -257,40 +352,42 @@ shaka.text.UITextDisplayer = class {
|
|
|
257
352
|
type = 'br';
|
|
258
353
|
}
|
|
259
354
|
|
|
355
|
+
const needWrapper = !isNested && cue.nestedCues.length > 0;
|
|
356
|
+
|
|
260
357
|
// Nested cues are inline elements. Top-level cues are block elements.
|
|
261
358
|
const cueElement = shaka.util.Dom.createHTMLElement(type);
|
|
262
359
|
if (type != 'br') {
|
|
263
|
-
this.setCaptionStyles_(cueElement, cue,
|
|
360
|
+
this.setCaptionStyles_(cueElement, cue, parents, needWrapper);
|
|
264
361
|
}
|
|
265
362
|
|
|
266
363
|
let wrapper = cueElement;
|
|
267
|
-
if (
|
|
364
|
+
if (needWrapper) {
|
|
268
365
|
// Create a wrapper element which will serve to contain all children into
|
|
269
366
|
// a single item. This ensures that nested span elements appear
|
|
270
367
|
// horizontally and br elements occupy no vertical space.
|
|
271
368
|
wrapper = shaka.util.Dom.createHTMLElement('span');
|
|
272
369
|
wrapper.classList.add('shaka-text-wrapper');
|
|
370
|
+
wrapper.style.backgroundColor = cue.backgroundColor;
|
|
273
371
|
cueElement.appendChild(wrapper);
|
|
274
372
|
}
|
|
275
373
|
|
|
276
|
-
|
|
277
|
-
this.displayCue_(wrapper, nestedCue, /* isNested= */ true);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
container.appendChild(cueElement);
|
|
281
|
-
return cueElement;
|
|
374
|
+
this.currentCuesMap_.set(cue, {cueElement, wrapper});
|
|
282
375
|
}
|
|
283
376
|
|
|
284
377
|
/**
|
|
285
378
|
* @param {!HTMLElement} cueElement
|
|
286
379
|
* @param {!shaka.extern.Cue} cue
|
|
287
|
-
* @param {
|
|
380
|
+
* @param {!Array.<!shaka.extern.Cue>} parents
|
|
381
|
+
* @param {boolean} hasWrapper
|
|
288
382
|
* @private
|
|
289
383
|
*/
|
|
290
|
-
setCaptionStyles_(cueElement, cue,
|
|
384
|
+
setCaptionStyles_(cueElement, cue, parents, hasWrapper) {
|
|
291
385
|
const Cue = shaka.text.Cue;
|
|
292
|
-
|
|
386
|
+
const inherit =
|
|
387
|
+
(cb) => shaka.text.UITextDisplayer.inheritProperty_(parents, cb);
|
|
388
|
+
const style = cueElement.style;
|
|
293
389
|
const isLeaf = cue.nestedCues.length == 0;
|
|
390
|
+
const isNested = parents.length > 1;
|
|
294
391
|
|
|
295
392
|
// TODO: wrapLine is not yet supported. Lines always wrap.
|
|
296
393
|
|
|
@@ -309,16 +406,6 @@ shaka.text.UITextDisplayer = class {
|
|
|
309
406
|
const nonBreakingSpace = '\xa0';
|
|
310
407
|
return nonBreakingSpace.repeat(match.length);
|
|
311
408
|
});
|
|
312
|
-
if (isNested) {
|
|
313
|
-
cueElement.textContent = text;
|
|
314
|
-
} else if (text.length) {
|
|
315
|
-
// If a top-level cue has text, move to a <span> so the background is
|
|
316
|
-
// styled correctly.
|
|
317
|
-
const span = shaka.util.Dom.createHTMLElement('span');
|
|
318
|
-
span.textContent = text;
|
|
319
|
-
cueElement.appendChild(span);
|
|
320
|
-
style = span.style;
|
|
321
|
-
}
|
|
322
409
|
|
|
323
410
|
style.backgroundColor = cue.backgroundColor;
|
|
324
411
|
style.border = cue.border;
|
|
@@ -327,27 +414,49 @@ shaka.text.UITextDisplayer = class {
|
|
|
327
414
|
style.opacity = cue.opacity;
|
|
328
415
|
style.paddingLeft = shaka.text.UITextDisplayer.convertLengthValue_(
|
|
329
416
|
cue.linePadding, cue, this.videoContainer_);
|
|
330
|
-
style.paddingRight =
|
|
331
|
-
|
|
417
|
+
style.paddingRight =
|
|
418
|
+
shaka.text.UITextDisplayer.convertLengthValue_(
|
|
419
|
+
cue.linePadding, cue, this.videoContainer_);
|
|
332
420
|
|
|
333
421
|
if (cue.backgroundImage) {
|
|
334
422
|
style.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
|
|
335
423
|
style.backgroundRepeat = 'no-repeat';
|
|
336
424
|
style.backgroundSize = 'contain';
|
|
337
425
|
style.backgroundPosition = 'center';
|
|
426
|
+
} else {
|
|
427
|
+
// If we have both text and nested cues, then style everything; otherwise
|
|
428
|
+
// place the text in its own <span> so the background doesn't fill the
|
|
429
|
+
// whole region.
|
|
430
|
+
let elem;
|
|
431
|
+
if (cue.nestedCues.length) {
|
|
432
|
+
elem = cueElement;
|
|
433
|
+
} else {
|
|
434
|
+
elem = shaka.util.Dom.createHTMLElement('span');
|
|
435
|
+
cueElement.appendChild(elem);
|
|
436
|
+
}
|
|
338
437
|
|
|
339
|
-
if (cue.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
438
|
+
if (cue.border) {
|
|
439
|
+
elem.style.border = cue.border;
|
|
440
|
+
}
|
|
441
|
+
if (!hasWrapper) {
|
|
442
|
+
const bgColor = inherit((c) => c.backgroundColor);
|
|
443
|
+
if (bgColor) {
|
|
444
|
+
elem.style.backgroundColor = bgColor;
|
|
445
|
+
} else if (text) {
|
|
446
|
+
// If there is no background, default to a semi-transparent black.
|
|
447
|
+
// Only do this for the text itself.
|
|
448
|
+
elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (text) {
|
|
452
|
+
elem.textContent = text;
|
|
344
453
|
}
|
|
345
454
|
}
|
|
346
455
|
|
|
347
456
|
// The displayAlign attribute specifies the vertical alignment of the
|
|
348
457
|
// captions inside the text container. Before means at the top of the
|
|
349
458
|
// text container, and after means at the bottom.
|
|
350
|
-
if (isNested) {
|
|
459
|
+
if (isNested && !parents[parents.length - 1].isContainer) {
|
|
351
460
|
style.display = 'inline';
|
|
352
461
|
} else {
|
|
353
462
|
style.display = 'flex';
|
|
@@ -393,20 +502,28 @@ shaka.text.UITextDisplayer = class {
|
|
|
393
502
|
// TODO: Implement lineAlignment of 'CENTER'.
|
|
394
503
|
if (cue.line) {
|
|
395
504
|
if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
|
|
505
|
+
// When setting absolute positioning, you need to set x/y/width/height
|
|
506
|
+
// so the element is positioned correctly. Set these as default and
|
|
507
|
+
// other settings will override them.
|
|
396
508
|
style.position = 'absolute';
|
|
509
|
+
style.left = '0';
|
|
510
|
+
style.top = '0';
|
|
397
511
|
if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
|
|
512
|
+
style.width = '100%';
|
|
398
513
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
399
514
|
style.top = cue.line + '%';
|
|
400
515
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
401
516
|
style.bottom = cue.line + '%';
|
|
402
517
|
}
|
|
403
518
|
} else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
|
|
519
|
+
style.height = '100%';
|
|
404
520
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
405
521
|
style.left = cue.line + '%';
|
|
406
522
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
407
523
|
style.right = cue.line + '%';
|
|
408
524
|
}
|
|
409
525
|
} else {
|
|
526
|
+
style.height = '100%';
|
|
410
527
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
411
528
|
style.right = cue.line + '%';
|
|
412
529
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
@@ -414,12 +531,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
414
531
|
}
|
|
415
532
|
}
|
|
416
533
|
}
|
|
417
|
-
} else if (cue.region && cue.region.id
|
|
418
|
-
((!isNested && !isLeaf) || (cue.backgroundImage))) {
|
|
419
|
-
// In text-base cues, regions are only applied to block container
|
|
420
|
-
// (!isNested && !isLeaf).
|
|
421
|
-
// In bitmap-based cues, region settings are used to specify the size and
|
|
422
|
-
// position of the backgroundImage.
|
|
534
|
+
} else if (cue.region && cue.region.id) {
|
|
423
535
|
const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
|
|
424
536
|
const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
|
|
425
537
|
const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
|
|
@@ -552,4 +664,25 @@ shaka.text.UITextDisplayer = class {
|
|
|
552
664
|
|
|
553
665
|
return (containerHeight * value / cue.cellResolution.rows) + 'px';
|
|
554
666
|
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Inherits a property from the parent Cue elements. If the value is falsy,
|
|
670
|
+
* it is assumed to be inherited from the parent. This returns null if the
|
|
671
|
+
* value isn't found.
|
|
672
|
+
*
|
|
673
|
+
* @param {!Array.<!shaka.extern.Cue>} parents
|
|
674
|
+
* @param {function(!shaka.extern.Cue):?T} cb
|
|
675
|
+
* @return {?T}
|
|
676
|
+
* @template T
|
|
677
|
+
* @private
|
|
678
|
+
*/
|
|
679
|
+
static inheritProperty_(parents, cb) {
|
|
680
|
+
for (let i = parents.length - 1; i >= 0; i--) {
|
|
681
|
+
const val = cb(parents[i]);
|
|
682
|
+
if (val || val === 0) {
|
|
683
|
+
return val;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
555
688
|
};
|
|
@@ -122,7 +122,7 @@ shaka.text.VttTextParser = class {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
* Add
|
|
125
|
+
* Add default color
|
|
126
126
|
*
|
|
127
127
|
* @param {!Map.<string, shaka.text.Cue>} styles
|
|
128
128
|
* @private
|
|
@@ -436,7 +436,9 @@ shaka.text.VttTextParser = class {
|
|
|
436
436
|
}
|
|
437
437
|
const tagEnd = payload.substring(i + 1, end);
|
|
438
438
|
const tagStart = names.pop();
|
|
439
|
-
if (
|
|
439
|
+
if (!tagEnd || !tagStart) {
|
|
440
|
+
return payload;
|
|
441
|
+
} else if (tagStart === tagEnd) {
|
|
440
442
|
newPayload += '/' + tagEnd + '>';
|
|
441
443
|
i += tagEnd.length + 1;
|
|
442
444
|
} else {
|
|
@@ -17,9 +17,10 @@ goog.require('shaka.text.Cue');
|
|
|
17
17
|
shaka.text.WebVttGenerator = class {
|
|
18
18
|
/**
|
|
19
19
|
* @param {!Array.<!shaka.text.Cue>} cues
|
|
20
|
+
* @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
|
|
20
21
|
* @return {string}
|
|
21
22
|
*/
|
|
22
|
-
static convert(cues) {
|
|
23
|
+
static convert(cues, adCuePoints) {
|
|
23
24
|
// Flatten nested cue payloads recursively. If a cue has nested cues,
|
|
24
25
|
// their contents should be combined and replace the payload of the parent.
|
|
25
26
|
const flattenPayload = (cue) => {
|
|
@@ -64,6 +65,25 @@ shaka.text.WebVttGenerator = class {
|
|
|
64
65
|
}
|
|
65
66
|
};
|
|
66
67
|
|
|
68
|
+
const webvttTimeString = (time) => {
|
|
69
|
+
let newTime = time;
|
|
70
|
+
for (const adCuePoint of adCuePoints) {
|
|
71
|
+
if (adCuePoint.end && adCuePoint.start < time) {
|
|
72
|
+
const offset = adCuePoint.end - adCuePoint.start;
|
|
73
|
+
newTime += offset;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const hours = Math.floor(newTime / 3600);
|
|
77
|
+
const minutes = Math.floor(newTime / 60 % 60);
|
|
78
|
+
const seconds = Math.floor(newTime % 60);
|
|
79
|
+
const milliseconds = Math.floor(newTime * 1000 % 1000);
|
|
80
|
+
return (hours < 10 ? '0' : '') + hours + ':' +
|
|
81
|
+
(minutes < 10 ? '0' : '') + minutes + ':' +
|
|
82
|
+
(seconds < 10 ? '0' : '') + seconds + '.' +
|
|
83
|
+
(milliseconds < 100 ? (milliseconds < 10 ? '00' : '0') : '') +
|
|
84
|
+
milliseconds;
|
|
85
|
+
};
|
|
86
|
+
|
|
67
87
|
// We don't want to modify the array or objects passed in, since we don't
|
|
68
88
|
// technically own them. So we build a new array and replace certain items
|
|
69
89
|
// in it if they need to be flattened.
|
|
@@ -80,17 +100,6 @@ shaka.text.WebVttGenerator = class {
|
|
|
80
100
|
|
|
81
101
|
let webvttString = 'WEBVTT\n\n';
|
|
82
102
|
for (const cue of flattenedCues) {
|
|
83
|
-
const webvttTimeString = (time) => {
|
|
84
|
-
const hours = Math.floor(time / 3600);
|
|
85
|
-
const minutes = Math.floor(time / 60 % 60);
|
|
86
|
-
const seconds = Math.floor(time % 60);
|
|
87
|
-
const milliseconds = Math.floor(time * 1000 % 1000);
|
|
88
|
-
return (hours < 10 ? '0' : '') + hours + ':' +
|
|
89
|
-
(minutes < 10 ? '0' : '') + minutes + ':' +
|
|
90
|
-
(seconds < 10 ? '0' : '') + seconds + '.' +
|
|
91
|
-
(milliseconds < 100 ? (milliseconds < 10 ? '00' : '0') : '') +
|
|
92
|
-
milliseconds;
|
|
93
|
-
};
|
|
94
103
|
const webvttSettings = (cue) => {
|
|
95
104
|
const settings = [];
|
|
96
105
|
const Cue = shaka.text.Cue;
|
package/lib/util/fake_event.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
goog.provide('shaka.util.FakeEvent');
|
|
8
8
|
|
|
9
|
+
goog.require('goog.asserts');
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* @summary Create an Event work-alike object based on the provided dictionary.
|
|
@@ -16,18 +18,62 @@ goog.provide('shaka.util.FakeEvent');
|
|
|
16
18
|
*/
|
|
17
19
|
shaka.util.FakeEvent = class {
|
|
18
20
|
/**
|
|
19
|
-
* @param {
|
|
20
|
-
* @
|
|
21
|
+
* @param {!Event} event
|
|
22
|
+
* @return {!shaka.util.FakeEvent}
|
|
21
23
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
for (const key in
|
|
25
|
-
Object.defineProperty(
|
|
26
|
-
value:
|
|
24
|
+
static fromRealEvent(event) {
|
|
25
|
+
const fakeEvent = new shaka.util.FakeEvent(event.type);
|
|
26
|
+
for (const key in event) {
|
|
27
|
+
Object.defineProperty(fakeEvent, key, {
|
|
28
|
+
value: event[key],
|
|
27
29
|
writable: true,
|
|
28
30
|
enumerable: true,
|
|
29
31
|
});
|
|
30
32
|
}
|
|
33
|
+
return fakeEvent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Allows us to tell the compiler that the dictionary "map" is actually a
|
|
38
|
+
* generic object, for backwards compatibility.
|
|
39
|
+
* @param {!Map.<string, Object>} dict
|
|
40
|
+
* @return {!Object}
|
|
41
|
+
* @suppress {invalidCasts}
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
static recastDictAsObject_(dict) {
|
|
45
|
+
goog.asserts.assert(!(dict instanceof Map), 'dict should not be a map');
|
|
46
|
+
return /** @type {!Object} */ (dict);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} type
|
|
51
|
+
* @param {Map.<string, Object>=} dict
|
|
52
|
+
*/
|
|
53
|
+
constructor(type, dict) {
|
|
54
|
+
if (dict) {
|
|
55
|
+
if (dict instanceof Map) {
|
|
56
|
+
// Take properties from dict if present.
|
|
57
|
+
for (const key of dict.keys()) {
|
|
58
|
+
Object.defineProperty(this, key, {
|
|
59
|
+
value: dict.get(key),
|
|
60
|
+
writable: true,
|
|
61
|
+
enumerable: true,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// For backwards compatibility with external apps that may make use of
|
|
66
|
+
// this public constructor, this should still accept generic objects.
|
|
67
|
+
const obj = shaka.util.FakeEvent.recastDictAsObject_(dict);
|
|
68
|
+
for (const key in obj) {
|
|
69
|
+
Object.defineProperty(this, key, {
|
|
70
|
+
value: obj[key],
|
|
71
|
+
writable: true,
|
|
72
|
+
enumerable: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
31
77
|
|
|
32
78
|
// The properties below cannot be set by the dict. They are all provided
|
|
33
79
|
// for compatibility with native events.
|
|
@@ -9,6 +9,7 @@ goog.provide('shaka.util.FakeEventTarget');
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.log');
|
|
11
11
|
goog.require('shaka.util.FakeEvent');
|
|
12
|
+
goog.require('shaka.util.IReleasable');
|
|
12
13
|
goog.require('shaka.util.MultiMap');
|
|
13
14
|
|
|
14
15
|
|
|
@@ -18,13 +19,14 @@ goog.require('shaka.util.MultiMap');
|
|
|
18
19
|
* to non-DOM classes. Only FakeEvents should be dispatched.
|
|
19
20
|
*
|
|
20
21
|
* @implements {EventTarget}
|
|
22
|
+
* @implements {shaka.util.IReleasable}
|
|
21
23
|
* @exportInterface
|
|
22
24
|
*/
|
|
23
25
|
shaka.util.FakeEventTarget = class {
|
|
24
26
|
/** */
|
|
25
27
|
constructor() {
|
|
26
28
|
/**
|
|
27
|
-
* @private {
|
|
29
|
+
* @private {shaka.util.MultiMap.<shaka.util.FakeEventTarget.ListenerType>}
|
|
28
30
|
*/
|
|
29
31
|
this.listeners_ = new shaka.util.MultiMap();
|
|
30
32
|
|
|
@@ -46,6 +48,9 @@ shaka.util.FakeEventTarget = class {
|
|
|
46
48
|
* @exportInterface
|
|
47
49
|
*/
|
|
48
50
|
addEventListener(type, listener, options) {
|
|
51
|
+
if (!this.listeners_) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
49
54
|
this.listeners_.push(type, listener);
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -73,6 +78,9 @@ shaka.util.FakeEventTarget = class {
|
|
|
73
78
|
* @exportInterface
|
|
74
79
|
*/
|
|
75
80
|
removeEventListener(type, listener, options) {
|
|
81
|
+
if (!this.listeners_) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
76
84
|
this.listeners_.remove(type, listener);
|
|
77
85
|
}
|
|
78
86
|
|
|
@@ -90,6 +98,10 @@ shaka.util.FakeEventTarget = class {
|
|
|
90
98
|
goog.asserts.assert(event instanceof shaka.util.FakeEvent,
|
|
91
99
|
'FakeEventTarget can only dispatch FakeEvents!');
|
|
92
100
|
|
|
101
|
+
if (!this.listeners_) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
let listeners = this.listeners_.get(event.type) || [];
|
|
94
106
|
const universalListeners =
|
|
95
107
|
this.listeners_.get(shaka.util.FakeEventTarget.ALL_EVENTS_);
|
|
@@ -129,6 +141,14 @@ shaka.util.FakeEventTarget = class {
|
|
|
129
141
|
|
|
130
142
|
return event.defaultPrevented;
|
|
131
143
|
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @override
|
|
147
|
+
* @exportInterface
|
|
148
|
+
*/
|
|
149
|
+
release() {
|
|
150
|
+
this.listeners_ = null;
|
|
151
|
+
}
|
|
132
152
|
};
|
|
133
153
|
|
|
134
154
|
/**
|
|
@@ -176,7 +176,7 @@ shaka.util.Mp4BoxParsers = class {
|
|
|
176
176
|
if (version == 1) {
|
|
177
177
|
reader.skip(8); // Skip "creation_time"
|
|
178
178
|
reader.skip(8); // Skip "modification_time"
|
|
179
|
-
trackId = reader.
|
|
179
|
+
trackId = reader.readUint32();
|
|
180
180
|
} else {
|
|
181
181
|
reader.skip(4); // Skip "creation_time"
|
|
182
182
|
reader.skip(4); // Skip "modification_time"
|
package/lib/util/periods.js
CHANGED
|
@@ -80,7 +80,10 @@ shaka.util.PeriodCombiner = class {
|
|
|
80
80
|
|
|
81
81
|
/** @return {!Array.<shaka.extern.Stream>} */
|
|
82
82
|
getTextStreams() {
|
|
83
|
-
|
|
83
|
+
// Return a copy of the array because makeTextStreamsForClosedCaptions
|
|
84
|
+
// may make changes to the contents of the array. Those changes should not
|
|
85
|
+
// propagate back to the PeriodCombiner.
|
|
86
|
+
return this.textStreams_.slice();
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
/** @return {!Array.<shaka.extern.Stream>} */
|