shaka-player 4.2.5 → 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-and-test.yaml +7 -0
- package/.github/workflows/sync-labels.yaml +1 -1
- package/.github/workflows/update-issues.yaml +1 -1
- package/AUTHORS +4 -0
- package/CHANGELOG.md +114 -74
- package/CONTRIBUTORS +4 -0
- package/README.md +14 -0
- package/conditional/dummy_cast_proxy.js +120 -0
- package/demo/common/assets.js +58 -0
- package/demo/common/message_ids.js +22 -1
- package/demo/config.js +89 -5
- package/demo/demo.less +7 -1
- package/demo/index.html +8 -1
- package/demo/locales/en.json +21 -1
- package/demo/locales/source.json +82 -2
- package/demo/main.js +57 -1
- package/demo/search.js +2 -0
- package/demo/visualizer.js +337 -0
- package/demo/visualizer_button.js +103 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +107 -88
- package/dist/demo.compiled.debug.map +4 -4
- package/dist/demo.compiled.js +107 -88
- package/dist/demo.compiled.map +4 -4
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +21 -13
- package/dist/locales.js +31 -31
- package/dist/receiver.compiled.debug.js +75 -77
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +75 -77
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +181 -27
- package/dist/shaka-player.compiled.debug.d.ts +181 -27
- package/dist/shaka-player.compiled.debug.externs.js +169 -31
- package/dist/shaka-player.compiled.debug.js +510 -450
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +169 -31
- package/dist/shaka-player.compiled.js +912 -874
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +181 -27
- package/dist/shaka-player.ui.debug.d.ts +181 -27
- package/dist/shaka-player.ui.debug.externs.js +169 -31
- package/dist/shaka-player.ui.debug.js +533 -471
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +169 -31
- package/dist/shaka-player.ui.js +1073 -1036
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/lcevc-architecture.png +0 -0
- package/docs/design/lcevc-demo.png +0 -0
- package/docs/design/lcevc-integration.md +72 -0
- package/docs/tutorials/fairplay.md +7 -0
- package/docs/tutorials/faq.md +1 -1
- package/docs/tutorials/ui.md +7 -7
- package/docs/tutorials/upgrade.md +6 -0
- package/externs/ima.js +13 -1
- package/externs/lcevc.js +81 -0
- package/externs/mux.js +1 -42
- package/externs/shaka/ads.js +11 -1
- package/externs/shaka/manifest_parser.js +5 -1
- package/externs/shaka/mp4_parser.js +3 -0
- package/externs/shaka/player.js +178 -15
- package/lib/abr/simple_abr_manager.js +7 -0
- package/lib/ads/ad_manager.js +0 -8
- package/lib/ads/client_side_ad.js +15 -0
- package/lib/ads/client_side_ad_manager.js +21 -8
- package/lib/ads/server_side_ad.js +15 -0
- package/lib/ads/server_side_ad_manager.js +3 -3
- package/lib/cast/cast_utils.js +2 -0
- package/lib/cea/dummy_cea_parser.js +28 -0
- package/lib/cea/i_cea_parser.js +14 -2
- package/lib/cea/mp4_cea_parser.js +108 -5
- package/lib/config/auto_show_text.js +33 -0
- package/lib/dash/dash_parser.js +1 -1
- package/lib/hls/hls_parser.js +808 -384
- package/lib/lcevc/lcevc_dil.js +152 -0
- package/lib/media/closed_caption_parser.js +9 -6
- package/lib/media/drm_engine.js +14 -6
- package/lib/media/media_source_capabilities.js +36 -0
- package/lib/media/media_source_engine.js +113 -28
- package/lib/media/presentation_timeline.js +26 -11
- package/lib/media/segment_reference.js +49 -1
- package/lib/media/streaming_engine.js +142 -12
- package/lib/media/transmuxer.js +78 -21
- package/lib/offline/storage.js +1 -0
- package/lib/player.js +456 -77
- package/lib/polyfill/media_capabilities.js +9 -5
- package/lib/polyfill/patchedmediakeys_apple.js +63 -4
- package/lib/text/vtt_text_parser.js +35 -1
- package/lib/util/dom_utils.js +0 -1
- package/lib/util/error.js +17 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/id3_utils.js +300 -0
- package/lib/util/mime_utils.js +3 -6
- package/lib/util/mp4_box_parsers.js +39 -5
- package/lib/util/mp4_parser.js +35 -0
- package/lib/util/player_configuration.js +23 -1
- package/lib/util/stream_utils.js +5 -3
- package/lib/util/ts_parser.js +499 -0
- package/maintained-branches.md +2 -2
- package/package.json +7 -4
- package/roadmap.md +5 -3
- package/test/ads/ad_manager_unit.js +182 -29
- package/test/cast/cast_utils_unit.js +3 -1
- package/test/dash/dash_parser_content_protection_unit.js +1 -0
- package/test/dash/dash_parser_live_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +93 -1
- package/test/dash/dash_parser_segment_base_unit.js +1 -0
- package/test/dash/dash_parser_segment_list_unit.js +1 -0
- package/test/dash/dash_parser_segment_template_unit.js +1 -0
- package/test/demo/demo_unit.js +2 -1
- package/test/hls/hls_live_unit.js +253 -19
- package/test/hls/hls_parser_unit.js +631 -12
- package/test/media/closed_caption_parser_unit.js +2 -1
- package/test/media/drm_engine_integration.js +3 -1
- package/test/media/media_source_engine_integration.js +71 -6
- package/test/media/media_source_engine_unit.js +10 -5
- package/test/media/streaming_engine_integration.js +4 -1
- package/test/media/streaming_engine_unit.js +144 -1
- package/test/media/transmuxer_integration.js +21 -10
- package/test/player_integration.js +224 -82
- package/test/player_unit.js +5 -2
- package/test/polyfill/media_capabilities_unit.js +21 -23
- package/test/polyfill/patchedmediakeys_apple_unit.js +130 -0
- package/test/test/assets/id3-metadata.aac +0 -0
- package/test/test/assets/id3-metadata.ts +0 -0
- package/test/test/assets/thumbnails-sprites.vtt +10 -0
- package/test/test/assets/thumbnails.vtt +10 -0
- package/test/test/util/dash_parser_util.js +2 -0
- package/test/test/util/fake_ad.js +21 -0
- package/test/test/util/fake_media_source_engine.js +7 -0
- package/test/test/util/id3_generator.js +100 -0
- package/test/test/util/manifest_generator.js +3 -6
- package/test/test/util/simple_fakes.js +4 -0
- package/test/test/util/stream_generator.js +38 -0
- package/test/test/util/test_scheme.js +43 -14
- package/test/text/vtt_text_parser_unit.js +11 -0
- package/test/util/id3_utils_unit.js +116 -0
- package/test/util/ts_parser_unit.js +77 -0
- package/third_party/closure-uri/uri.js +1 -1
- package/ui/less/containers.less +7 -0
- package/ui/ui.js +43 -4
package/lib/util/mp4_parser.js
CHANGED
|
@@ -172,6 +172,7 @@ shaka.util.Mp4Parser = class {
|
|
|
172
172
|
|
|
173
173
|
/** @type {shaka.extern.ParsedBox} */
|
|
174
174
|
const box = {
|
|
175
|
+
name,
|
|
175
176
|
parser: this,
|
|
176
177
|
partialOkay: partialOkay || false,
|
|
177
178
|
version,
|
|
@@ -236,6 +237,40 @@ shaka.util.Mp4Parser = class {
|
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
|
|
240
|
+
/**
|
|
241
|
+
* A callback that tells the Mp4 parser to treat the body of a box as a visual
|
|
242
|
+
* sample entry. A visual sample entry has some fixed-sized fields
|
|
243
|
+
* describing the video codec parameters, followed by an arbitrary number of
|
|
244
|
+
* appended children. Each child is a box.
|
|
245
|
+
*
|
|
246
|
+
* @param {!shaka.extern.ParsedBox} box
|
|
247
|
+
* @export
|
|
248
|
+
*/
|
|
249
|
+
static visualSampleEntry(box) {
|
|
250
|
+
// The "reader" starts at the payload, so we need to add the header to the
|
|
251
|
+
// start position. The header size varies.
|
|
252
|
+
const headerSize = shaka.util.Mp4Parser.headerSize(box);
|
|
253
|
+
|
|
254
|
+
// Skip 6 reserved bytes.
|
|
255
|
+
// Skip 2-byte data reference index.
|
|
256
|
+
// Skip 16 more reserved bytes.
|
|
257
|
+
// Skip 4 bytes for width/height.
|
|
258
|
+
// Skip 8 bytes for horizontal/vertical resolution.
|
|
259
|
+
// Skip 4 more reserved bytes (0)
|
|
260
|
+
// Skip 2-byte frame count.
|
|
261
|
+
// Skip 32-byte compressor name (length byte, then name, then 0-padding).
|
|
262
|
+
// Skip 2-byte depth.
|
|
263
|
+
// Skip 2 more reserved bytes (0xff)
|
|
264
|
+
// 78 bytes total.
|
|
265
|
+
// See also https://github.com/shaka-project/shaka-packager/blob/d5ca6e84/packager/media/formats/mp4/box_definitions.cc#L1544
|
|
266
|
+
box.reader.skip(78);
|
|
267
|
+
|
|
268
|
+
while (box.reader.hasMoreData() && !box.parser.done_) {
|
|
269
|
+
box.parser.parseNext(box.start + headerSize, box.reader, box.partialOkay);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
239
274
|
/**
|
|
240
275
|
* Create a callback that tells the Mp4 parser to treat the body of a box as a
|
|
241
276
|
* binary blob and to parse the body's contents using the provided callback.
|
|
@@ -8,6 +8,7 @@ goog.provide('shaka.util.PlayerConfiguration');
|
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.abr.SimpleAbrManager');
|
|
11
|
+
goog.require('shaka.config.AutoShowText');
|
|
11
12
|
goog.require('shaka.log');
|
|
12
13
|
goog.require('shaka.net.NetworkingEngine');
|
|
13
14
|
goog.require('shaka.util.ConfigUtils');
|
|
@@ -127,6 +128,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
127
128
|
[element],
|
|
128
129
|
element);
|
|
129
130
|
},
|
|
131
|
+
sequenceMode: false,
|
|
130
132
|
},
|
|
131
133
|
hls: {
|
|
132
134
|
ignoreTextStreamFailures: false,
|
|
@@ -136,6 +138,8 @@ shaka.util.PlayerConfiguration = class {
|
|
|
136
138
|
ignoreManifestProgramDateTime: false,
|
|
137
139
|
mediaPlaylistFullMimeType:
|
|
138
140
|
'video/mp2t; codecs="avc1.42E01E, mp4a.40.2"',
|
|
141
|
+
useSafariBehaviorForLive: true,
|
|
142
|
+
liveSegmentsDelay: 3,
|
|
139
143
|
},
|
|
140
144
|
};
|
|
141
145
|
|
|
@@ -160,7 +164,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
160
164
|
startAtSegmentBoundary: false,
|
|
161
165
|
gapDetectionThreshold: 0.5,
|
|
162
166
|
durationBackoff: 1,
|
|
163
|
-
|
|
167
|
+
forceTransmux: false,
|
|
164
168
|
// Offset by 5 seconds since Chromecast takes a few seconds to start
|
|
165
169
|
// playing after a seek, even when buffered.
|
|
166
170
|
safeSeekOffset: 5,
|
|
@@ -182,6 +186,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
182
186
|
dispatchAllEmsgBoxes: false,
|
|
183
187
|
observeQualityChanges: false,
|
|
184
188
|
maxDisabledTime: 30,
|
|
189
|
+
parsePrftBox: false,
|
|
185
190
|
};
|
|
186
191
|
|
|
187
192
|
// WebOS, Tizen, and Chromecast have long hardware pipelines that respond
|
|
@@ -256,6 +261,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
256
261
|
slowHalfLife: 5,
|
|
257
262
|
},
|
|
258
263
|
restrictToElementSize: false,
|
|
264
|
+
restrictToScreenSize: false,
|
|
259
265
|
ignoreDevicePixelRatio: false,
|
|
260
266
|
};
|
|
261
267
|
|
|
@@ -266,14 +272,29 @@ shaka.util.PlayerConfiguration = class {
|
|
|
266
272
|
useHeaders: false,
|
|
267
273
|
};
|
|
268
274
|
|
|
275
|
+
const lcevc = {
|
|
276
|
+
enabled: false,
|
|
277
|
+
dynamicPerformanceScaling: true,
|
|
278
|
+
logLevel: 0,
|
|
279
|
+
drawLogo: false,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const mediaSource = {
|
|
283
|
+
sourceBufferExtraFeatures: '',
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const AutoShowText = shaka.config.AutoShowText;
|
|
287
|
+
|
|
269
288
|
/** @type {shaka.extern.PlayerConfiguration} */
|
|
270
289
|
const config = {
|
|
271
290
|
drm: drm,
|
|
272
291
|
manifest: manifest,
|
|
273
292
|
streaming: streaming,
|
|
293
|
+
mediaSource: mediaSource,
|
|
274
294
|
offline: offline,
|
|
275
295
|
abrFactory: () => new shaka.abr.SimpleAbrManager(),
|
|
276
296
|
abr: abr,
|
|
297
|
+
autoShowText: AutoShowText.IF_SUBTITLES_MAY_BE_NEEDED,
|
|
277
298
|
preferredAudioLanguage: '',
|
|
278
299
|
preferredTextLanguage: '',
|
|
279
300
|
preferredVariantRole: '',
|
|
@@ -299,6 +320,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
299
320
|
playRangeEnd: Infinity,
|
|
300
321
|
textDisplayFactory: () => null,
|
|
301
322
|
cmcd: cmcd,
|
|
323
|
+
lcevc: lcevc,
|
|
302
324
|
};
|
|
303
325
|
|
|
304
326
|
// Add this callback so that we can reference the preferred audio language
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -8,6 +8,7 @@ goog.provide('shaka.util.StreamUtils');
|
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.log');
|
|
11
|
+
goog.require('shaka.media.Capabilities');
|
|
11
12
|
goog.require('shaka.text.TextEngine');
|
|
12
13
|
goog.require('shaka.util.Functional');
|
|
13
14
|
goog.require('shaka.util.LanguageUtils');
|
|
@@ -437,6 +438,7 @@ shaka.util.StreamUtils = class {
|
|
|
437
438
|
// See: https://github.com/shaka-project/shaka-player/issues/3860
|
|
438
439
|
const video = variant.video;
|
|
439
440
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
441
|
+
const Capabilities = shaka.media.Capabilities;
|
|
440
442
|
if (video) {
|
|
441
443
|
let videoCodecs =
|
|
442
444
|
shaka.util.StreamUtils.getCorrectVideoCodecs_(video.codecs);
|
|
@@ -454,13 +456,13 @@ shaka.util.StreamUtils = class {
|
|
|
454
456
|
shaka.util.StreamUtils.getCorrectAudioCodecs_(audioCodecs);
|
|
455
457
|
const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
456
458
|
video.mimeType, audioCodecs, ContentType.AUDIO);
|
|
457
|
-
if (!
|
|
459
|
+
if (!Capabilities.isTypeSupported(audioFullType)) {
|
|
458
460
|
return false;
|
|
459
461
|
}
|
|
460
462
|
}
|
|
461
463
|
const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
462
464
|
video.mimeType, videoCodecs, ContentType.VIDEO);
|
|
463
|
-
if (!
|
|
465
|
+
if (!Capabilities.isTypeSupported(fullType)) {
|
|
464
466
|
return false;
|
|
465
467
|
}
|
|
466
468
|
}
|
|
@@ -470,7 +472,7 @@ shaka.util.StreamUtils = class {
|
|
|
470
472
|
shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
|
|
471
473
|
const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
472
474
|
audio.mimeType, codecs, ContentType.AUDIO);
|
|
473
|
-
if (!
|
|
475
|
+
if (!Capabilities.isTypeSupported(fullType)) {
|
|
474
476
|
return false;
|
|
475
477
|
}
|
|
476
478
|
}
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.provide('shaka.util.TsParser');
|
|
8
|
+
|
|
9
|
+
goog.require('goog.asserts');
|
|
10
|
+
goog.require('shaka.log');
|
|
11
|
+
goog.require('shaka.util.Id3Utils');
|
|
12
|
+
goog.require('shaka.util.Uint8ArrayUtils');
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @see https://en.wikipedia.org/wiki/MPEG_transport_stream
|
|
17
|
+
* @export
|
|
18
|
+
*/
|
|
19
|
+
shaka.util.TsParser = class {
|
|
20
|
+
/** */
|
|
21
|
+
constructor() {
|
|
22
|
+
/** @private {?number} */
|
|
23
|
+
this.pmtId_ = null;
|
|
24
|
+
|
|
25
|
+
/** @private {boolean} */
|
|
26
|
+
this.pmtParsed_ = false;
|
|
27
|
+
|
|
28
|
+
/** @private {?number} */
|
|
29
|
+
this.videoStartTime_ = null;
|
|
30
|
+
|
|
31
|
+
/** @private {?number} */
|
|
32
|
+
this.videoPid_ = null;
|
|
33
|
+
|
|
34
|
+
/** @private {?string} */
|
|
35
|
+
this.videoCodec_ = null;
|
|
36
|
+
|
|
37
|
+
/** @private {!Array.<Uint8Array>} */
|
|
38
|
+
this.videoData_ = [];
|
|
39
|
+
|
|
40
|
+
/** @private {?number} */
|
|
41
|
+
this.audioStartTime_ = null;
|
|
42
|
+
|
|
43
|
+
/** @private {?number} */
|
|
44
|
+
this.audioPid_ = null;
|
|
45
|
+
|
|
46
|
+
/** @private {?string} */
|
|
47
|
+
this.audioCodec_ = null;
|
|
48
|
+
|
|
49
|
+
/** @private {!Array.<Uint8Array>} */
|
|
50
|
+
this.audioData_ = [];
|
|
51
|
+
|
|
52
|
+
/** @private {?number} */
|
|
53
|
+
this.id3Pid_ = null;
|
|
54
|
+
|
|
55
|
+
/** @private {!Array.<Uint8Array>} */
|
|
56
|
+
this.id3Data_ = [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse the given data
|
|
61
|
+
*
|
|
62
|
+
* @param {Uint8Array} data
|
|
63
|
+
* @return {!shaka.util.TsParser}
|
|
64
|
+
*/
|
|
65
|
+
parse(data) {
|
|
66
|
+
const timescale = shaka.util.TsParser.Timescale_;
|
|
67
|
+
const packetLength = shaka.util.TsParser.PacketLength_;
|
|
68
|
+
|
|
69
|
+
// A TS fragment should contain at least 3 TS packets, a PAT, a PMT, and
|
|
70
|
+
// one PID.
|
|
71
|
+
if (data.length < 3 * packetLength) {
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
const syncOffset = Math.max(0, shaka.util.TsParser.syncOffset(data));
|
|
75
|
+
|
|
76
|
+
const length = data.length - (data.length + syncOffset) % packetLength;
|
|
77
|
+
|
|
78
|
+
let unknownPIDs = false;
|
|
79
|
+
|
|
80
|
+
// loop through TS packets
|
|
81
|
+
for (let start = syncOffset; start < length; start += packetLength) {
|
|
82
|
+
if (data[start] === 0x47) {
|
|
83
|
+
const payloadUnitStartIndicator = !!(data[start + 1] & 0x40);
|
|
84
|
+
// pid is a 13-bit field starting at the last 5 bits of TS[1]
|
|
85
|
+
const pid = ((data[start + 1] & 0x1f) << 8) + data[start + 2];
|
|
86
|
+
const adaptationFieldControl = (data[start + 3] & 0x30) >> 4;
|
|
87
|
+
|
|
88
|
+
// if an adaption field is present, its length is specified by the
|
|
89
|
+
// fifth byte of the TS packet header.
|
|
90
|
+
let offset;
|
|
91
|
+
if (adaptationFieldControl > 1) {
|
|
92
|
+
offset = start + 5 + data[start + 4];
|
|
93
|
+
// continue if there is only adaptation field
|
|
94
|
+
if (offset === start + packetLength) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
offset = start + 4;
|
|
99
|
+
}
|
|
100
|
+
switch (pid) {
|
|
101
|
+
case 0:
|
|
102
|
+
if (payloadUnitStartIndicator) {
|
|
103
|
+
offset += data[offset] + 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.pmtId_ = this.getPmtId_(data, offset);
|
|
107
|
+
break;
|
|
108
|
+
case 17:
|
|
109
|
+
case 0x1fff:
|
|
110
|
+
break;
|
|
111
|
+
case this.pmtId_: {
|
|
112
|
+
if (payloadUnitStartIndicator) {
|
|
113
|
+
offset += data[offset] + 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const parsedPIDs = this.parsePMT(data, offset);
|
|
117
|
+
|
|
118
|
+
// only update track id if track PID found while parsing PMT
|
|
119
|
+
// this is to avoid resetting the PID to -1 in case
|
|
120
|
+
// track PID transiently disappears from the stream
|
|
121
|
+
// this could happen in case of transient missing audio samples
|
|
122
|
+
// for example
|
|
123
|
+
// NOTE this is only the PID of the track as found in TS,
|
|
124
|
+
// but we are not using this for MP4 track IDs.
|
|
125
|
+
if (this.videoPid_ == null) {
|
|
126
|
+
this.videoPid_ = parsedPIDs.video;
|
|
127
|
+
this.videoCodec_ = parsedPIDs.videoCodec;
|
|
128
|
+
}
|
|
129
|
+
if (this.audioPid_ == null) {
|
|
130
|
+
this.audioPid_ = parsedPIDs.audio;
|
|
131
|
+
this.audioCodec_ = parsedPIDs.audioCodec;
|
|
132
|
+
}
|
|
133
|
+
if (this.id3Pid_ == null) {
|
|
134
|
+
this.id3Pid_ = parsedPIDs.id3;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (unknownPIDs && !this.pmtParsed_) {
|
|
138
|
+
shaka.log.debug('reparse from beginning');
|
|
139
|
+
unknownPIDs = false;
|
|
140
|
+
// we set it to -188, the += 188 in the for loop will reset
|
|
141
|
+
// start to 0
|
|
142
|
+
start = syncOffset - packetLength;
|
|
143
|
+
}
|
|
144
|
+
this.pmtParsed_ = true;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case this.videoPid_: {
|
|
148
|
+
const videoData = data.subarray(offset, start + packetLength);
|
|
149
|
+
if (this.videoStartTime_ == null) {
|
|
150
|
+
const pes = this.parsePES(videoData);
|
|
151
|
+
if (pes && pes.pts != null) {
|
|
152
|
+
this.videoStartTime_ = pes.pts / timescale;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
this.videoData_.push(videoData);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case this.audioPid_: {
|
|
159
|
+
const audioData = data.subarray(offset, start + packetLength);
|
|
160
|
+
if (this.audioStartTime_ == null) {
|
|
161
|
+
const pes = this.parsePES(audioData);
|
|
162
|
+
if (pes && pes.pts != null) {
|
|
163
|
+
this.audioStartTime_ = pes.pts / timescale;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
this.audioData_.push(audioData);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case this.id3Pid_:
|
|
170
|
+
this.id3Data_.push(data.subarray(offset, start + packetLength));
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
unknownPIDs = true;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
shaka.log.warning('Found TS packet that do not start with 0x47');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get the PMT ID from the PAT
|
|
185
|
+
*
|
|
186
|
+
* @param {Uint8Array} data
|
|
187
|
+
* @param {number} offset
|
|
188
|
+
* @return {number}
|
|
189
|
+
* @private
|
|
190
|
+
*/
|
|
191
|
+
getPmtId_(data, offset) {
|
|
192
|
+
// skip the PSI header and parse the first PMT entry
|
|
193
|
+
return ((data[offset + 10] & 0x1f) << 8) | data[offset + 11];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Parse PMT
|
|
198
|
+
*
|
|
199
|
+
* @param {Uint8Array} data
|
|
200
|
+
* @param {number} offset
|
|
201
|
+
* @return {!shaka.util.TsParser.PMT}
|
|
202
|
+
*/
|
|
203
|
+
parsePMT(data, offset) {
|
|
204
|
+
const result = {
|
|
205
|
+
audio: -1,
|
|
206
|
+
video: -1,
|
|
207
|
+
id3: -1,
|
|
208
|
+
audioCodec: '',
|
|
209
|
+
videoCodec: '',
|
|
210
|
+
};
|
|
211
|
+
const sectionLength = ((data[offset + 1] & 0x0f) << 8) | data[offset + 2];
|
|
212
|
+
const tableEnd = offset + 3 + sectionLength - 4;
|
|
213
|
+
// to determine where the table is, we have to figure out how
|
|
214
|
+
// long the program info descriptors are
|
|
215
|
+
const programInfoLength =
|
|
216
|
+
((data[offset + 10] & 0x0f) << 8) | data[offset + 11];
|
|
217
|
+
// advance the offset to the first entry in the mapping table
|
|
218
|
+
offset += 12 + programInfoLength;
|
|
219
|
+
while (offset < tableEnd) {
|
|
220
|
+
const pid = ((data[offset + 1] & 0x1f) << 8) | data[offset + 2];
|
|
221
|
+
switch (data[offset]) {
|
|
222
|
+
// SAMPLE-AES AAC
|
|
223
|
+
case 0xcf:
|
|
224
|
+
break;
|
|
225
|
+
// ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio)
|
|
226
|
+
case 0x0f:
|
|
227
|
+
if (result.audio === -1) {
|
|
228
|
+
result.audio = pid;
|
|
229
|
+
result.audioCodec = 'aac';
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
// Packetized metadata (ID3)
|
|
233
|
+
case 0x15:
|
|
234
|
+
if (result.id3 === -1) {
|
|
235
|
+
result.id3 = pid;
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
// SAMPLE-AES AVC
|
|
239
|
+
case 0xdb:
|
|
240
|
+
break;
|
|
241
|
+
// ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video)
|
|
242
|
+
case 0x1b:
|
|
243
|
+
if (result.video === -1) {
|
|
244
|
+
result.video = pid;
|
|
245
|
+
result.videoCodec = 'avc';
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
// ISO/IEC 11172-3 (MPEG-1 audio)
|
|
249
|
+
// or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio)
|
|
250
|
+
case 0x03:
|
|
251
|
+
case 0x04:
|
|
252
|
+
if (result.audio === -1) {
|
|
253
|
+
result.audio = pid;
|
|
254
|
+
result.audioCodec = 'mp3';
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
// HEVC
|
|
258
|
+
case 0x24:
|
|
259
|
+
if (result.video === -1) {
|
|
260
|
+
result.video = pid;
|
|
261
|
+
result.videoCodec = 'hvc';
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
default:
|
|
265
|
+
// shaka.log.warning('Unknown stream type:', data[offset]);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
// move to the next table entry
|
|
269
|
+
// skip past the elementary stream descriptors, if present
|
|
270
|
+
offset += (((data[offset + 3] & 0x0f) << 8) | data[offset + 4]) + 5;
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Parse PES
|
|
277
|
+
*
|
|
278
|
+
* @param {Uint8Array} data
|
|
279
|
+
* @return {?shaka.util.TsParser.PES}
|
|
280
|
+
*/
|
|
281
|
+
parsePES(data) {
|
|
282
|
+
const startPrefix = (data[0] << 16) | (data[1] << 8) | data[2];
|
|
283
|
+
// In certain live streams, the start of a TS fragment has ts packets
|
|
284
|
+
// that are frame data that is continuing from the previous fragment. This
|
|
285
|
+
// is to check that the pes data is the start of a new pes data
|
|
286
|
+
if (startPrefix !== 1) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
/** @type {shaka.util.TsParser.PES} */
|
|
290
|
+
const pes = {
|
|
291
|
+
data: new Uint8Array(0),
|
|
292
|
+
// get the packet length, this will be 0 for video
|
|
293
|
+
packetLength: 6 + ((data[4] << 8) | data[5]),
|
|
294
|
+
pts: null,
|
|
295
|
+
dts: null,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// PES packets may be annotated with a PTS value, or a PTS value
|
|
299
|
+
// and a DTS value. Determine what combination of values is
|
|
300
|
+
// available to work with.
|
|
301
|
+
const ptsDtsFlags = data[7];
|
|
302
|
+
|
|
303
|
+
// PTS and DTS are normally stored as a 33-bit number. Javascript
|
|
304
|
+
// performs all bitwise operations on 32-bit integers but javascript
|
|
305
|
+
// supports a much greater range (52-bits) of integer using standard
|
|
306
|
+
// mathematical operations.
|
|
307
|
+
// We construct a 31-bit value using bitwise operators over the 31
|
|
308
|
+
// most significant bits and then multiply by 4 (equal to a left-shift
|
|
309
|
+
// of 2) before we add the final 2 least significant bits of the
|
|
310
|
+
// timestamp (equal to an OR.)
|
|
311
|
+
if (ptsDtsFlags & 0xC0) {
|
|
312
|
+
// the PTS and DTS are not written out directly. For information
|
|
313
|
+
// on how they are encoded, see
|
|
314
|
+
// http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
|
|
315
|
+
pes.pts =
|
|
316
|
+
(data[9] & 0x0e) * 536870912 + // 1 << 29
|
|
317
|
+
(data[10] & 0xff) * 4194304 + // 1 << 22
|
|
318
|
+
(data[11] & 0xfe) * 16384 + // 1 << 14
|
|
319
|
+
(data[12] & 0xff) * 128 + // 1 << 7
|
|
320
|
+
(data[13] & 0xfe) / 2;
|
|
321
|
+
|
|
322
|
+
pes.dts = pes.pts;
|
|
323
|
+
if (ptsDtsFlags & 0x40) {
|
|
324
|
+
pes.dts =
|
|
325
|
+
(data[14] & 0x0e) * 536870912 + // 1 << 29
|
|
326
|
+
(data[15] & 0xff) * 4194304 + // 1 << 22
|
|
327
|
+
(data[16] & 0xfe) * 16384 + // 1 << 14
|
|
328
|
+
(data[17] & 0xff) * 128 + // 1 << 7
|
|
329
|
+
(data[18] & 0xfe) / 2;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// the data section starts immediately after the PES header.
|
|
333
|
+
// pes_header_data_length specifies the number of header bytes
|
|
334
|
+
// that follow the last byte of the field.
|
|
335
|
+
pes.data = data.subarray(9 + data[8]);
|
|
336
|
+
|
|
337
|
+
return pes;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Return the ID3 metadata
|
|
342
|
+
*
|
|
343
|
+
* @return {!Array.<shaka.extern.ID3Metadata>}
|
|
344
|
+
*/
|
|
345
|
+
getMetadata() {
|
|
346
|
+
const timescale = shaka.util.TsParser.Timescale_;
|
|
347
|
+
const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
|
|
348
|
+
const metadata = [];
|
|
349
|
+
let prevId3Data = new Uint8Array(0);
|
|
350
|
+
// parsePES() only works if the data begins on a PES boundary.
|
|
351
|
+
// Try the last data blob first, and if it doesn't begin on a
|
|
352
|
+
// PES boundary, prepend the previous blob and try again.
|
|
353
|
+
// This way, a successful parse will always begin and end on
|
|
354
|
+
// the correct boundary, and no data will be skipped.
|
|
355
|
+
for (let i = this.id3Data_.length - 1; i >= 0; i--) {
|
|
356
|
+
const data = this.id3Data_[i];
|
|
357
|
+
goog.asserts.assert(data, 'We should have a data');
|
|
358
|
+
const id3Data = Uint8ArrayUtils.concat(data, prevId3Data);
|
|
359
|
+
const pes = this.parsePES(id3Data);
|
|
360
|
+
if (pes) {
|
|
361
|
+
metadata.unshift({
|
|
362
|
+
cueTime: pes.pts ? pes.pts / timescale : null,
|
|
363
|
+
data: pes.data,
|
|
364
|
+
frames: shaka.util.Id3Utils.getID3Frames(pes.data),
|
|
365
|
+
dts: pes.dts,
|
|
366
|
+
pts: pes.pts,
|
|
367
|
+
});
|
|
368
|
+
prevId3Data = new Uint8Array(0);
|
|
369
|
+
} else {
|
|
370
|
+
prevId3Data = id3Data;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return metadata;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Return the start time for the audio and video
|
|
378
|
+
*
|
|
379
|
+
* @return {{audio: ?number, video: ?number}}
|
|
380
|
+
*/
|
|
381
|
+
getStartTime() {
|
|
382
|
+
return {
|
|
383
|
+
audio: this.audioStartTime_,
|
|
384
|
+
video: this.videoStartTime_,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Return the audio and video codecs
|
|
390
|
+
*
|
|
391
|
+
* @return {{audio: ?string, video: ?string}}
|
|
392
|
+
*/
|
|
393
|
+
getCodecs() {
|
|
394
|
+
return {
|
|
395
|
+
audio: this.audioCodec_,
|
|
396
|
+
video: this.videoCodec_,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Check if the passed data corresponds to an MPEG2-TS
|
|
402
|
+
*
|
|
403
|
+
* @param {Uint8Array} data
|
|
404
|
+
* @return {boolean}
|
|
405
|
+
*/
|
|
406
|
+
static probe(data) {
|
|
407
|
+
const syncOffset = shaka.util.TsParser.syncOffset(data);
|
|
408
|
+
if (syncOffset < 0) {
|
|
409
|
+
return false;
|
|
410
|
+
} else {
|
|
411
|
+
if (syncOffset > 0) {
|
|
412
|
+
shaka.log.warning('MPEG2-TS detected but first sync word found @ ' +
|
|
413
|
+
'offset ' + syncOffset + ', junk ahead ?');
|
|
414
|
+
}
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Returns the synchronization offset
|
|
421
|
+
*
|
|
422
|
+
* @param {Uint8Array} data
|
|
423
|
+
* @return {number}
|
|
424
|
+
*/
|
|
425
|
+
static syncOffset(data) {
|
|
426
|
+
const packetLength = shaka.util.TsParser.PacketLength_;
|
|
427
|
+
// scan 1000 first bytes
|
|
428
|
+
const scanwindow = Math.min(1000, data.length - 3 * packetLength);
|
|
429
|
+
let i = 0;
|
|
430
|
+
while (i < scanwindow) {
|
|
431
|
+
// a TS fragment should contain at least 3 TS packets, a PAT, a PMT, and
|
|
432
|
+
// one PID, each starting with 0x47
|
|
433
|
+
if (data[i] === 0x47 &&
|
|
434
|
+
data[i + packetLength] === 0x47 &&
|
|
435
|
+
data[i + 2 * packetLength] === 0x47) {
|
|
436
|
+
return i;
|
|
437
|
+
} else {
|
|
438
|
+
i++;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return -1;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* @const {number}
|
|
448
|
+
* @private
|
|
449
|
+
*/
|
|
450
|
+
shaka.util.TsParser.PacketLength_ = 188;
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* @const {number}
|
|
455
|
+
* @private
|
|
456
|
+
*/
|
|
457
|
+
shaka.util.TsParser.Timescale_ = 90000;
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* @typedef {{
|
|
462
|
+
* audio: number,
|
|
463
|
+
* video: number,
|
|
464
|
+
* id3: number,
|
|
465
|
+
* audioCodec: string,
|
|
466
|
+
* videoCodec: string
|
|
467
|
+
* }}
|
|
468
|
+
*
|
|
469
|
+
* @summary PMT.
|
|
470
|
+
* @property {number} audio
|
|
471
|
+
* Audio PID
|
|
472
|
+
* @property {number} video
|
|
473
|
+
* Video PID
|
|
474
|
+
* @property {number} id3
|
|
475
|
+
* ID3 PID
|
|
476
|
+
* @property {string} audioCodec
|
|
477
|
+
* Audio codec
|
|
478
|
+
* @property {string} videoCodec
|
|
479
|
+
* Video codec
|
|
480
|
+
*/
|
|
481
|
+
shaka.util.TsParser.PMT;
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* @typedef {{
|
|
486
|
+
* data: Uint8Array,
|
|
487
|
+
* packetLength: number,
|
|
488
|
+
* pts: ?number,
|
|
489
|
+
* dts: ?number
|
|
490
|
+
* }}
|
|
491
|
+
*
|
|
492
|
+
* @summary PES.
|
|
493
|
+
* @property {Uint8Array} data
|
|
494
|
+
* @property {number} packetLength
|
|
495
|
+
* @property {?number} pts
|
|
496
|
+
* @property {?number} dts
|
|
497
|
+
*/
|
|
498
|
+
shaka.util.TsParser.PES;
|
|
499
|
+
|
package/maintained-branches.md
CHANGED