Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Parse ID3 metadata #4409

Merged
merged 38 commits into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8ff3a78
feat: Parse ID3 metadata
Aug 16, 2022
6d3fd90
Fix typo
Aug 24, 2022
1a064c6
Update variable name
Aug 24, 2022
b1b186e
Merge remote-tracking branch 'upstream/main' into id3
Aug 26, 2022
5584ffe
Fix media source engine
Aug 26, 2022
47d8641
Add ts_parser.js
Aug 26, 2022
0432b90
Update ts_parser.js
Aug 29, 2022
16294cb
Remove mux.js metadata and integrate own metadata
Aug 29, 2022
57e8dc0
Fix typo
Aug 30, 2022
6090779
Add ID3 support in aac
Aug 30, 2022
b494b64
Merge branch 'main' into id3
avelad Sep 28, 2022
1c3e385
Add ID3-EMSG support
avelad Sep 28, 2022
9e19aef
Update Id3Utils
avelad Sep 28, 2022
0001d39
Merge branch 'main' into id3
avelad Oct 3, 2022
9b35f2a
Update shaka.util.TsParser
avelad Oct 3, 2022
9483a26
Update shaka.extern.ID3Metadata definition
avelad Oct 3, 2022
d22645b
Merge branch 'main' into id3
avelad Oct 3, 2022
c5021e2
fix comment
avelad Oct 3, 2022
714fe80
Merge branch 'main' into id3
avelad Oct 4, 2022
d3c2087
Add id3 generator for tests
avelad Oct 4, 2022
204960b
Skip Extended Header
avelad Oct 4, 2022
896abcc
Add basic ID3 test
avelad Oct 4, 2022
620e760
Fix lint errors
avelad Oct 5, 2022
9ad25f5
Fix errors
avelad Oct 5, 2022
269a6be
Add all test for Id3Utils
avelad Oct 5, 2022
a16420b
Add test for emsg - https://aomedia.org/emsg/ID3
avelad Oct 5, 2022
abc2841
Fix lint error
avelad Oct 5, 2022
f2ced1d
Add ts_parser_unit.js
avelad Oct 7, 2022
7e114ee
Merge branch 'main' into id3
avelad Oct 7, 2022
02cd1a6
Add tests to ts_parser_unit.js
avelad Oct 7, 2022
6cfddba
Test AAC metadata
avelad Oct 8, 2022
0b9ae39
Fix test
avelad Oct 8, 2022
8811d1c
Test TS metadata
avelad Oct 8, 2022
75c16d3
Fix tests in Firefox
avelad Oct 8, 2022
edcb15c
Simplify ID3 metadata
avelad Oct 8, 2022
cc657cb
Filter wrong characters
avelad Oct 9, 2022
1056bfd
Filter empty metadata
avelad Oct 9, 2022
5d33d1d
Add some asserts
avelad Oct 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/types/core
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
+../../lib/util/functional.js
+../../lib/util/i_destroyable.js
+../../lib/util/i_releasable.js
+../../lib/util/id3_utils.js
+../../lib/util/iterables.js
+../../lib/util/language_utils.js
+../../lib/util/lazy.js
Expand All @@ -101,6 +102,7 @@
+../../lib/util/switch_history.js
+../../lib/util/text_parser.js
+../../lib/util/timer.js
+../../lib/util/ts_parser.js
+../../lib/util/uint8array_utils.js
+../../lib/util/xml_utils.js

