shaka-player 4.7.11 → 4.7.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +2 -4
  2. package/dist/deps.js +6 -7
  3. package/dist/locales.js +34 -34
  4. package/dist/shaka-player.compiled.d.ts +14 -12
  5. package/dist/shaka-player.compiled.debug.d.ts +14 -12
  6. package/dist/shaka-player.compiled.debug.externs.js +78 -64
  7. package/dist/shaka-player.compiled.debug.js +321 -312
  8. package/dist/shaka-player.compiled.debug.map +4 -4
  9. package/dist/shaka-player.compiled.externs.js +78 -64
  10. package/dist/shaka-player.compiled.js +1167 -1153
  11. package/dist/shaka-player.compiled.map +4 -4
  12. package/dist/shaka-player.ui.d.ts +14 -12
  13. package/dist/shaka-player.ui.debug.d.ts +14 -12
  14. package/dist/shaka-player.ui.debug.externs.js +78 -64
  15. package/dist/shaka-player.ui.debug.js +443 -434
  16. package/dist/shaka-player.ui.debug.map +4 -4
  17. package/dist/shaka-player.ui.externs.js +78 -64
  18. package/dist/shaka-player.ui.js +1319 -1305
  19. package/dist/shaka-player.ui.map +4 -4
  20. package/externs/shaka/manifest.js +11 -1
  21. package/externs/shaka/offline.js +7 -1
  22. package/externs/shaka/player.js +32 -19
  23. package/lib/abr/simple_abr_manager.js +21 -26
  24. package/lib/cast/cast_proxy.js +1 -3
  25. package/lib/cast/cast_sender.js +8 -11
  26. package/lib/cea/cea608_memory.js +1 -1
  27. package/lib/cea/ts_cea_parser.js +10 -1
  28. package/lib/dash/dash_parser.js +13 -3
  29. package/lib/dash/segment_base.js +8 -1
  30. package/lib/dash/segment_list.js +24 -18
  31. package/lib/dash/segment_template.js +21 -2
  32. package/lib/hls/hls_parser.js +36 -17
  33. package/lib/media/drm_engine.js +6 -3
  34. package/lib/media/media_source_engine.js +96 -33
  35. package/lib/media/playhead.js +3 -3
  36. package/lib/media/presentation_timeline.js +23 -0
  37. package/lib/media/segment_index.js +21 -20
  38. package/lib/media/segment_utils.js +10 -1
  39. package/lib/media/streaming_engine.js +2 -14
  40. package/lib/mss/mss_parser.js +4 -0
  41. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  42. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  43. package/lib/offline/manifest_converter.js +10 -3
  44. package/lib/offline/storage.js +2 -0
  45. package/lib/player.js +30 -4
  46. package/lib/polyfill/media_capabilities.js +20 -1
  47. package/lib/polyfill/mediasource.js +37 -14
  48. package/lib/text/vtt_text_parser.js +51 -51
  49. package/lib/transmuxer/h264.js +3 -3
  50. package/lib/transmuxer/h265.js +3 -3
  51. package/lib/transmuxer/ts_transmuxer.js +42 -3
  52. package/lib/util/manifest_parser_utils.js +1 -0
  53. package/lib/util/mime_utils.js +10 -5
  54. package/lib/util/mp4_parser.js +57 -0
  55. package/lib/util/periods.js +5 -37
  56. package/lib/util/platform.js +4 -0
  57. package/lib/util/stream_utils.js +334 -271
  58. package/lib/util/ts_parser.js +99 -10
  59. package/package.json +1 -1
  60. package/lib/polyfill/storage_estimate.js +0 -57
@@ -14,6 +14,7 @@ goog.require('shaka.media.SegmentIndex');
14
14
  goog.require('shaka.media.SegmentReference');
15
15
  goog.require('shaka.offline.OfflineUri');
16
16
  goog.require('shaka.util.ManifestParserUtils');
17
+ goog.require('shaka.util.MimeUtils');
17
18
 
18
19
 
19
20
  /**
@@ -169,7 +170,7 @@ shaka.offline.ManifestConverter = class {
169
170
  fromStreamDB_(streamDB, timeline) {
170
171
  /** @type {!Array.<!shaka.media.SegmentReference>} */
171
172
  const segments = streamDB.segments.map(
172
- (segment, index) => this.fromSegmentDB_(index, segment));
173
+ (segment, index) => this.fromSegmentDB_(index, segment, streamDB));
173
174
 
