shaka-player 4.7.2 → 4.7.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/README.md +3 -1
- package/dist/deps.js +1 -1
- package/dist/locales.js +31 -31
- package/dist/shaka-player.compiled.d.ts +4 -0
- package/dist/shaka-player.compiled.debug.d.ts +4 -0
- package/dist/shaka-player.compiled.debug.externs.js +5 -0
- package/dist/shaka-player.compiled.debug.js +314 -311
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +5 -0
- package/dist/shaka-player.compiled.js +1120 -1121
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +4 -0
- package/dist/shaka-player.ui.debug.d.ts +4 -0
- package/dist/shaka-player.ui.debug.externs.js +5 -0
- package/dist/shaka-player.ui.debug.js +339 -334
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +5 -0
- package/dist/shaka-player.ui.js +1207 -1208
- package/dist/shaka-player.ui.map +3 -3
- package/lib/ads/client_side_ad.js +13 -2
- package/lib/ads/client_side_ad_manager.js +8 -5
- package/lib/cea/cea608_memory.js +1 -1
- package/lib/cea/cea_decoder.js +1 -1
- package/lib/dash/dash_parser.js +0 -1
- package/lib/hls/hls_parser.js +27 -2
- package/lib/media/media_source_engine.js +14 -9
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/net/networking_utils.js +3 -1
- package/lib/offline/session_deleter.js +0 -8
- package/lib/player.js +12 -6
- package/lib/text/vtt_text_parser.js +5 -0
- package/lib/util/periods.js +284 -230
- package/package.json +1 -1
- package/ui/hidden_seek_button.js +5 -1
- package/ui/play_button.js +17 -0
- package/ui/small_play_button.js +2 -2
package/lib/util/periods.js
CHANGED
|
@@ -11,12 +11,10 @@ goog.require('shaka.log');
|
|
|
11
11
|
goog.require('shaka.media.DrmEngine');
|
|
12
12
|
goog.require('shaka.media.MetaSegmentIndex');
|
|
13
13
|
goog.require('shaka.media.SegmentIndex');
|
|
14
|
-
goog.require('shaka.util.ArrayUtils');
|
|
15
14
|
goog.require('shaka.util.Error');
|
|
16
15
|
goog.require('shaka.util.IReleasable');
|
|
17
16
|
goog.require('shaka.util.LanguageUtils');
|
|
18
17
|
goog.require('shaka.util.ManifestParserUtils');
|
|
19
|
-
goog.require('shaka.util.MapUtils');
|
|
20
18
|
goog.require('shaka.util.MimeUtils');
|
|
21
19
|
|
|
22
20
|
/**
|
|
@@ -107,27 +105,52 @@ shaka.util.PeriodCombiner = class {
|
|
|
107
105
|
}
|
|
108
106
|
|
|
109
107
|
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
* Returns an object that contains arrays of streams by type
|
|
109
|
+
* @param {!Array<shaka.extern.Period>} periods
|
|
110
|
+
* @param {boolean} addDummy
|
|
111
|
+
* @return {{
|
|
112
|
+
* audioStreamsPerPeriod: !Array<!Map<string, shaka.extern.Stream>>,
|
|
113
|
+
* videoStreamsPerPeriod: !Array<!Map<string, shaka.extern.Stream>>,
|
|
114
|
+
* textStreamsPerPeriod: !Array<!Map<string, shaka.extern.Stream>>,
|
|
115
|
+
* imageStreamsPerPeriod: !Array<!Map<string, shaka.extern.Stream>>
|
|
116
|
+
* }}
|
|
118
117
|
* @private
|
|
119
|
-
|
|
120
|
-
getStreamsPerPeriod_(periods) {
|
|
118
|
+
*/
|
|
119
|
+
getStreamsPerPeriod_(periods, addDummy) {
|
|
120
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
121
|
+
const PeriodCombiner = shaka.util.PeriodCombiner;
|
|
122
|
+
|
|
121
123
|
const audioStreamsPerPeriod = [];
|
|
122
124
|
const videoStreamsPerPeriod = [];
|
|
123
125
|
const textStreamsPerPeriod = [];
|
|
124
126
|
const imageStreamsPerPeriod = [];
|
|
125
127
|
|
|
126
128
|
for (const period of periods) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const audioMap = new Map(period.audioStreams.map((s) =>
|
|
130
|
+
[PeriodCombiner.generateAudioKey_(s), s]));
|
|
131
|
+
const videoMap = new Map(period.videoStreams.map((s) =>
|
|
132
|
+
[PeriodCombiner.generateVideoKey_(s), s]));
|
|
133
|
+
const textMap = new Map(period.textStreams.map((s) =>
|
|
134
|
+
[PeriodCombiner.generateTextKey_(s), s]));
|
|
135
|
+
const imageMap = new Map(period.imageStreams.map((s) =>
|
|
136
|
+
[PeriodCombiner.generateImageKey_(s), s]));
|
|
137
|
+
|
|
138
|
+
// It's okay to have a period with no text or images, but our algorithm
|
|
139
|
+
// fails on any period without matching streams. So we add dummy streams
|
|
140
|
+
// to each period. Since we combine text streams by language and image
|
|
141
|
+
// streams by resolution, we might need a dummy even in periods with these
|
|
142
|
+
// streams already.
|
|
143
|
+
if (addDummy) {
|
|
144
|
+
const dummyText = PeriodCombiner.dummyStream_(ContentType.TEXT);
|
|
145
|
+
textMap.set(PeriodCombiner.generateTextKey_(dummyText), dummyText);
|
|
146
|
+
const dummyImage = PeriodCombiner.dummyStream_(ContentType.IMAGE);
|
|
147
|
+
imageMap.set(PeriodCombiner.generateImageKey_(dummyImage), dummyImage);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
audioStreamsPerPeriod.push(audioMap);
|
|
151
|
+
videoStreamsPerPeriod.push(videoMap);
|
|
152
|
+
textStreamsPerPeriod.push(textMap);
|
|
153
|
+
imageStreamsPerPeriod.push(imageMap);
|
|
131
154
|
}
|
|
132
155
|
return {
|
|
133
156
|
audioStreamsPerPeriod,
|
|
@@ -147,17 +170,22 @@ shaka.util.PeriodCombiner = class {
|
|
|
147
170
|
async combinePeriods(periods, isDynamic) {
|
|
148
171
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
149
172
|
|
|
150
|
-
shaka.util.PeriodCombiner.filterOutDuplicates_(periods);
|
|
151
|
-
|
|
152
173
|
// Optimization: for single-period VOD, do nothing. This makes sure
|
|
153
174
|
// single-period DASH content will be 100% accurately represented in the
|
|
154
175
|
// output.
|
|
155
176
|
if (!isDynamic && periods.length == 1) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
177
|
+
// We need to filter out duplicates, so call getStreamsPerPeriod()
|
|
178
|
+
// so it will do that by usage of Map.
|
|
179
|
+
const {
|
|
180
|
+
audioStreamsPerPeriod,
|
|
181
|
+
videoStreamsPerPeriod,
|
|
182
|
+
textStreamsPerPeriod,
|
|
183
|
+
imageStreamsPerPeriod,
|
|
184
|
+
} = this.getStreamsPerPeriod_(periods, /* addDummy= */ false);
|
|
185
|
+
this.audioStreams_ = Array.from(audioStreamsPerPeriod[0].values());
|
|
186
|
+
this.videoStreams_ = Array.from(videoStreamsPerPeriod[0].values());
|
|
187
|
+
this.textStreams_ = Array.from(textStreamsPerPeriod[0].values());
|
|
188
|
+
this.imageStreams_ = Array.from(imageStreamsPerPeriod[0].values());
|
|
161
189
|
} else {
|
|
162
190
|
// Find the first period we haven't seen before. Tag all the periods we
|
|
163
191
|
// see now as "used".
|
|
@@ -187,21 +215,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
187
215
|
videoStreamsPerPeriod,
|
|
188
216
|
textStreamsPerPeriod,
|
|
189
217
|
imageStreamsPerPeriod,
|
|
190
|
-
} = this.getStreamsPerPeriod_(periods);
|
|
191
|
-
|
|
192
|
-
// It's okay to have a period with no text or images, but our algorithm
|
|
193
|
-
// fails on any period without matching streams. So we add dummy streams
|
|
194
|
-
// to each period. Since we combine text streams by language and image
|
|
195
|
-
// streams by resolution, we might need a dummy even in periods with these
|
|
196
|
-
// streams already.
|
|
197
|
-
for (const textStreams of textStreamsPerPeriod) {
|
|
198
|
-
textStreams.push(shaka.util.PeriodCombiner.dummyStream_(
|
|
199
|
-
ContentType.TEXT));
|
|
200
|
-
}
|
|
201
|
-
for (const imageStreams of imageStreamsPerPeriod) {
|
|
202
|
-
imageStreams.push(shaka.util.PeriodCombiner.dummyStream_(
|
|
203
|
-
ContentType.IMAGE));
|
|
204
|
-
}
|
|
218
|
+
} = this.getStreamsPerPeriod_(periods, /* addDummy= */ true);
|
|
205
219
|
|
|
206
220
|
await Promise.all([
|
|
207
221
|
this.combine_(
|
|
@@ -290,92 +304,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
290
304
|
this.variants_ = variants;
|
|
291
305
|
}
|
|
292
306
|
|
|
293
|
-
/**
|
|
294
|
-
* @param {!Array<shaka.extern.Period>} periods
|
|
295
|
-
* @private
|
|
296
|
-
*/
|
|
297
|
-
static filterOutDuplicates_(periods) {
|
|
298
|
-
const PeriodCombiner = shaka.util.PeriodCombiner;
|
|
299
|
-
const ArrayUtils = shaka.util.ArrayUtils;
|
|
300
|
-
const MapUtils = shaka.util.MapUtils;
|
|
301
|
-
|
|
302
|
-
for (const period of periods) {
|
|
303
|
-
// Two video streams are considered to be duplicates of
|
|
304
|
-
// one another if their ids are different, but all the other
|
|
305
|
-
// information is the same.
|
|
306
|
-
period.videoStreams = PeriodCombiner.filterOutStreamDuplicates_(
|
|
307
|
-
period.videoStreams, (v1, v2) => {
|
|
308
|
-
return v1.id !== v2.id &&
|
|
309
|
-
v1.fastSwitching == v2.fastSwitching &&
|
|
310
|
-
v1.width === v2.width &&
|
|
311
|
-
v1.frameRate === v2.frameRate &&
|
|
312
|
-
v1.codecs === v2.codecs &&
|
|
313
|
-
v1.mimeType === v2.mimeType &&
|
|
314
|
-
v1.label === v2.label &&
|
|
315
|
-
ArrayUtils.hasSameElements(v1.roles, v2.roles) &&
|
|
316
|
-
MapUtils.hasSameElements(v1.closedCaptions, v2.closedCaptions) &&
|
|
317
|
-
v1.bandwidth === v2.bandwidth;
|
|
318
|
-
});
|
|
319
|
-
// Two audio streams are considered to be duplicates of
|
|
320
|
-
// one another if their ids are different, but all the other
|
|
321
|
-
// information is the same.
|
|
322
|
-
period.audioStreams = PeriodCombiner.filterOutStreamDuplicates_(
|
|
323
|
-
period.audioStreams, (a1, a2) => {
|
|
324
|
-
return a1.id !== a2.id &&
|
|
325
|
-
a1.fastSwitching == a2.fastSwitching &&
|
|
326
|
-
a1.channelsCount === a2.channelsCount &&
|
|
327
|
-
a1.language === a2.language &&
|
|
328
|
-
a1.bandwidth === a2.bandwidth &&
|
|
329
|
-
a1.label === a2.label &&
|
|
330
|
-
a1.codecs === a2.codecs &&
|
|
331
|
-
a1.mimeType === a2.mimeType &&
|
|
332
|
-
ArrayUtils.hasSameElements(a1.roles, a2.roles) &&
|
|
333
|
-
a1.audioSamplingRate === a2.audioSamplingRate &&
|
|
334
|
-
a1.primary === a2.primary;
|
|
335
|
-
});
|
|
336
|
-
// Two text streams are considered to be duplicates of
|
|
337
|
-
// one another if their ids are different, but all the other
|
|
338
|
-
// information is the same.
|
|
339
|
-
period.textStreams = PeriodCombiner.filterOutStreamDuplicates_(
|
|
340
|
-
period.textStreams, (t1, t2) => {
|
|
341
|
-
return t1.id !== t2.id &&
|
|
342
|
-
t1.language === t2.language &&
|
|
343
|
-
t1.label === t2.label &&
|
|
344
|
-
t1.codecs === t2.codecs &&
|
|
345
|
-
t1.mimeType === t2.mimeType &&
|
|
346
|
-
t1.bandwidth === t2.bandwidth &&
|
|
347
|
-
ArrayUtils.hasSameElements(t1.roles, t2.roles);
|
|
348
|
-
});
|
|
349
|
-
// Two image streams are considered to be duplicates of
|
|
350
|
-
// one another if their ids are different, but all the other
|
|
351
|
-
// information is the same.
|
|
352
|
-
period.imageStreams = PeriodCombiner.filterOutStreamDuplicates_(
|
|
353
|
-
period.imageStreams, (i1, i2) => {
|
|
354
|
-
return i1.id !== i2.id &&
|
|
355
|
-
i1.width === i2.width &&
|
|
356
|
-
i1.codecs === i2.codecs &&
|
|
357
|
-
i1.mimeType === i2.mimeType;
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* @param {!Array<shaka.extern.Stream>} streams
|
|
364
|
-
* @param {function(
|
|
365
|
-
* shaka.extern.Stream, shaka.extern.Stream): boolean} isDuplicate
|
|
366
|
-
* @return {!Array<shaka.extern.Stream>}
|
|
367
|
-
* @private
|
|
368
|
-
*/
|
|
369
|
-
static filterOutStreamDuplicates_(streams, isDuplicate) {
|
|
370
|
-
const filteredStreams = [];
|
|
371
|
-
for (const s1 of streams) {
|
|
372
|
-
const duplicate = filteredStreams.some((s2) => isDuplicate(s1, s2));
|
|
373
|
-
if (!duplicate) {
|
|
374
|
-
filteredStreams.push(s1);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return filteredStreams;
|
|
378
|
-
}
|
|
379
307
|
|
|
380
308
|
/**
|
|
381
309
|
* Stitch together DB streams across periods, taking a mix of stream types.
|
|
@@ -389,6 +317,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
389
317
|
*/
|
|
390
318
|
static async combineDbStreams(streamDbsPerPeriod) {
|
|
391
319
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
320
|
+
const PeriodCombiner = shaka.util.PeriodCombiner;
|
|
392
321
|
|
|
393
322
|
// Optimization: for single-period content, do nothing. This makes sure
|
|
394
323
|
// single-period DASH or any HLS content stored offline will be 100%
|
|
@@ -398,13 +327,21 @@ shaka.util.PeriodCombiner = class {
|
|
|
398
327
|
}
|
|
399
328
|
|
|
400
329
|
const audioStreamDbsPerPeriod = streamDbsPerPeriod.map(
|
|
401
|
-
(streams) => streams
|
|
330
|
+
(streams) => new Map(streams
|
|
331
|
+
.filter((s) => s.type === ContentType.AUDIO)
|
|
332
|
+
.map((s) => [PeriodCombiner.generateAudioKey_(s), s])));
|
|
402
333
|
const videoStreamDbsPerPeriod = streamDbsPerPeriod.map(
|
|
403
|
-
(streams) => streams
|
|
334
|
+
(streams) => new Map(streams
|
|
335
|
+
.filter((s) => s.type === ContentType.VIDEO)
|
|
336
|
+
.map((s) => [PeriodCombiner.generateVideoKey_(s), s])));
|
|
404
337
|
const textStreamDbsPerPeriod = streamDbsPerPeriod.map(
|
|
405
|
-
(streams) => streams
|
|
338
|
+
(streams) => new Map(streams
|
|
339
|
+
.filter((s) => s.type === ContentType.TEXT)
|
|
340
|
+
.map((s) => [PeriodCombiner.generateTextKey_(s), s])));
|
|
406
341
|
const imageStreamDbsPerPeriod = streamDbsPerPeriod.map(
|
|
407
|
-
(streams) => streams
|
|
342
|
+
(streams) => new Map(streams
|
|
343
|
+
.filter((s) => s.type === ContentType.IMAGE)
|
|
344
|
+
.map((s) => [PeriodCombiner.generateImageKey_(s), s])));
|
|
408
345
|
|
|
409
346
|
// It's okay to have a period with no text or images, but our algorithm
|
|
410
347
|
// fails on any period without matching streams. So we add dummy streams to
|
|
@@ -412,12 +349,12 @@ shaka.util.PeriodCombiner = class {
|
|
|
412
349
|
// by resolution, we might need a dummy even in periods with these streams
|
|
413
350
|
// already.
|
|
414
351
|
for (const textStreams of textStreamDbsPerPeriod) {
|
|
415
|
-
|
|
416
|
-
|
|
352
|
+
const dummy = PeriodCombiner.dummyStreamDB_(ContentType.TEXT);
|
|
353
|
+
textStreams.set(PeriodCombiner.generateTextKey_(dummy), dummy);
|
|
417
354
|
}
|
|
418
355
|
for (const imageStreams of imageStreamDbsPerPeriod) {
|
|
419
|
-
|
|
420
|
-
|
|
356
|
+
const dummy = PeriodCombiner.dummyStreamDB_(ContentType.IMAGE);
|
|
357
|
+
imageStreams.set(PeriodCombiner.generateImageKey_(dummy), dummy);
|
|
421
358
|
}
|
|
422
359
|
|
|
423
360
|
const periodCombiner = new shaka.util.PeriodCombiner();
|
|
@@ -488,7 +425,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
488
425
|
*
|
|
489
426
|
* @param {!Array.<T>} outputStreams A list of existing output streams, to
|
|
490
427
|
* facilitate updates for live DASH content. Will be modified and returned.
|
|
491
|
-
* @param {!Array
|
|
428
|
+
* @param {!Array<!Map<string, T>>} streamsPerPeriod A list of maps of Streams
|
|
492
429
|
* from each period.
|
|
493
430
|
* @param {number} firstNewPeriodIndex An index into streamsPerPeriod which
|
|
494
431
|
* represents the first new period that hasn't been processed yet.
|
|
@@ -506,17 +443,14 @@ shaka.util.PeriodCombiner = class {
|
|
|
506
443
|
*/
|
|
507
444
|
async combine_(
|
|
508
445
|
outputStreams, streamsPerPeriod, firstNewPeriodIndex, clone, concat) {
|
|
509
|
-
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
510
|
-
|
|
511
446
|
const unusedStreamsPerPeriod = [];
|
|
512
|
-
for (let i = 0; i <
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
447
|
+
for (let i = 0; i < firstNewPeriodIndex; i++) {
|
|
448
|
+
// This period's streams have all been used already.
|
|
449
|
+
unusedStreamsPerPeriod.push(new Set());
|
|
450
|
+
}
|
|
451
|
+
for (let i = firstNewPeriodIndex; i < streamsPerPeriod.length; i++) {
|
|
452
|
+
// This periods streams are all new.
|
|
453
|
+
unusedStreamsPerPeriod.push(new Set(streamsPerPeriod[i].values()));
|
|
520
454
|
}
|
|
521
455
|
|
|
522
456
|
// First, extend all existing output Streams into the new periods.
|
|
@@ -556,10 +490,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
556
490
|
|
|
557
491
|
for (const unusedStreams of unusedStreamsPerPeriod) {
|
|
558
492
|
for (const stream of unusedStreams) {
|
|
559
|
-
|
|
560
|
-
const isDummyImage = stream.type == ContentType.IMAGE &&
|
|
561
|
-
!stream.tilesLayout;
|
|
562
|
-
if (isDummyText || isDummyImage) {
|
|
493
|
+
if (shaka.util.PeriodCombiner.isDummy_(stream)) {
|
|
563
494
|
// This is one of our dummy streams, so ignore it. We may not use
|
|
564
495
|
// them all, and that's fine.
|
|
565
496
|
continue;
|
|
@@ -590,7 +521,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
590
521
|
/**
|
|
591
522
|
* @param {T} outputStream An existing output stream which needs to be
|
|
592
523
|
* extended into new periods.
|
|
593
|
-
* @param {!Array
|
|
524
|
+
* @param {!Array<!Map<string, T>>} streamsPerPeriod A list of maps of Streams
|
|
594
525
|
* from each period.
|
|
595
526
|
* @param {number} firstNewPeriodIndex An index into streamsPerPeriod which
|
|
596
527
|
* represents the first new period that hasn't been processed yet.
|
|
@@ -689,7 +620,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
689
620
|
* Templatized to handle both DASH Streams and offline StreamDBs.
|
|
690
621
|
*
|
|
691
622
|
* @param {T} stream An input stream on which to base the output stream.
|
|
692
|
-
* @param {!Array
|
|
623
|
+
* @param {!Array<!Map<string, T>>} streamsPerPeriod A list of maps of Streams
|
|
693
624
|
* from each period.
|
|
694
625
|
* @param {function(T):T} clone Make a clone of an input stream.
|
|
695
626
|
* @param {function(T, T)} concat Concatenate the second stream onto the end
|
|
@@ -707,6 +638,11 @@ shaka.util.PeriodCombiner = class {
|
|
|
707
638
|
*/
|
|
708
639
|
createNewOutputStream_(
|
|
709
640
|
stream, streamsPerPeriod, clone, concat, unusedStreamsPerPeriod) {
|
|
641
|
+
// Check do we want to create output stream from dummy stream
|
|
642
|
+
// and if so, return quickly.
|
|
643
|
+
if (shaka.util.PeriodCombiner.isDummy_(stream)) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
710
646
|
// Start by cloning the stream without segments, key IDs, etc.
|
|
711
647
|
const outputStream = clone(stream);
|
|
712
648
|
|
|
@@ -790,9 +726,32 @@ shaka.util.PeriodCombiner = class {
|
|
|
790
726
|
for (const codec of match.codecs.split(',')) {
|
|
791
727
|
codecs.add(codec);
|
|
792
728
|
}
|
|
793
|
-
|
|
729
|
+
const PeriodCombiner = shaka.util.PeriodCombiner;
|
|
730
|
+
outputStream.codecs = PeriodCombiner.filterDuplicateCodecs_(
|
|
731
|
+
Array.from(codecs)).join(',');
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* @param {!Array.<string>} codecs
|
|
738
|
+
* @return {!Array.<string>} codecs
|
|
739
|
+
* @private
|
|
740
|
+
*/
|
|
741
|
+
static filterDuplicateCodecs_(codecs) {
|
|
742
|
+
// Filter out duplicate codecs.
|
|
743
|
+
const seen = new Set();
|
|
744
|
+
const ret = [];
|
|
745
|
+
for (const codec of codecs) {
|
|
746
|
+
const shortCodec = shaka.util.MimeUtils.getCodecBase(codec);
|
|
747
|
+
if (!seen.has(shortCodec)) {
|
|
748
|
+
ret.push(codec);
|
|
749
|
+
seen.add(shortCodec);
|
|
750
|
+
} else {
|
|
751
|
+
shaka.log.debug('Ignoring duplicate codec');
|
|
794
752
|
}
|
|
795
753
|
}
|
|
754
|
+
return ret;
|
|
796
755
|
}
|
|
797
756
|
|
|
798
757
|
/**
|
|
@@ -826,6 +785,8 @@ shaka.util.PeriodCombiner = class {
|
|
|
826
785
|
}
|
|
827
786
|
};
|
|
828
787
|
|
|
788
|
+
// Clone roles array so this output stream can own it.
|
|
789
|
+
clone.roles = clone.roles.slice();
|
|
829
790
|
clone.segmentIndex = null;
|
|
830
791
|
clone.emsgSchemeIdUris = [];
|
|
831
792
|
clone.keyIds = new Set();
|
|
@@ -847,6 +808,8 @@ shaka.util.PeriodCombiner = class {
|
|
|
847
808
|
const clone = /** @type {shaka.extern.StreamDB} */(Object.assign(
|
|
848
809
|
{}, streamDb));
|
|
849
810
|
|
|
811
|
+
// Clone roles array so this output stream can own it.
|
|
812
|
+
clone.roles = clone.roles.slice();
|
|
850
813
|
// These are wiped out now and rebuilt later from the various per-period
|
|
851
814
|
// streams that match this output.
|
|
852
815
|
clone.keyIds = new Set();
|
|
@@ -871,7 +834,17 @@ shaka.util.PeriodCombiner = class {
|
|
|
871
834
|
// the line.
|
|
872
835
|
|
|
873
836
|
// Combine arrays, keeping only the unique elements
|
|
874
|
-
const combineArrays = (
|
|
837
|
+
const combineArrays = (output, input) => {
|
|
838
|
+
if (!output) {
|
|
839
|
+
output = [];
|
|
840
|
+
}
|
|
841
|
+
for (const item of input) {
|
|
842
|
+
if (!output.includes(item)) {
|
|
843
|
+
output.push(item);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return output;
|
|
847
|
+
};
|
|
875
848
|
output.roles = combineArrays(output.roles, input.roles);
|
|
876
849
|
|
|
877
850
|
if (input.emsgSchemeIdUris) {
|
|
@@ -879,8 +852,9 @@ shaka.util.PeriodCombiner = class {
|
|
|
879
852
|
output.emsgSchemeIdUris, input.emsgSchemeIdUris);
|
|
880
853
|
}
|
|
881
854
|
|
|
882
|
-
const
|
|
883
|
-
|
|
855
|
+
for (const keyId of input.keyIds) {
|
|
856
|
+
output.keyIds.add(keyId);
|
|
857
|
+
}
|
|
884
858
|
|
|
885
859
|
if (output.originalId == null) {
|
|
886
860
|
output.originalId = input.originalId;
|
|
@@ -952,11 +926,22 @@ shaka.util.PeriodCombiner = class {
|
|
|
952
926
|
*/
|
|
953
927
|
static concatenateStreamDBs_(output, input) {
|
|
954
928
|
// Combine arrays, keeping only the unique elements
|
|
955
|
-
const combineArrays = (
|
|
929
|
+
const combineArrays = (output, input) => {
|
|
930
|
+
if (!output) {
|
|
931
|
+
output = [];
|
|
932
|
+
}
|
|
933
|
+
for (const item of input) {
|
|
934
|
+
if (!output.includes(item)) {
|
|
935
|
+
output.push(item);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return output;
|
|
939
|
+
};
|
|
956
940
|
output.roles = combineArrays(output.roles, input.roles);
|
|
957
941
|
|
|
958
|
-
const
|
|
959
|
-
|
|
942
|
+
for (const keyId of input.keyIds) {
|
|
943
|
+
output.keyIds.add(keyId);
|
|
944
|
+
}
|
|
960
945
|
|
|
961
946
|
// The output is encrypted if any input was encrypted.
|
|
962
947
|
output.encrypted = output.encrypted && input.encrypted;
|
|
@@ -978,7 +963,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
978
963
|
/**
|
|
979
964
|
* Finds streams in all periods which match the output stream.
|
|
980
965
|
*
|
|
981
|
-
* @param {!Array
|
|
966
|
+
* @param {!Array<!Map<string, T>>} streamsPerPeriod
|
|
982
967
|
* @param {T} outputStream
|
|
983
968
|
*
|
|
984
969
|
* @template T
|
|
@@ -1001,7 +986,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
1001
986
|
/**
|
|
1002
987
|
* Find the best match for the output stream.
|
|
1003
988
|
*
|
|
1004
|
-
* @param {!
|
|
989
|
+
* @param {!Map<string, T>} streams
|
|
1005
990
|
* @param {T} outputStream
|
|
1006
991
|
* @return {?T} Returns null if no match can be found.
|
|
1007
992
|
*
|
|
@@ -1011,29 +996,42 @@ shaka.util.PeriodCombiner = class {
|
|
|
1011
996
|
* @private
|
|
1012
997
|
*/
|
|
1013
998
|
findBestMatchInPeriod_(streams, outputStream) {
|
|
1014
|
-
const
|
|
1015
|
-
'audio':
|
|
1016
|
-
'video':
|
|
1017
|
-
'text': shaka.util.PeriodCombiner.
|
|
1018
|
-
'image': shaka.util.PeriodCombiner.
|
|
1019
|
-
}[outputStream.type];
|
|
1020
|
-
|
|
1021
|
-
const isBetterMatch = {
|
|
1022
|
-
'audio': shaka.util.PeriodCombiner.isAudioStreamBetterMatch_,
|
|
1023
|
-
'video': shaka.util.PeriodCombiner.isVideoStreamBetterMatch_,
|
|
1024
|
-
'text': shaka.util.PeriodCombiner.isTextStreamBetterMatch_,
|
|
1025
|
-
'image': shaka.util.PeriodCombiner.isImageStreamBetterMatch_,
|
|
999
|
+
const getKey = {
|
|
1000
|
+
'audio': shaka.util.PeriodCombiner.generateAudioKey_,
|
|
1001
|
+
'video': shaka.util.PeriodCombiner.generateVideoKey_,
|
|
1002
|
+
'text': shaka.util.PeriodCombiner.generateTextKey_,
|
|
1003
|
+
'image': shaka.util.PeriodCombiner.generateImageKey_,
|
|
1026
1004
|
}[outputStream.type];
|
|
1027
1005
|
|
|
1028
1006
|
let best = null;
|
|
1007
|
+
const key = getKey(outputStream);
|
|
1008
|
+
if (streams.has(key)) {
|
|
1009
|
+
// We've found exact match by hashing.
|
|
1010
|
+
best = streams.get(key);
|
|
1011
|
+
} else {
|
|
1012
|
+
// We haven't found exact match, try to find the best one via
|
|
1013
|
+
// linear search.
|
|
1014
|
+
const areCompatible = {
|
|
1015
|
+
'audio': (os, s) => this.areAVStreamsCompatible_(os, s),
|
|
1016
|
+
'video': (os, s) => this.areAVStreamsCompatible_(os, s),
|
|
1017
|
+
'text': shaka.util.PeriodCombiner.areTextStreamsCompatible_,
|
|
1018
|
+
'image': shaka.util.PeriodCombiner.areImageStreamsCompatible_,
|
|
1019
|
+
}[outputStream.type];
|
|
1020
|
+
const isBetterMatch = {
|
|
1021
|
+
'audio': shaka.util.PeriodCombiner.isAudioStreamBetterMatch_,
|
|
1022
|
+
'video': shaka.util.PeriodCombiner.isVideoStreamBetterMatch_,
|
|
1023
|
+
'text': shaka.util.PeriodCombiner.isTextStreamBetterMatch_,
|
|
1024
|
+
'image': shaka.util.PeriodCombiner.isImageStreamBetterMatch_,
|
|
1025
|
+
}[outputStream.type];
|
|
1026
|
+
|
|
1027
|
+
for (const stream of streams.values()) {
|
|
1028
|
+
if (!areCompatible(outputStream, stream)) {
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1029
1031
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
if (!best || isBetterMatch(outputStream, best, stream)) {
|
|
1036
|
-
best = stream;
|
|
1032
|
+
if (!best || isBetterMatch(outputStream, best, stream)) {
|
|
1033
|
+
best = stream;
|
|
1034
|
+
}
|
|
1037
1035
|
}
|
|
1038
1036
|
}
|
|
1039
1037
|
|
|
@@ -1128,20 +1126,17 @@ shaka.util.PeriodCombiner = class {
|
|
|
1128
1126
|
// For text, we don't care about MIME type or codec. We can always switch
|
|
1129
1127
|
// between text types.
|
|
1130
1128
|
|
|
1131
|
-
// The output stream should not be a dummy stream inserted to fill a period
|
|
1132
|
-
// gap. So reject any candidate if the output has no language. This would
|
|
1133
|
-
// cause findMatchesInAllPeriods_ to return null and this output stream to
|
|
1134
|
-
// be skipped (meaning no output streams based on it).
|
|
1135
|
-
if (!outputStream.language) {
|
|
1136
|
-
return false;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
1129
|
// If the candidate is a dummy, then it is compatible, and we could use it
|
|
1140
1130
|
// if nothing else matches.
|
|
1141
1131
|
if (!candidate.language) {
|
|
1142
1132
|
return true;
|
|
1143
1133
|
}
|
|
1144
1134
|
|
|
1135
|
+
// Forced subtitles should be treated as unique streams
|
|
1136
|
+
if (outputStream.forced !== candidate.forced) {
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1145
1140
|
const languageRelatedness = LanguageUtils.relatedness(
|
|
1146
1141
|
outputStream.language, candidate.language);
|
|
1147
1142
|
|
|
@@ -1170,14 +1165,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
1170
1165
|
// For image, we don't care about MIME type. We can always switch
|
|
1171
1166
|
// between image types.
|
|
1172
1167
|
|
|
1173
|
-
// The output stream should not be a dummy stream inserted to fill a period
|
|
1174
|
-
// gap. So reject any candidate if the output has no tilesLayout. This
|
|
1175
|
-
// would cause findMatchesInAllPeriods_ to return null and this output
|
|
1176
|
-
// stream to be skipped (meaning no output streams based on it).
|
|
1177
|
-
if (!outputStream.tilesLayout) {
|
|
1178
|
-
return false;
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
1168
|
return true;
|
|
1182
1169
|
}
|
|
1183
1170
|
|
|
@@ -1196,13 +1183,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
1196
1183
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
1197
1184
|
const {BETTER, EQUAL, WORSE} = shaka.util.PeriodCombiner.BetterOrWorse;
|
|
1198
1185
|
|
|
1199
|
-
// If the output stream was based on the candidate stream, the candidate
|
|
1200
|
-
// stream should be considered a better match. We can check this by
|
|
1201
|
-
// comparing their ids.
|
|
1202
|
-
if (outputStream.id == candidate.id) {
|
|
1203
|
-
return true;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
1186
|
// An exact match is better than a non-exact match.
|
|
1207
1187
|
const bestIsExact = shaka.util.PeriodCombiner.areAVStreamsExactMatch_(
|
|
1208
1188
|
outputStream, best);
|
|
@@ -1215,9 +1195,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
1215
1195
|
return true;
|
|
1216
1196
|
}
|
|
1217
1197
|
|
|
1218
|
-
// Otherwise, compare the streams' characteristics to determine the best
|
|
1219
|
-
// match.
|
|
1220
|
-
|
|
1221
1198
|
// The most important thing is language. In some cases, we will accept a
|
|
1222
1199
|
// different language across periods when we must.
|
|
1223
1200
|
const bestRelatedness = LanguageUtils.relatedness(
|
|
@@ -1331,13 +1308,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
1331
1308
|
static isVideoStreamBetterMatch_(outputStream, best, candidate) {
|
|
1332
1309
|
const {BETTER, EQUAL, WORSE} = shaka.util.PeriodCombiner.BetterOrWorse;
|
|
1333
1310
|
|
|
1334
|
-
// If the output stream was based on the candidate stream, the candidate
|
|
1335
|
-
// stream should be considered a better match. We can check this by
|
|
1336
|
-
// comparing their ids.
|
|
1337
|
-
if (outputStream.id == candidate.id) {
|
|
1338
|
-
return true;
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
1311
|
// An exact match is better than a non-exact match.
|
|
1342
1312
|
const bestIsExact = shaka.util.PeriodCombiner.areAVStreamsExactMatch_(
|
|
1343
1313
|
outputStream, best);
|
|
@@ -1350,9 +1320,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
1350
1320
|
return true;
|
|
1351
1321
|
}
|
|
1352
1322
|
|
|
1353
|
-
// Otherwise, compare the streams' characteristics to determine the best
|
|
1354
|
-
// match.
|
|
1355
|
-
|
|
1356
1323
|
// Take the video with the closest resolution to the output.
|
|
1357
1324
|
const resolutionBetterOrWorse =
|
|
1358
1325
|
shaka.util.PeriodCombiner.compareClosestPreferLower(
|
|
@@ -1414,16 +1381,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
1414
1381
|
static isTextStreamBetterMatch_(outputStream, best, candidate) {
|
|
1415
1382
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
1416
1383
|
|
|
1417
|
-
// If the output stream was based on the candidate stream, the candidate
|
|
1418
|
-
// stream should be considered a better match. We can check this by
|
|
1419
|
-
// comparing their ids.
|
|
1420
|
-
if (outputStream.id == candidate.id) {
|
|
1421
|
-
return true;
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
// Otherwise, compare the streams' characteristics to determine the best
|
|
1425
|
-
// match.
|
|
1426
|
-
|
|
1427
1384
|
// The most important thing is language. In some cases, we will accept a
|
|
1428
1385
|
// different language across periods when we must.
|
|
1429
1386
|
const bestRelatedness = LanguageUtils.relatedness(
|
|
@@ -1500,13 +1457,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
1500
1457
|
static isImageStreamBetterMatch_(outputStream, best, candidate) {
|
|
1501
1458
|
const {BETTER, EQUAL, WORSE} = shaka.util.PeriodCombiner.BetterOrWorse;
|
|
1502
1459
|
|
|
1503
|
-
// If the output stream was based on the candidate stream, the candidate
|
|
1504
|
-
// stream should be considered a better match. We can check this by
|
|
1505
|
-
// comparing their ids.
|
|
1506
|
-
if (outputStream.id == candidate.id) {
|
|
1507
|
-
return true;
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
1460
|
// Take the image with the closest resolution to the output.
|
|
1511
1461
|
const resolutionBetterOrWorse =
|
|
1512
1462
|
shaka.util.PeriodCombiner.compareClosestPreferLower(
|
|
@@ -1685,6 +1635,110 @@ shaka.util.PeriodCombiner = class {
|
|
|
1685
1635
|
|
|
1686
1636
|
return EQUAL;
|
|
1687
1637
|
}
|
|
1638
|
+
|
|
1639
|
+
/**
|
|
1640
|
+
* @param {T} stream
|
|
1641
|
+
* @return {boolean}
|
|
1642
|
+
* @template T
|
|
1643
|
+
* Accepts either a StreamDB or Stream type.
|
|
1644
|
+
* @private
|
|
1645
|
+
*/
|
|
1646
|
+
static isDummy_(stream) {
|
|
1647
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
1648
|
+
switch (stream.type) {
|
|
1649
|
+
case ContentType.TEXT:
|
|
1650
|
+
return !stream.language;
|
|
1651
|
+
case ContentType.IMAGE:
|
|
1652
|
+
return !stream.tilesLayout;
|
|
1653
|
+
default:
|
|
1654
|
+
return false;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* @param {T} v
|
|
1660
|
+
* @return {string}
|
|
1661
|
+
* @template T
|
|
1662
|
+
* Accepts either a StreamDB or Stream type.
|
|
1663
|
+
* @private
|
|
1664
|
+
*/
|
|
1665
|
+
static generateVideoKey_(v) {
|
|
1666
|
+
return shaka.util.PeriodCombiner.generateKey_([
|
|
1667
|
+
v.fastSwitching,
|
|
1668
|
+
v.width,
|
|
1669
|
+
v.frameRate,
|
|
1670
|
+
v.codecs,
|
|
1671
|
+
v.mimeType,
|
|
1672
|
+
v.label,
|
|
1673
|
+
v.roles,
|
|
1674
|
+
v.closedCaptions ? Array.from(v.closedCaptions.entries()) : null,
|
|
1675
|
+
v.bandwidth,
|
|
1676
|
+
]);
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
/**
|
|
1680
|
+
* @param {T} a
|
|
1681
|
+
* @return {string}
|
|
1682
|
+
* @template T
|
|
1683
|
+
* Accepts either a StreamDB or Stream type.
|
|
1684
|
+
* @private
|
|
1685
|
+
*/
|
|
1686
|
+
static generateAudioKey_(a) {
|
|
1687
|
+
return shaka.util.PeriodCombiner.generateKey_([
|
|
1688
|
+
a.fastSwitching,
|
|
1689
|
+
a.channelsCount,
|
|
1690
|
+
a.language,
|
|
1691
|
+
a.bandwidth,
|
|
1692
|
+
a.label,
|
|
1693
|
+
a.codecs,
|
|
1694
|
+
a.mimeType,
|
|
1695
|
+
a.roles,
|
|
1696
|
+
a.audioSamplingRate,
|
|
1697
|
+
a.primary,
|
|
1698
|
+
]);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* @param {T} t
|
|
1703
|
+
* @return {string}
|
|
1704
|
+
* @template T
|
|
1705
|
+
* Accepts either a StreamDB or Stream type.
|
|
1706
|
+
* @private
|
|
1707
|
+
*/
|
|
1708
|
+
static generateTextKey_(t) {
|
|
1709
|
+
return shaka.util.PeriodCombiner.generateKey_([
|
|
1710
|
+
t.language,
|
|
1711
|
+
t.label,
|
|
1712
|
+
t.codecs,
|
|
1713
|
+
t.mimeType,
|
|
1714
|
+
t.bandwidth,
|
|
1715
|
+
t.roles,
|
|
1716
|
+
]);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
/**
|
|
1720
|
+
* @param {T} i
|
|
1721
|
+
* @return {string}
|
|
1722
|
+
* @template T
|
|
1723
|
+
* Accepts either a StreamDB or Stream type.
|
|
1724
|
+
* @private
|
|
1725
|
+
*/
|
|
1726
|
+
static generateImageKey_(i) {
|
|
1727
|
+
return shaka.util.PeriodCombiner.generateKey_([
|
|
1728
|
+
i.width,
|
|
1729
|
+
i.codecs,
|
|
1730
|
+
i.mimeType,
|
|
1731
|
+
]);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* @param {!Array<*>} values
|
|
1736
|
+
* @return {string}
|
|
1737
|
+
* @private
|
|
1738
|
+
*/
|
|
1739
|
+
static generateKey_(values) {
|
|
1740
|
+
return JSON.stringify(values);
|
|
1741
|
+
}
|
|
1688
1742
|
};
|
|
1689
1743
|
|
|
1690
1744
|
/**
|