diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 807c215d87..56fefab2bb 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -1982,7 +1982,6 @@ shaka.media.StreamingEngine = class { const messageData = box.reader.readBytes( box.reader.getLength() - box.reader.getPosition()); - // See DASH sec. 5.10.3.3.1 // If a DASH client detects an event message box with a scheme that is not // defined in MPD, the client is expected to ignore it. @@ -1992,23 +1991,8 @@ 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' || - schemeId == 'https://developer.apple.com/streaming/emsg-id3') { - // See https://aomediacodec.github.io/id3-emsg/ - 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 { + // All other schemes are dispatched as a general 'emsg' event. /** @type {shaka.extern.EmsgInfo} */ const emsg = { startTime: startTime, @@ -2026,7 +2010,38 @@ shaka.media.StreamingEngine = class { const eventName = shaka.util.FakeEvent.EventName.Emsg; const data = (new Map()).set('detail', emsg); const event = new shaka.util.FakeEvent(eventName, data); + // A user can call preventDefault() on a cancelable event. + event.cancelable = true; + this.playerInterface_.onEvent(event); + + if (event.defaultPrevented) { + // If the caller uses preventDefault() on the 'emsg' event, don't + // process any further, and don't generate an ID3 'metadata' event + // for the same data. + return; + } + + // Additionally, ID3 events generate a 'metadata' event. This is a + // pre-parsed version of the metadata blob already dispatched in the + // 'emsg' event. + if (schemeId == 'https://aomedia.org/emsg/ID3' || + schemeId == 'https://developer.apple.com/streaming/emsg-id3') { + // See https://aomediacodec.github.io/id3-emsg/ + 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); + } + } } } } diff --git a/lib/player.js b/lib/player.js index 919c1fbbf3..22851c1368 100644 --- a/lib/player.js +++ b/lib/player.js @@ -99,7 +99,9 @@ goog.requireType('shaka.routing.Payload'); /** * @event shaka.Player.EmsgEvent - * @description Fired when a non-typical emsg is found in a segment. + * @description Fired when an emsg box is found in a segment. + * If the application calls preventDefault() on this event, further parsing + * will not happen, and no 'metadata' event will be raised for ID3 payloads. * @property {string} type * 'emsg' * @property {shaka.extern.EmsgInfo} detail diff --git a/test/media/streaming_engine_unit.js b/test/media/streaming_engine_unit.js index 492b6c55e3..5ba3e58cc2 100644 --- a/test/media/streaming_engine_unit.js +++ b/test/media/streaming_engine_unit.js @@ -3130,10 +3130,14 @@ describe('StreamingEngine', () => { expect(onManifestUpdate).toHaveBeenCalled(); }); - it('triggers metadata event', async () => { + it('triggers both emsg event and metadata event for ID3', async () => { setSegment0(emsgSegmentV0ID3); videoStream.emsgSchemeIdUris = [id3SchemeUri]; + onEvent.and.callFake((emsgEvent) => { + expect(emsgEvent.type).toBe('emsg'); + }); + // Here we go! streamingEngine.switchVariant(variant); streamingEngine.switchTextStream(textStream); @@ -3141,10 +3145,30 @@ describe('StreamingEngine', () => { playing = true; await runTest(); - expect(onEvent).not.toHaveBeenCalled(); + expect(onEvent).toHaveBeenCalled(); expect(onMetadata).toHaveBeenCalled(); }); + it('only triggers emsg event for ID3 if event canceled', async () => { + setSegment0(emsgSegmentV0ID3); + videoStream.emsgSchemeIdUris = [id3SchemeUri]; + + onEvent.and.callFake((emsgEvent) => { + expect(emsgEvent.type).toBe('emsg'); + emsgEvent.preventDefault(); + }); + + // Here we go! + streamingEngine.switchVariant(variant); + streamingEngine.switchTextStream(textStream); + await streamingEngine.start(); + playing = true; + await runTest(); + + expect(onEvent).toHaveBeenCalled(); + expect(onMetadata).not.toHaveBeenCalled(); + }); + it('event start matches presentation time', async () => { // This box has a non-zero event time, which doesn't matter. setSegment0(emsgSegmentV1NonZeroStart);