174
175
  timeline.notifySegments(segments);
175
176
 
@@ -212,6 +213,8 @@ shaka.offline.ManifestConverter = class {
212
213
  accessibilityPurpose: null,
213
214
  external: streamDB.external,
214
215
  fastSwitching: streamDB.fastSwitching,
216
+ fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
217
+ streamDB.mimeType, streamDB.codecs)]),
215
218
  };
216
219
 
217
220
  return stream;
@@ -220,10 +223,11 @@ shaka.offline.ManifestConverter = class {
220
223
  /**
221
224
  * @param {number} index
222
225
  * @param {shaka.extern.SegmentDB} segmentDB
226
+ * @param {shaka.extern.StreamDB} streamDB
223
227
  * @return {!shaka.media.SegmentReference}
224
228
  * @private
225
229
  */
226
- fromSegmentDB_(index, segmentDB) {
230
+ fromSegmentDB_(index, segmentDB, streamDB) {
227
231
  /** @type {!shaka.offline.OfflineUri} */
228
232
  const uri = shaka.offline.OfflineUri.segment(
229
233
  this.mechanism_, this.cell_, segmentDB.dataKey);
@@ -231,7 +235,7 @@ shaka.offline.ManifestConverter = class {
231
235
  const initSegmentReference = segmentDB.initSegmentKey != null ?
232
236
  this.fromInitSegmentDB_(segmentDB.initSegmentKey) : null;
233
237
 
234
- return new shaka.media.SegmentReference(
238
+ const ref = new shaka.media.SegmentReference(
235
239
  segmentDB.startTime,
236
240
  segmentDB.endTime,
237
241
  () => [uri.toString()],
@@ -243,6 +247,9 @@ shaka.offline.ManifestConverter = class {
243
247
  segmentDB.appendWindowEnd,
244
248
  /* partialReferences= */ [],
245
249
  segmentDB.tilesLayout || '');
250
+ ref.mimeType = segmentDB.mimeType || streamDB.mimeType || '';
251
+ ref.codecs = segmentDB.codecs || streamDB.codecs || '';
252
+ return ref;
246
253
  }
247
254
 
248
255
  /**
@@ -1389,6 +1389,8 @@ shaka.offline.Storage = class {
1389
1389
  tilesLayout: segment.tilesLayout,
1390
1390
  pendingSegmentRefId,
1391
1391
  dataKey: 0,
1392
+ mimeType: segment.mimeType,
1393
+ codecs: segment.codecs,
1392
1394
  };
1393
1395
  streamDb.segments.push(segmentDB);
1394
1396
  groupId = (groupId + 1) % numberOfParallelDownloads;
package/lib/player.js CHANGED
@@ -2174,12 +2174,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2174
2174
  accessibilityPurpose: null,
2175
2175
  external: false,
2176
2176
  fastSwitching: false,
2177
+ fullMimeTypes: new Set(),
2177
2178
  },
2178
2179
  bandwidth: 100,
2179
2180
  allowedByApplication: true,
2180
2181
  allowedByKeySystem: true,
2181
2182
  decodingInfos: [],
2182
2183
  };
2184
+ variant.video.fullMimeTypes.add(shaka.util.MimeUtils.getFullType(
2185
+ variant.video.mimeType, variant.video.codecs));
2183
2186
 
2184
2187
  this.drmEngine_.setSrcEquals(/* srcEquals= */ true);
2185
2188
  await this.drmEngine_.initForPlayback(
@@ -3620,12 +3623,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3620
3623
  const currentTime = this.video_.currentTime;
3621
3624
  const seekRange = this.seekRange();
3622
3625
  const safeSeekOffset = this.config_.streaming.safeSeekOffset;
3626
+
3627
+ // Cancel trick play if we hit the beginning or end of the seekable
3628
+ // (Sub-second accuracy not required here)
3623
3629
  if (rate > 0) {
3624
- if (currentTime >= seekRange.end) {
3630
+ if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
3625
3631
  this.cancelTrickPlay();
3626
3632
  }
3627
3633
  } else {
3628
- if (currentTime <= (seekRange.start + safeSeekOffset)) {
3634
+ if (Math.floor(currentTime) <=
3635
+ Math.floor(seekRange.start + safeSeekOffset)) {
3629
3636
  this.cancelTrickPlay();
3630
3637
  }
3631
3638
  }
@@ -4494,7 +4501,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4494
4501
 
4495
4502
  if (this.manifest_) {
4496
4503
  const timeline = this.manifest_.presentationTimeline;
4497
- const startTime = timeline.getPresentationStartTime();
4504
+ const startTime = timeline.getInitialProgramDateTime() ||
4505
+ timeline.getPresentationStartTime();
4498
4506
  return new Date(/* ms= */ (startTime + presentationTime) * 1000);
4499
4507
  } else if (this.video_ && this.video_.getStartDate) {
4500
4508
  // Apple's native HLS gives us getStartDate(), which is only available if
@@ -4803,6 +4811,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4803
4811
  accessibilityPurpose: null,
4804
4812
  external: true,
4805
4813
  fastSwitching: false,
4814
+ fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
4815
+ mimeType || '', codec || '')]),
4806
4816
  };
4807
4817
 
4808
4818
  const fullMimeType = shaka.util.MimeUtils.getFullType(
@@ -4959,6 +4969,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4959
4969
  accessibilityPurpose: null,
4960
4970
  external: true,
4961
4971
  fastSwitching: false,
4972
+ fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
4973
+ mimeType || '', '')]),
4962
4974
  };
