shaka-player 4.6.5 → 4.6.7

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.
Files changed (40) hide show
  1. package/AUTHORS +1 -0
  2. package/CHANGELOG.md +32 -0
  3. package/CONTRIBUTORS +1 -0
  4. package/demo/main.js +11 -1
  5. package/dist/demo.compiled.debug.js +30 -30
  6. package/dist/demo.compiled.debug.map +2 -2
  7. package/dist/demo.compiled.js +30 -30
  8. package/dist/demo.compiled.map +2 -2
  9. package/dist/locales.js +31 -31
  10. package/dist/shaka-player.compiled.debug.js +112 -112
  11. package/dist/shaka-player.compiled.debug.map +2 -2
  12. package/dist/shaka-player.compiled.js +708 -706
  13. package/dist/shaka-player.compiled.map +4 -4
  14. package/dist/shaka-player.ui.debug.js +69 -70
  15. package/dist/shaka-player.ui.debug.map +3 -3
  16. package/dist/shaka-player.ui.js +834 -834
  17. package/dist/shaka-player.ui.map +2 -2
  18. package/docs/tutorials/fairplay.md +2 -2
  19. package/externs/isoboxer.js +20 -12
  20. package/externs/shaka/codecs.js +4 -0
  21. package/lib/ads/client_side_ad.js +9 -0
  22. package/lib/ads/client_side_ad_manager.js +3 -2
  23. package/lib/ads/media_tailor_ad_manager.js +3 -2
  24. package/lib/hls/hls_parser.js +12 -1
  25. package/lib/media/segment_prefetch.js +2 -2
  26. package/lib/net/networking_utils.js +3 -1
  27. package/lib/player.js +1 -1
  28. package/lib/polyfill/patchedmediakeys_apple.js +6 -0
  29. package/lib/text/ui_text_displayer.js +7 -0
  30. package/lib/transmuxer/mss_transmuxer.js +3 -2
  31. package/lib/transmuxer/ts_transmuxer.js +4 -4
  32. package/lib/util/mp4_generator.js +14 -10
  33. package/lib/util/periods.js +5 -0
  34. package/lib/util/platform.js +20 -1
  35. package/lib/util/ts_parser.js +15 -16
  36. package/lib/util/uint8array_utils.js +12 -7
  37. package/package.json +1 -1
  38. package/test/text/ui_text_displayer_unit.js +9 -0
  39. package/test/util/periods_unit.js +49 -0
  40. package/ui/controls.js +2 -1
@@ -98,9 +98,9 @@ player.getNetworkingEngine().registerRequestFilter((type, request, context) => {
98
98
  const originalPayload = new Uint8Array(request.body);
99
99
  const base64Payload =
100
100
  shaka.util.Uint8ArrayUtils.toStandardBase64(originalPayload);
101
- const params = 'spc=' + base64Payload;
101
+ const params = 'spc=' + encodeURIComponent(base64Payload);
102
102
  request.headers['Content-Type'] = 'application/x-www-form-urlencoded';
103
- request.body = shaka.util.StringUtils.toUTF8(encodeURIComponent(params));
103
+ request.body = shaka.util.StringUtils.toUTF8(params);
104
104
  });
105
105
 
