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.
- package/README.md +2 -4
- package/dist/deps.js +6 -7
- package/dist/locales.js +34 -34
- package/dist/shaka-player.compiled.d.ts +14 -12
- package/dist/shaka-player.compiled.debug.d.ts +14 -12
- package/dist/shaka-player.compiled.debug.externs.js +78 -64
- package/dist/shaka-player.compiled.debug.js +321 -312
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +78 -64
- package/dist/shaka-player.compiled.js +1167 -1153
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +14 -12
- package/dist/shaka-player.ui.debug.d.ts +14 -12
- package/dist/shaka-player.ui.debug.externs.js +78 -64
- package/dist/shaka-player.ui.debug.js +443 -434
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +78 -64
- package/dist/shaka-player.ui.js +1319 -1305
- package/dist/shaka-player.ui.map +4 -4
- package/externs/shaka/manifest.js +11 -1
- package/externs/shaka/offline.js +7 -1
- package/externs/shaka/player.js +32 -19
- package/lib/abr/simple_abr_manager.js +21 -26
- package/lib/cast/cast_proxy.js +1 -3
- package/lib/cast/cast_sender.js +8 -11
- package/lib/cea/cea608_memory.js +1 -1
- package/lib/cea/ts_cea_parser.js +10 -1
- package/lib/dash/dash_parser.js +13 -3
- package/lib/dash/segment_base.js +8 -1
- package/lib/dash/segment_list.js +24 -18
- package/lib/dash/segment_template.js +21 -2
- package/lib/hls/hls_parser.js +36 -17
- package/lib/media/drm_engine.js +6 -3
- package/lib/media/media_source_engine.js +96 -33
- package/lib/media/playhead.js +3 -3
- package/lib/media/presentation_timeline.js +23 -0
- package/lib/media/segment_index.js +21 -20
- package/lib/media/segment_utils.js +10 -1
- package/lib/media/streaming_engine.js +2 -14
- package/lib/mss/mss_parser.js +4 -0
- package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
- package/lib/offline/manifest_converter.js +10 -3
- package/lib/offline/storage.js +2 -0
- package/lib/player.js +30 -4
- package/lib/polyfill/media_capabilities.js +20 -1
- package/lib/polyfill/mediasource.js +37 -14
- package/lib/text/vtt_text_parser.js +51 -51
- package/lib/transmuxer/h264.js +3 -3
- package/lib/transmuxer/h265.js +3 -3
- package/lib/transmuxer/ts_transmuxer.js +42 -3
- package/lib/util/manifest_parser_utils.js +1 -0
- package/lib/util/mime_utils.js +10 -5
- package/lib/util/mp4_parser.js +57 -0
- package/lib/util/periods.js +5 -37
- package/lib/util/platform.js +4 -0
- package/lib/util/stream_utils.js +334 -271
- package/lib/util/ts_parser.js +99 -10
- package/package.json +1 -1
- 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
|
-
|
|
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
|
/**
|
package/lib/offline/storage.js
CHANGED
|
@@ -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 <=
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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'
|
|
88
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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}
|
package/lib/transmuxer/h264.js
CHANGED
|
@@ -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
|
|
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(
|
|
271
|
-
|
|
270
|
+
nalusData.push(naluLength);
|
|
271
|
+
nalusData.push(nalu.fullData);
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
if (!nalusData.length) {
|
package/lib/transmuxer/h265.js
CHANGED
|
@@ -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
|
|
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(
|
|
656
|
-
|
|
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 = [];
|
package/lib/util/mime_utils.js
CHANGED
|
@@ -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 = [
|
|
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
|
-
|
|
97
|
-
|
|
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
|
}
|
package/lib/util/mp4_parser.js
CHANGED
|
@@ -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.
|