4963
4975
 
4964
4976
  if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
@@ -5389,6 +5401,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5389
5401
  accessibilityPurpose: null,
5390
5402
  external: false,
5391
5403
  fastSwitching: false,
5404
+ fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
5405
+ mimeType, '')]),
5392
5406
  };
5393
5407
  manifest.textStreams.push(textStream);
5394
5408
  closedCaptionsSet.add(id);
@@ -6099,6 +6113,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6099
6113
  // change.
6100
6114
  if (this.playhead_) {
6101
6115
  this.playhead_.notifyOfBufferingChange();
6116
+ // Skip the initial buffer gap
6117
+ const startTime = this.mediaSourceEngine_.bufferStart(contentType);
6118
+ if (
6119
+ !this.isLive() &&
6120
+ // If not paused then GapJumpingController will handle this gap.
6121
+ this.video_.paused &&
6122
+ startTime != null &&
6123
+ startTime > 0 &&
6124
+ this.playhead_.getTime() < startTime
6125
+ ) {
6126
+ this.playhead_.setStartTime(startTime);
6127
+ }
6102
6128
  }
6103
6129
  this.pollBufferState_();
6104
6130
 
@@ -6934,7 +6960,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6934
6960
  * @export
6935
6961
  */
6936
6962
  // eslint-disable-next-line no-useless-concat
6937
- shaka.Player.version = 'v4.7.11' + '-uncompiled'; // x-release-please-version
6963
+ shaka.Player.version = 'v4.7.13' + '-uncompiled'; // x-release-please-version
6938
6964
 
6939
6965
  // Initialize the deprecation system using the version string we just set
6940
6966
  // on the player.
@@ -300,7 +300,17 @@ shaka.polyfill.MediaCapabilities = class {
300
300
  // this query is specifically for HDR.
301
301
  displayType += '; eotf=smpte2084';
302
302
  }
303
- return cast.__platform__.canDisplayType(displayType);
303
+ let result = false;
304
+ if (displayType in shaka.polyfill.MediaCapabilities
305
+ .memoizedCanDisplayTypeRequests_) {
306
+ result = shaka.polyfill.MediaCapabilities
307
+ .memoizedCanDisplayTypeRequests_[displayType];
308
+ } else {
309
+ result = cast.__platform__.canDisplayType(displayType);
310
+ shaka.polyfill.MediaCapabilities
311
+ .memoizedCanDisplayTypeRequests_[displayType] = result;
312
+ }
313
+ return result;
304
314
  }
305
315
 
306
316
  /**
@@ -337,6 +347,15 @@ shaka.polyfill.MediaCapabilities.originalMcap = null;
337
347
  */
338
348
  shaka.polyfill.MediaCapabilities.memoizedMediaKeySystemAccessRequests_ = {};
339
349
 
350
+ /**
351
+ * A cache that stores the canDisplayType result of calling
352
+ * `cast.__platform__.canDisplayType`.
353
+ *
354
+ * @type {(Object<(!string), (!boolean)>)}
355
+ * @export
356
+ */
357
+ shaka.polyfill.MediaCapabilities.memoizedCanDisplayTypeRequests_ = {};
358
+
340
359
  // Install at a lower priority than MediaSource polyfill, so that we have