Expand Down
43 changes: 1 addition & 42 deletions externs/mux.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,13 @@ muxjs.mp4.Transmuxer = class {
* @typedef {{
* initSegment: !Uint8Array,
* data: !Uint8Array,
* captions: !Array,
* metadata: !Array
* captions: !Array
* }}
*
* @description Transmuxed data from mux.js.
* @property {!Uint8Array} initSegment
* @property {!Uint8Array} data
* @property {!Array} captions
* @property {!Array} metadata
* @exportDoc
*/
muxjs.mp4.Transmuxer.Segment;
Expand Down Expand Up @@ -170,42 +168,3 @@ muxjs.mp4.ParsedClosedCaptions;
*/
muxjs.mp4.ClosedCaption;


/**
* @typedef {{
* cueTime: number,
* data: !Uint8Array,
* dispatchType: string,
* dts: number,
* frames: !Array.<muxjs.mp4.MetadataFrame>,
* pts: number
* }}
*
* @description metadata parsed from mux.js.
* @property {number} cueTime
* @property {number} data
* @property {number} dispatchType
* @property {number} dts
* @property {string} frames
* @property {string} pts
*/
muxjs.mp4.Metadata;


/**
* @typedef {{
* data: string,
* description: string,
* id: string,
* key: string,
* value: string
* }}
*
* @description metadata parsed from mux.js.
* @property {number} data
* @property {number} description
* @property {number} id
* @property {number} key
* @property {string} value
*/
muxjs.mp4.MetadataFrame;
2 changes: 1 addition & 1 deletion externs/shaka/ads.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ shaka.extern.IAdManager = class extends EventTarget {
onHlsTimedMetadata(metadata, timestampOffset) {}

/**
* @param {shaka.extern.ID3Metadata} value
* @param {shaka.extern.MetadataFrame} value
*/
onCueMetadataChange(value) {}
};
Expand Down
47 changes: 45 additions & 2 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,20 +437,63 @@ shaka.extern.DrmSupportType;
*/
shaka.extern.SupportType;


/**
* @typedef {!Object.<string, ?>}
* @typedef {{
* cueTime: ?number,
* data: !Uint8Array,
* frames: !Array.<shaka.extern.MetadataFrame>,
* dts: ?number,
* pts: ?number
* }}
*
* @description
* ID3 metadata in format defined by
* https://id3.org/id3v2.3.0#Declared_ID3v2_frames
* The content of the field.
*
* @property {?number} cueTime
* @property {!Uint8Array} data
* @property {!Array.<shaka.extern.MetadataFrame>} frames
* @property {?number} dts
* @property {?number} pts
*
* @exportDoc
*/
shaka.extern.ID3Metadata;


/**
* @typedef {{
* type: string,
* size: number,
* data: Uint8Array
* }}
*
* @description metadata raw frame.
* @property {string} type
* @property {number} size
* @property {Uint8Array} data
* @exportDoc
*/
shaka.extern.MetadataRawFrame;


/**
* @typedef {{
* key: string,
* data: (ArrayBuffer|string),
* description: string
* }}
*
* @description metadata frame parsed.
* @property {string} key
* @property {ArrayBuffer|string} data
* @property {string} description
* @exportDoc
*/
shaka.extern.MetadataFrame;


/**
* @typedef {{
* schemeIdUri: string,
Expand Down
6 changes: 3 additions & 3 deletions lib/ads/server_side_ad_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ shaka.ads.ServerSideAdManager = class {
}

/**
* @param {shaka.extern.ID3Metadata} value
* @param {shaka.extern.MetadataFrame} value
*/
onCueMetadataChange(value) {
// Native HLS over Safari/iOS/iPadOS
Expand All @@ -246,9 +246,9 @@ shaka.ads.ServerSideAdManager = class {
// done through timed metadata. Timed metadata is carried as part of the
// DAI stream content and carries ad break timing information used by the
// SDK to track ad breaks.
if (value['key'] && value['data']) {
if (value.key && value.data) {
const metadata = {};
metadata[value['key']] = value['data'];
metadata[value.key] = value.data;
this.streamManager_.onTimedMetadata(metadata);
}
}
Expand Down
41 changes: 32 additions & 9 deletions lib/media/media_source_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ goog.require('shaka.media.SegmentReference');
goog.require('shaka.media.TimeRangesUtils');
goog.require('shaka.media.Transmuxer');
goog.require('shaka.text.TextEngine');
goog.require('shaka.util.BufferUtils');
goog.require('shaka.util.Destroyer');
goog.require('shaka.util.Error');
goog.require('shaka.util.EventManager');
goog.require('shaka.util.Functional');
goog.require('shaka.util.IDestroyable');
goog.require('shaka.util.Id3Utils');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.Platform');
goog.require('shaka.util.PublicPromise');
goog.require('shaka.util.TsParser');
goog.require('shaka.lcevc.Dil');


Expand Down Expand Up @@ -568,6 +571,35 @@ shaka.media.MediaSourceEngine = class {
return;
}

const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
if (shaka.util.TsParser.probe(uint8ArrayData)) {
const metadata = new shaka.util.TsParser().parse(uint8ArrayData)
.getMetadata();
if (metadata.length) {
const timestampOffset =
avelad marked this conversation as resolved.
Show resolved Hide resolved
this.sourceBuffers_[contentType].timestampOffset;
this.onMetadata_(metadata, timestampOffset,
reference ? reference.endTime : null);
}
} else {
const containerType = shaka.util.MimeUtils.getContainerType(
this.sourceBufferTypes_[contentType]);
if (containerType === 'aac') {
const frames = shaka.util.Id3Utils.getID3Frames(uint8ArrayData);
avelad marked this conversation as resolved.
Show resolved Hide resolved
if (frames.length && reference) {
/** @private {shaka.extern.ID3Metadata} */
const metadata = {
cueTime: reference.startTime,
data: uint8ArrayData,
frames: frames,
dts: reference.startTime,
pts: reference.startTime,
};
this.onMetadata_([metadata], /* offset= */ 0, reference.endTime);
avelad marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

if (this.transmuxers_[contentType]) {
const transmuxedData =
await this.transmuxers_[contentType].transmux(data);
Expand All @@ -576,15 +608,6 @@ shaka.media.MediaSourceEngine = class {
if (!this.textEngine_) {
this.reinitText('text/vtt', this.sequenceMode_);
}

if (transmuxedData.metadata) {
const timestampOffset =
this.sourceBuffers_[contentType].timestampOffset;
this.onMetadata_(
transmuxedData.metadata,
timestampOffset,
reference ? reference.endTime : null);
}
// This doesn't work for native TS support (ex. Edge/Chromecast),
// since no transmuxing is needed for native TS.
if (transmuxedData.captions && transmuxedData.captions.length) {
Expand Down
22 changes: 21 additions & 1 deletion lib/media/streaming_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ goog.require('shaka.util.Destroyer');
goog.require('shaka.util.Error');
goog.require('shaka.util.FakeEvent');
goog.require('shaka.util.IDestroyable');
goog.require('shaka.util.Id3Utils');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.Mp4Parser');
Expand Down Expand Up @@ -1814,6 +1815,21 @@ shaka.media.StreamingEngine = class {
// A special scheme in DASH used to signal manifest updates.
if (schemeId == 'urn:mpeg:dash:event:2012') {
this.playerInterface_.onManifestUpdate();
} else if (schemeId == 'https://aomedia.org/emsg/ID3') {
// See https://aomediacodec.github.io/id3-emsg/
avelad marked this conversation as resolved.
Show resolved Hide resolved
const frames = shaka.util.Id3Utils.getID3Frames(messageData);
if (frames.length && reference) {
/** @private {shaka.extern.ID3Metadata} */
const metadata = {
cueTime: reference.startTime,
data: messageData,
frames: frames,
dts: reference.startTime,
pts: reference.startTime,
};
this.playerInterface_.onMetadata(
[metadata], /* offset= */ 0, reference.endTime);
}
} else {
/** @type {shaka.extern.EmsgInfo} */
const emsg = {
Expand Down Expand Up @@ -2191,7 +2207,8 @@ shaka.media.StreamingEngine = class {
* !shaka.util.ManifestParserUtils.ContentType),
* onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference),
* beforeAppendSegment: function(
* shaka.util.ManifestParserUtils.ContentType,!BufferSource):Promise
* shaka.util.ManifestParserUtils.ContentType,!BufferSource):Promise,
* onMetadata: !function(!Array.<shaka.extern.ID3Metadata>, number, ?number)
* }}
*
* @property {function():number} getPresentationTime
Expand Down Expand Up @@ -2224,6 +2241,9 @@ shaka.media.StreamingEngine = class {
* @property {!function(shaka.util.ManifestParserUtils.ContentType,
* !BufferSource):Promise} beforeAppendSegment
* A function called just before appending to the source buffer.
* @property
* {!function(!Array.<shaka.extern.ID3Metadata>, number, ?number)} onMetadata
avelad marked this conversation as resolved.
Show resolved Hide resolved
* Called when an ID3 is found in a EMSG.
*/
shaka.media.StreamingEngine.PlayerInterface;

Expand Down
9 changes: 1 addition & 8 deletions lib/media/transmuxer.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ shaka.media.Transmuxer = class {
/** @private {!Array.<muxjs.mp4.ClosedCaption>} */
this.captions_ = [];

/** @private {!Array.<muxjs.mp4.Metadata>} */
this.metadata_ = [];

/** @private {boolean} */
this.isTransmuxing_ = false;

Expand Down Expand Up @@ -152,8 +149,7 @@ shaka.media.Transmuxer = class {
* Transmux from Transport stream to MP4, using the mux.js library.
* @param {BufferSource} data
* @return {!Promise.<{data: !Uint8Array,
* captions: !Array.<!muxjs.mp4.ClosedCaption>,
* metadata: !Array.<!Object>}>}
* captions: !Array.<!muxjs.mp4.ClosedCaption>}>}
*/
transmux(data) {
goog.asserts.assert(!this.isTransmuxing_,
Expand All @@ -162,7 +158,6 @@ shaka.media.Transmuxer = class {
this.transmuxPromise_ = new shaka.util.PublicPromise();
this.transmuxedData_ = [];
this.captions_ = [];
this.metadata_ = [];

const dataArray = shaka.util.BufferUtils.toUint8(data);
this.muxTransmuxer_.push(dataArray);
Expand Down Expand Up @@ -194,7 +189,6 @@ shaka.media.Transmuxer = class {
*/
onTransmuxed_(segment) {
this.captions_ = segment.captions;
this.metadata_ = segment.metadata;
this.transmuxedData_.push(
shaka.util.Uint8ArrayUtils.concat(segment.initSegment, segment.data));
}
Expand All @@ -209,7 +203,6 @@ shaka.media.Transmuxer = class {
const output = {
data: shaka.util.Uint8ArrayUtils.concat(...this.transmuxedData_),
captions: this.captions_,
metadata: this.metadata_,
};

this.transmuxPromise_.resolve(output);
Expand Down
15 changes: 9 additions & 6 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ goog.requireType('shaka.routing.Payload');
* the cue applies.
* @property {string} metadataType
* Type of metadata. Eg: org.id3 or org.mp4ra
* @property {shaka.extern.ID3Metadata} payload
* @property {shaka.extern.MetadataFrame} payload
* The metadata itself
* @exportDoc
*/
Expand Down Expand Up @@ -2688,11 +2688,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
*/
processTimedMetadataMediaSrc_(metadata, offset, segmentEndTime) {
for (const sample of metadata) {
if (sample['data'] && sample['cueTime'] && sample['frames']) {
const start = sample['cueTime'] + offset;
if (sample.data && sample.cueTime && sample.frames) {
const start = sample.cueTime + offset;
const end = segmentEndTime;
const metadataType = 'ID3';
for (const frame of sample['frames']) {
const metadataType = 'org.id3';
for (const frame of sample.frames) {
const payload = frame;
this.dispatchMetadataEvent_(start, end, metadataType, payload);
}
Expand All @@ -2711,7 +2711,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
* @param {number} startTime
* @param {?number} endTime
* @param {string} metadataType
* @param {shaka.extern.ID3Metadata} payload
* @param {shaka.extern.MetadataFrame} payload
* @private
*/
dispatchMetadataEvent_(startTime, endTime, metadataType, payload) {
Expand Down Expand Up @@ -3083,6 +3083,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
beforeAppendSegment: (contentType, segment) => {
return this.drmEngine_.parseInbandPssh(contentType, segment);
},
onMetadata: (metadata, offset, endTime) => {
this.processTimedMetadataMediaSrc_(metadata, offset, endTime);
avelad marked this conversation as resolved.
Show resolved Hide resolved
},
};

return new shaka.media.StreamingEngine(this.manifest_, playerInterface);
Expand Down
Loading