106
106
  player.getNetworkingEngine().registerResponseFilter((type, response, context) => {
@@ -13,27 +13,35 @@
13
13
  /**
14
14
  * @typedef {{
15
15
  * Utils: !ISOBoxerUtils,
16
- * parseBuffer: function(!(ArrayBuffer|ArrayBufferView)):!ISOBoxer,
16
+ * parseBuffer: function(!(ArrayBuffer|ArrayBufferView)):!ISOFile,
17
17
  * createBox: function(string, !ISOBoxer, boolean=):!ISOBoxer,
18
18
  * createFullBox: function(string, !ISOBoxer, ?ISOBoxer=):!ISOBoxer,
19
19
  * addBoxProcessor: function(string, function()):!ISOBoxer,
20
- * createFile: function():!ISOBoxer,
21
- * write: function():!ArrayBuffer,
22
- * fetch: function(!ArrayBuffer):!ISOBoxer
20
+ * createFile: function():!ISOFile,
23
21
  * }}
24
22
  * @property {!ISOBoxerUtils} Utils
25
- * @property {function(!(ArrayBuffer|ArrayBufferView)):!ISOBoxer} parseBuffer
23
+ * @property {function(!(ArrayBuffer|ArrayBufferView)):!ISOFile} parseBuffer
26
24
  * @property {function(string, !ISOBoxer, boolean=):!ISOBoxer} createBox
27
25
  * @property {function(string, !ISOBoxer, ?ISOBoxer=):!ISOBoxer} createFullBox
28
26
  * @property {function(string, function()):!ISOBoxer} addBoxProcessor
29
- * @property {function():!ISOBoxer} createFile
30
- * @property {function():!ArrayBuffer} write
31
- * @property {function(!ArrayBuffer):!ISOBoxer} fetch
27
+ * @property {function():!ISOFile} createFile
32
28
  * @const
33
29
  */
34
30
  var ISOBoxer;
35
31
 
36
32
 
33
+ /**
34
+ * @typedef {{
35
+ * write: function():!ArrayBuffer,
36
+ * fetch: function(string):!ISOBox
37
+ * }}
38
+ * @property {function():!ArrayBuffer} write
39
+ * @property {function(string):!ISOBox} fetch
40
+ * @const
41
+ */
42
+ var ISOFile;
43
+
44
+
37
45
  /**
38
46
  * @typedef {{
39
47
  * appendBox: function(!ISOBoxer, !ISOBoxer):!ISOBox
@@ -91,7 +99,7 @@ var ISOBoxerUtils;
91
99
  * graphicsmode: number,
92
100
  * opcolor: Array.<number>,
93
101
  * balance: number,
94
- * entries: Array.<!ISOBox>,
102
+ * entries: Array.<!ISOEntry>,
95
103
  * location: string,
96
104
  * data_reference_index: number,
97
105
  * pre_defined1: number,
@@ -119,7 +127,7 @@ var ISOBoxerUtils;
119
127
  * default_sample_size: number,
120
128
  * default_sample_flags: number,
121
129
  * baseMediaDecodeTime: number,
122
- * usertype: ?string,
130
+ * usertype: (string|null|undefined),
123
131
  * offset: Array.<number>,
124
132
  * sample_info_size: Array.<number>,
125
133
  * data_offset: number
@@ -170,7 +178,7 @@ var ISOBoxerUtils;
170
178
  * @property {number} graphicsmode
171
179
  * @property {Array.<number>} opcolor
172
180
  * @property {number} balance
173
- * @property {Array.<!ISOBox} entries
181
+ * @property {Array.<!ISOEntry>} entries
174
182
  * @property {string} location
175
183
  * @property {number} data_reference_index
176
184
  * @property {number} pre_defined1
@@ -198,7 +206,7 @@ var ISOBoxerUtils;
198
206
  * @property {number} default_sample_size
199
207
  * @property {number} default_sample_flags
200
208
  * @property {number} baseMediaDecodeTime
201
- * @property {?string} usertype
209
+ * @property {string|null|undefined} usertype
202
210
  * @property {Array.<number} offset
203
211
  * @property {Array.<number} sample_info_size
204
212
  * @property {number} data_offset
@@ -4,6 +4,10 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ /**
8
+ * @externs
9
+ */
10
+
7
11
  /**
8
12
  * @typedef {{
9
13
  * data: !Uint8Array,
@@ -207,6 +207,15 @@ shaka.ads.ClientSideAd = class {
207
207
  }
208
208
  }
209
209
 
210
+ /**
211
+ * It's required for a muted ad to start when autoplaying.
212
+ *
213
+ * @param {number} videoVolume
214
+ */
215
+ setInitialMuted(videoVolume) {
216
+ this.volume_ = videoVolume;
217
+ this.manager_.setVolume(0);
218
+ }
210
219
 
211
220
  /**
212
221
  * @override
@@ -532,9 +532,10 @@ shaka.ads.ClientSideAdManager = class {
532
532
  if (this.ad_.isLinear()) {
533
533
  this.adContainer_.setAttribute('ad-active', 'true');
534
534
  this.video_.pause();
535
- this.ad_.setVolume(this.video_.volume);
536
535
  if (this.video_.muted) {
537
- this.ad_.setMuted(true);
536
+ this.ad_.setInitialMuted(this.video_.volume);
537
+ } else {
538
+ this.ad_.setVolume(this.video_.volume);
538
539
  }
539
540
  }
540
541
  }
@@ -576,8 +576,9 @@ shaka.ads.MediaTailorAdManager = class {
576
576
  if (!app.data.source.length) {
577
577
  continue;
578
578
  }
579
- const imageElement = document.createElement('img');
580
- imageElement.setAttribute('src', app.data.source[0].url);
579
+ const imageElement = /** @type {!HTMLImageElement} */ (
580
+ document.createElement('img'));
581
+ imageElement.src = app.data.source[0].url;
581
582
  imageElement.style.top = (app.placeholder.top || 0) + '%';
582
583
  imageElement.style.height = (100 - (app.placeholder.top || 0)) + '%';
583
584
  imageElement.style.left = (app.placeholder.left || 0) + '%';
@@ -295,6 +295,7 @@ shaka.hls.HlsParser = class {
295
295
  this.groupIdToStreamInfosMap_.clear();
296
296
  this.groupIdToCodecsMap_.clear();
297
297
  this.globalVariables_.clear();
298
+ this.mapTagToInitSegmentRefMap_.clear();
298
299
 
299
300
  if (this.contentSteeringManager_) {
300
301
  this.contentSteeringManager_.destroy();
@@ -905,6 +906,7 @@ shaka.hls.HlsParser = class {
905
906
  let initData = null;
906
907
  const initSegmentRef = this.getInitSegmentReference_(
907
908
  playlist, middleSegment.tags, getUris);
909
+ this.mapTagToInitSegmentRefMap_.clear();
908
910
  if (initSegmentRef) {
909
911
  const initSegmentRequest = shaka.net.NetworkingEngine.makeRequest(
910
912
  initSegmentRef.getUris(), this.config_.retryParameters);
@@ -3450,6 +3452,13 @@ shaka.hls.HlsParser = class {
3450
3452
  if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
3451
3453
  firstStartTime = mediaSequenceToStartTime.get(position);
3452
3454
  }
3455
+ // This is for recovering from disconnects.
3456
+ if (firstStartTime === 0 &&
3457
+ this.presentationType_ == shaka.hls.HlsParser.PresentationType_.LIVE &&
3458
+ mediaSequenceToStartTime.size > 0 &&
3459
+ !mediaSequenceToStartTime.has(position)) {
3460
+ firstStartTime = this.presentationTimeline_.getSegmentAvailabilityStart();
3461
+ }
3453
3462
 
3454
3463
  /** @type {!Array.<!shaka.media.SegmentReference>} */
3455
3464
  const references = [];
@@ -3764,7 +3773,9 @@ shaka.hls.HlsParser = class {
3764
3773
 
3765
3774
  contentMimeType = response.headers['content-type'];
3766
3775
  } catch (error) {
3767
- if (error && error.code == shaka.util.Error.Code.HTTP_ERROR) {
3776
+ if (error &&
3777
+ (error.code == shaka.util.Error.Code.HTTP_ERROR ||
3778
+ error.code == shaka.util.Error.Code.BAD_HTTP_STATUS)) {
3768
3779
  headRequest.method = 'GET';
3769
3780
  const response = await this.makeNetworkRequest_(
3770
3781
  headRequest, requestType, {type});
@@ -165,12 +165,12 @@ shaka.media.SegmentPrefetch = class {
165
165
  if (reference instanceof shaka.media.SegmentReference) {
166
166
  shaka.log.debug(
167
167
  logPrefix,
168
- 'reused prefetched segment at time:', reference.startTime,
168
+ 'missed segment at time:', reference.startTime,
169
169
  'mapSize', this.segmentPrefetchMap_.size);
170
170
  } else {
171
171
  shaka.log.debug(
172
172
  logPrefix,
173
- 'reused prefetched init segment at time, mapSize',
173
+ 'missed init segment at time, mapSize',
174
174
  this.segmentPrefetchMap_.size);
175
175
  }
176
176
  return null;
@@ -37,7 +37,9 @@ shaka.net.NetworkingUtils = class {
37
37
  const response = await netEngine.request(type, request).promise;
38
38
  mimeType = response.headers['content-type'];
39
39
  } catch (error) {
40
- if (error && error.code == shaka.util.Error.Code.HTTP_ERROR) {
40
+ if (error &&
41
+ (error.code == shaka.util.Error.Code.HTTP_ERROR ||
42
+ error.code == shaka.util.Error.Code.BAD_HTTP_STATUS)) {
41
43
  request.method = 'GET';
42
44
  const response = await netEngine.request(type, request).promise;
43
45
  mimeType = response.headers['content-type'];
package/lib/player.js CHANGED
@@ -6779,7 +6779,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6779
6779
  * @export
6780
6780
  */
6781
6781
  // eslint-disable-next-line no-useless-concat
6782
- shaka.Player.version = 'v4.6.5' + '-uncompiled'; // x-release-please-version
6782
+ shaka.Player.version = 'v4.6.7' + '-uncompiled'; // x-release-please-version
6783
6783
 
6784
6784
  // Initialize the deprecation system using the version string we just set
6785
6785
  // on the player.
@@ -791,3 +791,9 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
791
791
  goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
792
792
  }
793
793
  };
794
+
795
+ if (!navigator.requestMediaKeySystemAccess ||
796
+ // eslint-disable-next-line no-restricted-syntax
797
+ !MediaKeySystemAccess.prototype.getConfiguration) {
798
+ shaka.polyfill.register(shaka.polyfill.PatchedMediaKeysApple.install);
799
+ }
@@ -154,6 +154,11 @@ shaka.text.UITextDisplayer = class {
154
154
  * @export
155
155
  */
156
156
  destroy() {
157
+ // Return resolved promise if destroy() has been called.
158
+ if (!this.textContainer_) {
159
+ return Promise.resolve();
160
+ }
161
+
157
162
  // Remove the text container element from the UI.
158
163
  this.videoContainer_.removeChild(this.textContainer_);
159
164
  this.textContainer_ = null;
@@ -176,6 +181,8 @@ shaka.text.UITextDisplayer = class {
176
181
  this.resizeObserver_.disconnect();
177
182
  this.resizeObserver_ = null;
178
183
  }
184
+
185
+ return Promise.resolve();
179
186
  }
180
187
 
181
188
 
@@ -253,6 +253,7 @@ shaka.transmuxer.MssTransmuxer = class {
253
253
  */
254
254
  processMediaSegment_(data, stream, reference) {
255
255
  let i;
256
+ /** @type {!ISOFile} */
256
257
  const isoFile = this.isoBoxer_.parseBuffer(data);
257
258
  // Update track_Id in tfhd box
258
259
  const tfhd = isoFile.fetch('tfhd');
@@ -313,8 +314,8 @@ shaka.transmuxer.MssTransmuxer = class {
313
314
  // (subsample_count field size)
314
315
  // 6 = 2 (BytesOfClearData field size) + 4
315
316
  // (BytesOfEncryptedData field size)
316
- saiz.sample_info_size[i] =
317
- 10 + (6 * sepiff.entry[i].NumberOfEntries);
317
+ const entry = /** @type {!ISOEntry} */ (sepiff.entry[i]);
318
+ saiz.sample_info_size[i] = 10 + (6 * entry.NumberOfEntries);
318
319
  }
319
320
  } else {
320
321
  // No sub-sample encryption => set default
@@ -705,7 +705,7 @@ shaka.transmuxer.TsTransmuxer = class {
705
705
  /** @type {?number} */
706
706
  let baseMediaDecodeTime = null;
707
707
 
708
- let nalus = [];
708
+ const nalus = [];
709
709
  const videoData = tsParser.getVideoData();
710
710
  if (!videoData.length) {
711
711
  throw new shaka.util.Error(
@@ -716,7 +716,7 @@ shaka.transmuxer.TsTransmuxer = class {
716
716
  for (let i = 0; i < videoData.length; i++) {
717
717
  const pes = videoData[i];
718
718
  const dataNalus = pes.nalus;
719
- nalus = nalus.concat(dataNalus);
719
+ nalus.push(...dataNalus);
720
720
  const frame = H264.parseFrame(dataNalus);
721
721
  if (!frame) {
722
722
  continue;
@@ -797,7 +797,7 @@ shaka.transmuxer.TsTransmuxer = class {
797
797
  /** @type {?number} */
798
798
  let baseMediaDecodeTime = null;
799
799
 
800
- let nalus = [];
800
+ const nalus = [];
801
801
  const videoData = tsParser.getVideoData();
802
802
  if (!videoData.length) {
803
803
  throw new shaka.util.Error(
@@ -808,7 +808,7 @@ shaka.transmuxer.TsTransmuxer = class {
808
808
  for (let i = 0; i < videoData.length; i++) {
809
809
  const pes = videoData[i];
810
810
  const dataNalus = pes.nalus;
811
- nalus = nalus.concat(dataNalus);
811
+ nalus.push(...dataNalus);
812
812
  const frame = H265.parseFrame(dataNalus);
813
813
  if (!frame) {
814
814
  continue;
@@ -47,10 +47,11 @@ shaka.util.Mp4Generator = class {
47
47
  goog.asserts.assert(this.streamInfos_.length > 0,
48
48
  'StreamInfos must have elements');
49
49
  const Mp4Generator = shaka.util.Mp4Generator;
50
- let traks = new Uint8Array([]);
50
+ const trakArrays = [];
51
51
  for (const streamInfo of this.streamInfos_) {
52
- traks = shaka.util.Uint8ArrayUtils.concat(traks, this.trak_(streamInfo));
52
+ trakArrays.push(this.trak_(streamInfo));
53
53
  }
54
+ const traks = shaka.util.Uint8ArrayUtils.concat(...trakArrays);
54
55
  const firstStreamInfo = this.streamInfos_[0];
55
56
  return Mp4Generator.box('moov',
56
57
  this.mvhd_(firstStreamInfo),
@@ -693,10 +694,11 @@ shaka.util.Mp4Generator = class {
693
694
  */
694
695
  mvex_() {
695
696
  const Mp4Generator = shaka.util.Mp4Generator;
696
- let trexs = new Uint8Array([]);
697
+ const trexArrays = [];
697
698
  for (const streamInfo of this.streamInfos_) {
698
- trexs = shaka.util.Uint8ArrayUtils.concat(trexs, this.trex_(streamInfo));
699
+ trexArrays.push(this.trex_(streamInfo));
699
700
  }
701
+ const trexs = shaka.util.Uint8ArrayUtils.concat(...trexArrays);
700
702
  return Mp4Generator.box('mvex', trexs);
701
703
  }
702
704
 
@@ -730,9 +732,9 @@ shaka.util.Mp4Generator = class {
730
732
  * @private
731
733
  */
732
734
  pssh_(streamInfo) {
733
- let boxes = new Uint8Array([]);
735
+ const initDatas = [];
734
736
  if (!streamInfo.encrypted) {
735
- return boxes;
737
+ return new Uint8Array([]);
736
738
  }
737
739
 
738
740
  for (const drmInfo of streamInfo.stream.drmInfos) {
@@ -740,9 +742,10 @@ shaka.util.Mp4Generator = class {
740
742
  continue;
741
743
  }
742
744
  for (const initData of drmInfo.initData) {
743
- boxes = shaka.util.Uint8ArrayUtils.concat(boxes, initData.initData);
745
+ initDatas.push(initData.initData);
744
746
  }
745
747
  }
748
+ const boxes = shaka.util.Uint8ArrayUtils.concat(...initDatas);
746
749
  return boxes;
747
750
  }
748
751
 
@@ -845,11 +848,12 @@ shaka.util.Mp4Generator = class {
845
848
  * @return {!Uint8Array}
846
849
  */
847
850
  segmentData() {
848
- let result = new Uint8Array([]);
851
+ const segmentDataArray = [];
849
852
  for (const streamInfo of this.streamInfos_) {
850
- result = shaka.util.Uint8ArrayUtils.concat(result,
851
- this.moof_(streamInfo), this.mdat_(streamInfo));
853
+ segmentDataArray.push(
854
+ ...[this.moof_(streamInfo), this.mdat_(streamInfo)]);
852
855
  }
856
+ const result = shaka.util.Uint8ArrayUtils.concat(...segmentDataArray);
853
857
  return result;
854
858
  }
855
859
 
@@ -1097,6 +1097,11 @@ shaka.util.PeriodCombiner = class {
1097
1097
  return true;
1098
1098
  }
1099
1099
 
1100
+ // Forced subtitles should be treated as unique streams
1101
+ if (outputStream.forced !== candidate.forced) {
1102
+ return false;
1103
+ }
1104
+
1100
1105
  const languageRelatedness = LanguageUtils.relatedness(
1101
1106
  outputStream.language, candidate.language);
1102
1107
 
@@ -234,10 +234,12 @@ shaka.util.Platform = class {
234
234
  return !!navigator.vendor && navigator.vendor.includes('Apple') &&
235
235
  !shaka.util.Platform.isTizen() &&
236
236
  !shaka.util.Platform.isEOS() &&
237
+ !shaka.util.Platform.isAPL() &&
237
238
  !shaka.util.Platform.isVirginMedia() &&
238
239
  !shaka.util.Platform.isOrange() &&
239
240
  !shaka.util.Platform.isPS4() &&
240
- !shaka.util.Platform.isAmazonFireTV();
241
+ !shaka.util.Platform.isAmazonFireTV() &&
242
+ !shaka.util.Platform.isWPE();
241
243
  }
242
244
 
243
245
  /**
@@ -292,6 +294,14 @@ shaka.util.Platform = class {
292
294
  return shaka.util.Platform.userAgentContains_('AFT');
293
295
  }
294
296
 
297
+ /**
298
+ * Check if the current platform is Comcast X1.
299
+ * @return {boolean}
300
+ */
301
+ static isWPE() {
302
+ return shaka.util.Platform.userAgentContains_('WPE');
303
+ }
304
+
295
305
  /**
296
306
  * Returns a major version number for Safari, or Safari-based iOS browsers.
297
307
  *
@@ -348,6 +358,15 @@ shaka.util.Platform = class {
348
358
  return shaka.util.Platform.userAgentContains_('PC=EOS');
349
359
  }
350
360
 
361
+ /**
362
+ * Check if the current platform is an APL set-top box.
363
+ *
364
+ * @return {boolean}
365
+ */
366
+ static isAPL() {
367
+ return shaka.util.Platform.userAgentContains_('PC=APL');
368
+ }
369
+
351
370
  /**
352
371
  * Guesses if the platform is a mobile one (iOS or Android).
353
372
  *
@@ -32,7 +32,7 @@ shaka.util.TsParser = class {
32
32
  /** @private {?string} */
33
33
  this.videoCodec_ = null;
34
34
 
35
- /** @private {!Array.<Uint8Array>} */
35
+ /** @private {!Array.<!Array.<Uint8Array>>} */
36
36
  this.videoData_ = [];
37
37
 
38
38
  /** @private {!Array.<shaka.extern.MPEG_PES>} */
@@ -44,7 +44,7 @@ shaka.util.TsParser = class {
44
44
  /** @private {?string} */
45
45
  this.audioCodec_ = null;
46
46
 
47
- /** @private {!Array.<Uint8Array>} */
47
+ /** @private {!Array.<!Array.<Uint8Array>>} */
48
48
  this.audioData_ = [];
49
49
 
50
50
  /** @private {!Array.<shaka.extern.MPEG_PES>} */
@@ -53,7 +53,7 @@ shaka.util.TsParser = class {
53
53
  /** @private {?number} */
54
54
  this.id3Pid_ = null;
55
55
 
56
- /** @private {!Array.<Uint8Array>} */
56
+ /** @private {!Array.<!Array.<Uint8Array>>} */
57
57
  this.id3Data_ = [];
58
58
  }
59
59
 
@@ -79,7 +79,6 @@ shaka.util.TsParser = class {
79
79
  */
80
80
  parse(data) {
81
81
  const packetLength = shaka.util.TsParser.PacketLength_;
82
- const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
83
82
 
84
83
  // A TS fragment should contain at least 3 TS packets, a PAT, a PMT, and
85
84
  // one PID.
@@ -162,12 +161,11 @@ shaka.util.TsParser = class {
162
161
  case this.videoPid_: {
163
162
  const videoData = data.subarray(offset, start + packetLength);
164
163
  if (payloadUnitStartIndicator) {
165
- this.videoData_.push(videoData);
164
+ this.videoData_.push([videoData]);
166
165
  } else if (this.videoData_.length) {
167
166
  const prevVideoData = this.videoData_[this.videoData_.length - 1];
168
167
  if (prevVideoData) {
169
- this.videoData_[this.videoData_.length - 1] =
170
- Uint8ArrayUtils.concat(prevVideoData, videoData);
168
+ this.videoData_[this.videoData_.length - 1].push(videoData);
171
169
  }
172
170
  }
173
171
  break;
@@ -175,12 +173,11 @@ shaka.util.TsParser = class {
175
173
  case this.audioPid_: {
176
174
  const audioData = data.subarray(offset, start + packetLength);
177
175
  if (payloadUnitStartIndicator) {
178
- this.audioData_.push(audioData);
176
+ this.audioData_.push([audioData]);
179
177
  } else if (this.audioData_.length) {
180
178
  const prevAudioData = this.audioData_[this.audioData_.length - 1];
181
179
  if (prevAudioData) {
182
- this.audioData_[this.audioData_.length - 1] =
183
- Uint8ArrayUtils.concat(prevAudioData, audioData);
180
+ this.audioData_[this.audioData_.length - 1].push(audioData);
184
181
  }
185
182
  }
186
183
  break;
@@ -188,12 +185,11 @@ shaka.util.TsParser = class {
188
185
  case this.id3Pid_: {
189
186
  const id3Data = data.subarray(offset, start + packetLength);
190
187
  if (payloadUnitStartIndicator) {
191
- this.id3Data_.push(id3Data);
188
+ this.id3Data_.push([id3Data]);
192
189
  } else if (this.id3Data_.length) {
193
190
  const prevId3Data = this.id3Data_[this.id3Data_.length - 1];
194
191
  if (prevId3Data) {
195
- this.id3Data_[this.id3Data_.length - 1] =
196
- Uint8ArrayUtils.concat(prevId3Data, id3Data);
192
+ this.id3Data_[this.id3Data_.length - 1].push(id3Data);
197
193
  }
198
194
  }
199
195
  break;
@@ -588,7 +584,8 @@ shaka.util.TsParser = class {
588
584
  getMetadata() {
589
585
  const timescale = shaka.util.TsParser.Timescale;
590
586
  const metadata = [];
591
- for (const id3Data of this.id3Data_) {
587
+ for (const id3DataArray of this.id3Data_) {
588
+ const id3Data = shaka.util.Uint8ArrayUtils.concat(...id3DataArray);
592
589
  const pes = this.parsePES_(id3Data);
593
590
  if (pes) {
594
591
  metadata.push({
@@ -612,7 +609,8 @@ shaka.util.TsParser = class {
612
609
  getAudioData() {
613
610
  if (this.audioData_.length && !this.audioPes_.length) {
614
611
  let sort = false;
615
- for (const audioData of this.audioData_) {
612
+ for (const audioDataArray of this.audioData_) {
613
+ const audioData = shaka.util.Uint8ArrayUtils.concat(...audioDataArray);
616
614
  const pes = this.parsePES_(audioData);
617
615
  let previousPes = this.audioPes_.length ?
618
616
  this.audioPes_[this.audioPes_.length - 1] : null;
@@ -654,7 +652,8 @@ shaka.util.TsParser = class {
654
652
  getVideoData(naluProcessing = true) {
655
653
  if (this.videoData_.length && !this.videoPes_.length) {
656
654
  let sort = false;
657
- for (const videoData of this.videoData_) {
655
+ for (const videoDataArray of this.videoData_) {
656
+ const videoData = shaka.util.Uint8ArrayUtils.concat(...videoDataArray);
658
657
  const pes = this.parsePES_(videoData);
659
658
  let previousPes = this.videoPes_.length ?
660
659
  this.videoPes_[this.videoPes_.length - 1] : null;
@@ -107,21 +107,26 @@ shaka.util.Uint8ArrayUtils = class {
107
107
  * @export
108
108
  */
109
109
  static concat(...varArgs) {
110
+ const BufferUtils = shaka.util.BufferUtils;
110
111
  let totalLength = 0;
111
- for (const arr of varArgs) {
112
- totalLength += arr.byteLength;
112
+ for (let i = 0; i < varArgs.length; ++i) {
113
+ const value = varArgs[i];
114
+ totalLength += value.byteLength;
113
115
  }
114
116
 
115
117
  const result = new Uint8Array(totalLength);
116
118
  let offset = 0;
117
- for (const arr of varArgs) {
118
- if (arr instanceof Uint8Array) {
119
- result.set(arr, offset);
119
+
120
+ for (let i = 0; i < varArgs.length; ++i) {
121
+ const value = varArgs[i];
122
+ if (value instanceof Uint8Array) {
123
+ result.set(value, offset);
120
124
  } else {
121
- result.set(shaka.util.BufferUtils.toUint8(arr), offset);
125
+ result.set(BufferUtils.toUint8(value), offset);
122
126
  }
123
- offset += arr.byteLength;
127
+ offset += value.byteLength;
124
128
  }
129
+
125
130
  return result;
126
131
  }
127
132
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.6.5",
4
+ "version": "4.6.7",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -605,4 +605,13 @@ describe('UITextDisplayer', () => {
605
605
  (e) => e.nodeType == Node.ELEMENT_NODE);
606
606
  expect(childrenOfTwo.length).toBe(3);
607
607
  });
608
+
609
+ it('textDisplayer does not crash if destroy is called more than once', () => {
610
+ expect(videoContainer.childNodes.length).toBe(1);
611
+
612
+ textDisplayer.destroy();
613
+ textDisplayer.destroy();
614
+
615
+ expect(videoContainer.childNodes.length).toBe(0);
616
+ });
608
617
  });
@@ -603,6 +603,55 @@ describe('PeriodCombiner', () => {
603
603
  }
604
604
  });
605
605
 
606
+ // Regression test for #6054, where we failed on multi-period content with
607
+ // different numbers of forced-subtitle streams per period.
608
+ it('Does not combine subtitle and forced-subtitle tracks', async () => {
609
+ const videoStreams = [makeVideoStream(1280)];
610
+ const audioStreams = [makeAudioStream('en', /* channels= */ 2)];
611
+ const imageStreams = [];
612
+
613
+ const forcedSubtitle = makeTextStream('de');
614
+ forcedSubtitle.roles = ['forced-subtitle'];
615
+ forcedSubtitle.forced = true;
616
+
617
+ const subtitle = makeTextStream('de');
618
+ subtitle.roles = ['subtitle'];
619
+
620
+ /** @type {!Array.<shaka.extern.Period>} */
621
+ const periods = [
622
+ {
623
+ id: '1',
624
+ videoStreams,
625
+ audioStreams,
626
+ textStreams: [
627
+ forcedSubtitle,
628
+ subtitle,
629
+ ],
630
+ imageStreams,
631
+ },
632
+ {
633
+ id: '2',
634
+ videoStreams,
635
+ audioStreams,
636
+ textStreams: [
637
+ subtitle,
638
+ ],
639
+ imageStreams,
640
+ },
641
+ {
642
+ id: '3',
643
+ videoStreams,
644
+ audioStreams,
645
+ textStreams: [],
646
+ imageStreams,
647
+ },
648
+ ];
649
+
650
+ await combiner.combinePeriods(periods, /* isDynamic= */ true);
651
+ const textStreams = combiner.getTextStreams();
652
+ expect(textStreams.every((s) => s.roles.length === 1)).toBe(true);
653
+ });
654
+
606
655
  // Regression test for #3383, where we failed on multi-period content with
607
656
  // multiple image streams per period.
608
657
  it('Can handle multiple image streams', async () => {