341
360
  // MediaSource available first.
342
361
  shaka.polyfill.register(shaka.polyfill.MediaCapabilities.install, -1);
@@ -30,14 +30,8 @@ shaka.polyfill.MediaSource = class {
30
30
  // platform detection tricks to decide which patches to install.
31
31
  const safariVersion = shaka.util.Platform.safariVersion();
32
32
 
33
- if (!window.MediaSource) {
33
+ if (!window.MediaSource && !window.ManagedMediaSource) {
34
34
  shaka.log.info('No MSE implementation available.');
35
- } else if (shaka.util.Platform.isChromecast()) {
36
- shaka.log.info('Patching Chromecast MSE bugs.');
37
- // Chromecast fails on some TS content, even though it is supposed to
38
- // support it. Better to transmux.
39
- // See https://github.com/shaka-project/shaka-player/issues/5278
40
- shaka.polyfill.MediaSource.rejectContainer_('mp2t');
41
35
  } else if (safariVersion) {
42
36
  // NOTE: shaka.Player.isBrowserSupported() has its own restrictions on
43
37
  // Safari version.
@@ -73,11 +67,6 @@ shaka.polyfill.MediaSource = class {
73
67
  // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
74
68
  shaka.polyfill.MediaSource.stubAbort_();
75
69
  }
76
- } else if (shaka.util.Platform.isEdge()) {
77
- shaka.log.info('Rejecting TS container.');
78
- // TS content is broken on Edge in general.
79
- // See https://github.com/shaka-project/shaka-player/issues/4955
80
- shaka.polyfill.MediaSource.rejectContainer_('mp2t');
81
70
  } else if (shaka.util.Platform.isTizen2() ||
82
71
  shaka.util.Platform.isTizen3() ||
83
72
  shaka.util.Platform.isTizen4()) {
@@ -90,6 +79,14 @@ shaka.polyfill.MediaSource = class {
90
79
  shaka.log.info('Using native MSE as-is.');
91
80
  }
92
81
 
82
+ if (window.MediaSource || window.ManagedMediaSource) {
83
+ // TS content is broken on all browsers in general.
84
+ // See https://github.com/shaka-project/shaka-player/issues/4955
85
+ // See https://github.com/shaka-project/shaka-player/issues/5278
86
+ // See https://github.com/shaka-project/shaka-player/issues/6334
87
+ shaka.polyfill.MediaSource.rejectContainer_('mp2t');
88
+ }
89
+
93
90
  if (window.MediaSource &&
94
91
  MediaSource.isTypeSupported('video/webm; codecs="vp9"') &&
95
92
  !MediaSource.isTypeSupported('video/webm; codecs="vp09.00.10.08"')) {
@@ -158,12 +155,25 @@ shaka.polyfill.MediaSource = class {
158
155
  * @private
159
156
  */
160
157
  static rejectContainer_(container) {
161
- const isTypeSupported = MediaSource.isTypeSupported;
158
+ const isTypeSupported =
159
+ // eslint-disable-next-line no-restricted-syntax
160
+ MediaSource.isTypeSupported.bind(MediaSource);
162
161
 
163
162
  MediaSource.isTypeSupported = (mimeType) => {
164
163
  const actualContainer = shaka.util.MimeUtils.getContainerType(mimeType);
165
164
  return actualContainer != container && isTypeSupported(mimeType);
166
165
  };
166
+
167
+ if (window.ManagedMediaSource) {
168
+ const isTypeSupportedManaged =
169
+ // eslint-disable-next-line no-restricted-syntax
170
+ ManagedMediaSource.isTypeSupported.bind(ManagedMediaSource);
171
+
172
+ window.ManagedMediaSource.isTypeSupported = (mimeType) => {
173
+ const actualContainer = shaka.util.MimeUtils.getContainerType(mimeType);
174
+ return actualContainer != container && isTypeSupportedManaged(mimeType);
175
+ };
176
+ }
167
177
  }
168
178
 
169
179
  /**
@@ -175,12 +185,25 @@ shaka.polyfill.MediaSource = class {
175
185
  * @private
176
186
  */
177
187
  static rejectCodec_(codec) {
178
- const isTypeSupported = MediaSource.isTypeSupported;
188
+ const isTypeSupported =
189
+ // eslint-disable-next-line no-restricted-syntax
190
+ MediaSource.isTypeSupported.bind(MediaSource);
179
191
 
180
192
  MediaSource.isTypeSupported = (mimeType) => {
181
193
  const actualCodec = shaka.util.MimeUtils.getCodecBase(mimeType);
182
194
  return actualCodec != codec && isTypeSupported(mimeType);
183
195
  };
196
+
197
+ if (window.ManagedMediaSource) {
198
+ const isTypeSupportedManaged =
199
+ // eslint-disable-next-line no-restricted-syntax
200
+ ManagedMediaSource.isTypeSupported.bind(ManagedMediaSource);
201
+
202
+ window.ManagedMediaSource.isTypeSupported = (mimeType) => {
203
+ const actualCodec = shaka.util.MimeUtils.getCodecBase(mimeType);
204
+ return actualCodec != codec && isTypeSupportedManaged(mimeType);
205
+ };
206
+ }
184
207
  }
185
208
 
186
209
  /**
@@ -84,51 +84,16 @@ shaka.text.VttTextParser = class {
84
84
  // HLS content, which should use X-TIMESTAMP-MAP and periodStart instead.
85
85
  let offset = time.vttOffset;
86
86
 
87
- // Only use 'X-TIMESTAMP-MAP' in sequence mode.
88
- // Note that an offset based on the first video
89
- // timestamp has already been extracted, and appears in periodStart.
90
- if (blocks[0].includes('X-TIMESTAMP-MAP') && this.sequenceMode_) {
91
- // https://bit.ly/2K92l7y
92
- // The 'X-TIMESTAMP-MAP' header is used in HLS to align text with
93
- // the rest of the media.
94
- // The header format is 'X-TIMESTAMP-MAP=MPEGTS:n,LOCAL:m'
95
- // (the attributes can go in any order)
96
- // where n is MPEG-2 time and m is cue time it maps to.
97
- // For example 'X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:900000'
98
- // means an offset of 10 seconds
99
- // 900000/MPEG_TIMESCALE - cue time.
100
- const cueTimeMatch =
101
- blocks[0].match(/LOCAL:((?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{3}))/m);
102
-
103
- const mpegTimeMatch = blocks[0].match(/MPEGTS:(\d+)/m);
104
- if (cueTimeMatch && mpegTimeMatch) {
105
- const parser = new shaka.util.TextParser(cueTimeMatch[1]);
106
- const cueTime = shaka.text.VttTextParser.parseTime_(parser);
107
- if (cueTime == null) {
108
- throw new shaka.util.Error(
109
- shaka.util.Error.Severity.CRITICAL,
110
- shaka.util.Error.Category.TEXT,
111
- shaka.util.Error.Code.INVALID_TEXT_HEADER);
112
- }
113
-
114
- let mpegTime = Number(mpegTimeMatch[1]);
115
- const mpegTimescale = shaka.text.VttTextParser.MPEG_TIMESCALE_;
116
-
117
- const rolloverSeconds =
118
- shaka.text.VttTextParser.TS_ROLLOVER_ / mpegTimescale;
119
- let segmentStart = time.segmentStart - time.periodStart;
120
- while (segmentStart >= rolloverSeconds) {
121
- segmentStart -= rolloverSeconds;
122
- mpegTime += shaka.text.VttTextParser.TS_ROLLOVER_;
123
- }
124
-
125
- offset = time.periodStart + mpegTime / mpegTimescale - cueTime;
126
- }
127
- } else if (blocks[0].includes('X-TIMESTAMP-MAP') &&
87
+ // Only use 'X-TIMESTAMP-MAP' with HLS. This overrides offset above.
88
+ if (blocks[0].includes('X-TIMESTAMP-MAP') &&
128
89
  this.manifestType_ == shaka.media.ManifestParser.HLS) {
129
- // In we use HLS segments mode, with the presence of this tag we need
130
- // calculate the offset from the segment startTime.
131
- offset = time.segmentStart;
90
+ if (this.sequenceMode_) {
91
+ // Compute a different, rollover-based offset for sequence mode.
92
+ offset = this.computeHlsSequenceModeOffset_(blocks[0], time);
93
+ } else {
94
+ // Calculate the offset from the segment startTime.
95
+ offset = time.segmentStart;
96
+ }
132
97
  }
133
98
 
134
99
  // Parse VTT regions.
@@ -159,6 +124,48 @@ shaka.text.VttTextParser = class {
159
124
  return ret;
160
125
  }
161
126
 
127
+ /**
128
+ * @param {string} headerBlock Contains X-TIMESTAMP-MAP.
129
+ * @param {shaka.extern.TextParser.TimeContext} time
130
+ * @return {number}
131
+ * @private
132
+ */
133
+ computeHlsSequenceModeOffset_(headerBlock, time) {
134
+ // https://bit.ly/2K92l7y
135
+ // The 'X-TIMESTAMP-MAP' header is used in HLS to align text with
136
+ // the rest of the media.
137
+ // The header format is 'X-TIMESTAMP-MAP=MPEGTS:n,LOCAL:m'
138
+ // (the attributes can go in any order)
139
+ // where n is MPEG-2 time and m is cue time it maps to.
140
+ // For example 'X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:900000'
141
+ // means an offset of 10 seconds
142
+ // 900000/MPEG_TIMESCALE - cue time.
143
+ const cueTimeMatch = headerBlock.match(
144
+ /LOCAL:((?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{3}))/m);
145
+ const mpegTimeMatch = headerBlock.match(/MPEGTS:(\d+)/m);
146
+
147
+ if (!cueTimeMatch || !mpegTimeMatch) {
148
+ throw new shaka.util.Error(
149
+ shaka.util.Error.Severity.CRITICAL,
150
+ shaka.util.Error.Category.TEXT,
151
+ shaka.util.Error.Code.INVALID_TEXT_HEADER);
152
+ }
153
+
154
+ const parser = new shaka.util.TextParser(cueTimeMatch[1]);
155
+ const cueTime = shaka.text.VttTextParser.parseTime_(parser);
156
+ if (cueTime == null) {
157
+ throw new shaka.util.Error(
158
+ shaka.util.Error.Severity.CRITICAL,
159
+ shaka.util.Error.Category.TEXT,
160
+ shaka.util.Error.Code.INVALID_TEXT_HEADER);
161
+ }
162
+
163
+ const mpegTime = Number(mpegTimeMatch[1]);
164
+ const mpegTimescale = shaka.text.VttTextParser.MPEG_TIMESCALE_;
165
+
166
+ return time.periodStart + mpegTime / mpegTimescale - cueTime;
167
+ }
168
+
162
169
  /**
163
170
  * Add default color
164
171
  *
@@ -1061,13 +1068,6 @@ shaka.text.VttTextParser = class {
1061
1068
  */
1062
1069
  shaka.text.VttTextParser.MPEG_TIMESCALE_ = 90000;
1063
1070
 
1064
- /**
1065
- * At this value, timestamps roll over in TS content.
1066
- * @const {number}
1067
- * @private
1068
- */
1069
- shaka.text.VttTextParser.TS_ROLLOVER_ = 0x200000000;
1070
-
1071
1071
  /**
1072
1072
  * @const
1073
1073
  * @private {!RegExp}
@@ -144,7 +144,7 @@ shaka.transmuxer.H264 = class {
144
144
  const vSpacingTable = [
145
145
  1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1,
146
146
  ];
147
- if (aspectRatioIdc > 0 && aspectRatioIdc < 16) {
147
+ if (aspectRatioIdc > 0 && aspectRatioIdc <= 16) {
148
148
  hSpacing = hSpacingTable[aspectRatioIdc - 1];
149
149
  vSpacing = vSpacingTable[aspectRatioIdc - 1];
150
150
  } else if (aspectRatioIdc === 255) {
@@ -267,8 +267,8 @@ shaka.transmuxer.H264 = class {
267
267
  naluLength[1] = (size >> 16) & 0xff;
268
268
  naluLength[2] = (size >> 8) & 0xff;
269
269
  naluLength[3] = size & 0xff;
270
- nalusData.push(shaka.util.Uint8ArrayUtils.concat(
271
- naluLength, nalu.fullData));
270
+ nalusData.push(naluLength);
271
+ nalusData.push(nalu.fullData);
272
272
  }
273
273
  }
274
274
  if (!nalusData.length) {
@@ -317,7 +317,7 @@ shaka.transmuxer.H265 = class {
317
317
  const sarHeightTable = [
318
318
  1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1,
319
319
  ];
320
- if (aspectRatioIdc > 0 && aspectRatioIdc < 16) {
320
+ if (aspectRatioIdc > 0 && aspectRatioIdc <= 16) {
321
321
  sarWidth = sarWidthTable[aspectRatioIdc - 1];
322
322
  sarHeight = sarHeightTable[aspectRatioIdc - 1];
323
323
  } else if (aspectRatioIdc === 255) {
@@ -652,8 +652,8 @@ shaka.transmuxer.H265 = class {
652
652
  naluLength[1] = (size >> 16) & 0xff;
653
653
  naluLength[2] = (size >> 8) & 0xff;
654
654
  naluLength[3] = size & 0xff;
655
- nalusData.push(shaka.util.Uint8ArrayUtils.concat(
656
- naluLength, nalu.fullData));
655
+ nalusData.push(naluLength);
656
+ nalusData.push(nalu.fullData);
657
657
  }
658
658
  }
659
659
  if (!nalusData.length) {
@@ -7,6 +7,7 @@
7
7
  goog.provide('shaka.transmuxer.TsTransmuxer');
8
8
 
9
9
  goog.require('shaka.media.Capabilities');
10
+ goog.require('shaka.transmuxer.AacTransmuxer');
10
11
  goog.require('shaka.transmuxer.Ac3');
11
12
  goog.require('shaka.transmuxer.ADTS');
12
13
  goog.require('shaka.transmuxer.Ec3');
@@ -16,6 +17,7 @@ goog.require('shaka.transmuxer.MpegAudio');
16
17
  goog.require('shaka.transmuxer.TransmuxerEngine');
17
18
  goog.require('shaka.util.BufferUtils');
18
19
  goog.require('shaka.util.Error');
20
+ goog.require('shaka.util.Id3Utils');
19
21
  goog.require('shaka.util.ManifestParserUtils');
20
22
  goog.require('shaka.util.MimeUtils');
21
23
  goog.require('shaka.util.Mp4Generator');
@@ -45,6 +47,9 @@ shaka.transmuxer.TsTransmuxer = class {
45
47
 
46
48
  /** @private {?shaka.util.TsParser} */
47
49
  this.tsParser_ = null;
50
+
51
+ /** @private {?shaka.transmuxer.AacTransmuxer} */
52
+ this.aacTransmuxer_ = null;
48
53
  }
49
54
 
50
55
 
@@ -54,6 +59,10 @@ shaka.transmuxer.TsTransmuxer = class {
54
59
  */
55
60
  destroy() {
56
61
  this.initSegments.clear();
62
+
63
+ if (this.aacTransmuxer_) {
64
+ this.aacTransmuxer_.destroy();
65
+ }
57
66
  }
58
67
 
59
68
 
@@ -187,14 +196,44 @@ shaka.transmuxer.TsTransmuxer = class {
187
196
  */
188
197
  transmux(data, stream, reference, duration, contentType) {
189
198
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
199
+ const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
200
+
201
+ const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
202
+
203
+ if (contentType == ContentType.AUDIO &&
204
+ !shaka.util.TsParser.probe(uint8ArrayData)) {
205
+ const id3Data = shaka.util.Id3Utils.getID3Data(uint8ArrayData);
206
+ let offset = id3Data.length;
207
+ for (; offset < uint8ArrayData.length; offset++) {
208
+ if (shaka.transmuxer.MpegAudio.probe(uint8ArrayData, offset)) {
209
+ return Promise.reject(new shaka.util.Error(
210
+ shaka.util.Error.Severity.CRITICAL,
211
+ shaka.util.Error.Category.MEDIA,
212
+ shaka.util.Error.Code.TRANSMUXING_FAILED));
213
+ }
214
+ }
215
+ offset = id3Data.length;
216
+ for (; offset < uint8ArrayData.length; offset++) {
217
+ if (shaka.transmuxer.ADTS.probe(uint8ArrayData, offset)) {
218
+ if (!this.aacTransmuxer_) {
219
+ this.aacTransmuxer_ =
220
+ new shaka.transmuxer.AacTransmuxer('audio/aac');
221
+ }
222
+ return this.aacTransmuxer_
223
+ .transmux(data, stream, reference, duration, contentType);
224
+ }
225
+ }
226
+ return Promise.reject(new shaka.util.Error(
227
+ shaka.util.Error.Severity.CRITICAL,
228
+ shaka.util.Error.Category.MEDIA,
229
+ shaka.util.Error.Code.TRANSMUXING_FAILED));
230
+ }
231
+
190
232
  if (!this.tsParser_) {
191
233
  this.tsParser_ = new shaka.util.TsParser();
192
234
  } else {
193
235
  this.tsParser_.clearData();
194
236
  }
195
- const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
196
-
197
- const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
198
237
 
199
238
  const tsParser = this.tsParser_.parse(uint8ArrayData);
200
239
  const streamInfos = [];
@@ -203,6 +203,7 @@ shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
203
203
  /^[ae]c-3$/,
204
204
  /^ac-4$/,
205
205
  /^dts[cex]$/, // DTS Digital Surround (dtsc), DTS Express (dtse), DTS:X (dtsx)
206
+ /^iamf/,
206
207
  ];
207
208
 
208
209
 
@@ -84,17 +84,22 @@ shaka.util.MimeUtils = class {
84
84
  * beyond the container and codec type, when available.
85
85
  *
86
86
  * @param {shaka.extern.Stream} stream
87
+ * @param {string} mimeType
88
+ * @param {string} codecs
87
89
  * @return {string}
88
90
  */
89
- static getExtendedType(stream) {
90
- const components = [stream.mimeType];
91
+ static getExtendedType(stream, mimeType, codecs) {
92
+ const components = [mimeType];
91
93
 
92
94
  const extendedMimeParams = shaka.util.MimeUtils.EXTENDED_MIME_PARAMETERS_;
93
95
  extendedMimeParams.forEach((mimeKey, streamKey) => {
94
96
  const value = stream[streamKey];
95
- if (streamKey == 'codecs' &&
96
- shaka.util.MimeUtils.RAW_FORMATS.includes(stream.mimeType)) {
97
- // Skip codecs for raw formats
97
+ if (streamKey == 'codecs') {
98
+ if (shaka.util.MimeUtils.RAW_FORMATS.includes(stream.mimeType)) {
99
+ // Skip codecs for raw formats
100
+ } else {
101
+ components.push('codecs="' + codecs + '"');
102
+ }
98
103
  } else if (value) {
99
104
  components.push(mimeKey + '="' + value + '"');
100
105
  }
@@ -271,6 +271,63 @@ shaka.util.Mp4Parser = class {
271
271
  }
272
272
 
273
273
 
274
+ /**
275
+ * A callback that tells the Mp4 parser to treat the body of a box as a audio
276
+ * sample entry. A audio sample entry has some fixed-sized fields
277
+ * describing the audio codec parameters, followed by an arbitrary number of
278
+ * appended children. Each child is a box.
279
+ *
280
+ * @param {!shaka.extern.ParsedBox} box
281
+ * @export
282
+ */
283
+ static audioSampleEntry(box) {
284
+ // The "reader" starts at the payload, so we need to add the header to the
285
+ // start position. The header size varies.
286
+ const headerSize = shaka.util.Mp4Parser.headerSize(box);
287
+
288
+ // 6 bytes reserved
289
+ // 2 bytes data reference index
290
+ box.reader.skip(8);
291
+ // 2 bytes version
292
+ const version = box.reader.readUint16();
293
+ // 2 bytes revision (0, could be ignored)
294
+ // 4 bytes reserved
295
+ box.reader.skip(6);
296
+
297
+ if (version == 2) {
298
+ // 16 bytes hard-coded values with no comments
299
+ // 8 bytes sample rate
300
+ // 4 bytes channel count
301
+ // 4 bytes hard-coded values with no comments
302
+ // 4 bytes bits per sample
303
+ // 4 bytes lpcm flags
304
+ // 4 bytes sample size
305
+ // 4 bytes samples per packet
306
+ box.reader.skip(48);
307
+ } else {
308
+ // 2 bytes channel count
309
+ // 2 bytes bits per sample
310
+ // 2 bytes compression ID
311
+ // 2 bytes packet size
312
+ // 2 bytes sample rate
313
+ // 2 byte reserved
314
+ box.reader.skip(12);
315
+ }
316
+
317
+ if (version == 1) {
318
+ // 4 bytes samples per packet
319
+ // 4 bytes bytes per packet
320
+ // 4 bytes bytes per frame
321
+ // 4 bytes bytes per sample
322
+ box.reader.skip(16);
323
+ }
324
+
325
+ while (box.reader.hasMoreData() && !box.parser.done_) {
326
+ box.parser.parseNext(box.start + headerSize, box.reader, box.partialOkay);
327
+ }
328
+ }
329
+
330
+
274
331
  /**
275
332
  * Create a callback that tells the Mp4 parser to treat the body of a box as a
276
333
  * binary blob and to parse the body's contents using the provided callback.