diff --git a/src/core/events/CoreEvents.js b/src/core/events/CoreEvents.js index 5148e263dc..fa686687d3 100644 --- a/src/core/events/CoreEvents.js +++ b/src/core/events/CoreEvents.js @@ -40,6 +40,7 @@ import EventsBase from './EventsBase.js'; class CoreEvents extends EventsBase { constructor () { super(); + this.ALTERNATIVE_EVENT_RECEIVED = 'alternativeEventReceived'; this.ATTEMPT_BACKGROUND_SYNC = 'attemptBackgroundSync'; this.BUFFERING_COMPLETED = 'bufferingCompleted'; this.BUFFER_CLEARED = 'bufferCleared'; diff --git a/src/dash/constants/DashConstants.js b/src/dash/constants/DashConstants.js index 77bfa880cb..7a7d4ce57d 100644 --- a/src/dash/constants/DashConstants.js +++ b/src/dash/constants/DashConstants.js @@ -39,7 +39,10 @@ export default { ADAPTATION_SETS: 'adaptationSets', ADAPTATION_SET_SWITCHING_SCHEME_ID_URI: 'urn:mpeg:dash:adaptation-set-switching:2016', ADD: 'add', - ALTERNATIVE_MPD: 'AlternativeMPD', + ALTERNATIVE_MPD: { + INSERT: 'InsertPresentation', + REPLACE: 'ReplacePresentation', + }, ALTERNATIVE_MPD_SCHEME_ID: 'urn:mpeg:dash:event:alternativeMPD:2022', ASSET_IDENTIFIER: 'AssetIdentifier', AUDIO_CHANNEL_CONFIGURATION: 'AudioChannelConfiguration', @@ -174,6 +177,7 @@ export default { START_NUMBER: 'startNumber', START_WITH_SAP: 'startWithSAP', STATIC: 'static', + STATUS: 'status', SUBSET: 'Subset', SUBTITLE: 'subtitle', SUB_REPRESENTATION: 'SubRepresentation', diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js index c70fac7fd1..7f2132102a 100644 --- a/src/dash/models/DashManifestModel.js +++ b/src/dash/models/DashManifestModel.js @@ -1062,9 +1062,18 @@ function DashManifestModel() { } else { event.id = null; } + if (currentMpdEvent.hasOwnProperty(DashConstants.STATUS)) { + event.status = currentMpdEvent.status; + } else { + event.status = null; + } - if (currentMpdEvent.hasOwnProperty(DashConstants.ALTERNATIVE_MPD)) { - event.alternativeMpd = getAlternativeMpd(currentMpdEvent.AlternativeMPD); + const alternativeMpdKey = Object.keys(DashConstants.ALTERNATIVE_MPD).find(key => + currentMpdEvent.hasOwnProperty(DashConstants.ALTERNATIVE_MPD[key]) + ); + + if (alternativeMpdKey) { + event.alternativeMpd = getAlternativeMpd(currentMpdEvent[DashConstants.ALTERNATIVE_MPD[alternativeMpdKey]], DashConstants.ALTERNATIVE_MPD[alternativeMpdKey]); event.calculatedPresentationTime = 0; } else { event.alternativeMpd = null; @@ -1092,16 +1101,37 @@ function DashManifestModel() { return events; } - function getAlternativeMpd(event) { + function getAlternativeMpd(event, mode) { + if (!mode) { + return + } const alternativeMpd = new AlternativeMpd(); - alternativeMpd.uri = event.uri ?? null; - alternativeMpd.duration = event.duration ?? null; - alternativeMpd.earliestResolutionTimeOffset = event.earliestResolutionTimeOffset / 1000 ?? null; - alternativeMpd.mode = event.mode ?? null; + + getAlternativeMpdCommonData(alternativeMpd, event); + + // Keep to avoid errors with the old signaling alternativeMpd.disableJumpTimeOffest = event.disableJumpTimeOffest ?? null; alternativeMpd.playTimes = event.playTimes ?? null; - alternativeMpd.returnOffset = event.returnOffset ?? null; - return alternativeMpd; + + if (mode === DashConstants.ALTERNATIVE_MPD.INSERT) { + alternativeMpd.mode = Constants.ALTERNATIVE_MPD.MODES.INSERT; + return alternativeMpd; + } + + if (mode === DashConstants.ALTERNATIVE_MPD.REPLACE) { + alternativeMpd.mode = Constants.ALTERNATIVE_MPD.MODES.REPLACE; + alternativeMpd.returnOffset = event.returnOffset ?? null; + alternativeMpd.clip = event.clip ?? null; + alternativeMpd.startAtPlayhead = event.startAtPlayhead ?? null; + return alternativeMpd; + } + } + + function getAlternativeMpdCommonData(alternativeMpd, event) { + alternativeMpd.url = event.url ?? null; + alternativeMpd.earliestResolutionTimeOffset = event.earliestResolutionTimeOffset / 1000 ?? null; + alternativeMpd.serviceDescriptionId = event.serviceDescriptionId; + alternativeMpd.maxDuration = event.maxDuration; } function getEventStreams(inbandStreams, representation, period) { diff --git a/src/dash/vo/AlternativeMpd.js b/src/dash/vo/AlternativeMpd.js index 515f56df14..694444c282 100644 --- a/src/dash/vo/AlternativeMpd.js +++ b/src/dash/vo/AlternativeMpd.js @@ -35,14 +35,21 @@ class AlternativeMpd { constructor() { - this.uri = ''; + this.url = ''; this.earliestResolutionTimeOffset = NaN; this.mode = ''; + this.maxDuration = ''; + this.serviceDescriptionId = NaN; + + // Replace + this.returnOffset = NaN; + this.returnOffset = NaN; + this.clip = true; + this.startAtPlayhead = false; + + // Old attributes this.disableJumpTimeOffest = NaN; this.playTimes = ''; - this.presentationTime = NaN; - this.duration = NaN; - this.returnOffset = NaN; } } diff --git a/src/streaming/constants/Constants.js b/src/streaming/constants/Constants.js index d938857d17..431bf4ed31 100644 --- a/src/streaming/constants/Constants.js +++ b/src/streaming/constants/Constants.js @@ -328,7 +328,11 @@ export default { REPLACE: 'replace', INSERT: 'insert' }, - URI: 'urn:mpeg:dash:event:alternativeMPD:2022' + URIS: { + REPLACE: 'urn:mpeg:dash:event:alternativeMPD:replace:2025', + INSERT: 'urn:mpeg:dash:event:alternativeMPD:insert:2025' + } + }, /** diff --git a/src/streaming/controllers/AlternativeMpdController.js b/src/streaming/controllers/AlternativeMpdController.js index 81b456f306..faaa8628cf 100644 --- a/src/streaming/controllers/AlternativeMpdController.js +++ b/src/streaming/controllers/AlternativeMpdController.js @@ -33,7 +33,6 @@ import MediaPlayerEvents from '../MediaPlayerEvents.js'; import MediaPlayer from '../MediaPlayer.js'; import EventBus from './../../core/EventBus.js'; import FactoryMaker from '../../core/FactoryMaker.js'; -import Constants from '../constants/Constants.js'; /* TODOS: @@ -49,7 +48,6 @@ function AlternativeMpdController() { const eventBus = EventBus(context).getInstance(); let instance, - dashConstants, scheduledEvents = [], eventTimeouts = [], videoModel, @@ -64,7 +62,8 @@ function AlternativeMpdController() { altVideoElement, alternativeContext, isMainDynamic = false, - lastTimestamp = 0; + lastTimestamp = 0, + manifestInfo = {}; function setConfig(config) { if (!config) { @@ -74,13 +73,6 @@ function AlternativeMpdController() { if (!videoModel) { videoModel = config.videoModel; } - // manifestModel = config.manifestModel; - dashConstants = config.DashConstants; - - // if (!altPlayer) { - // let mediaPlayerFactory = config.mediaPlayerFactory; - // altPlayer = mediaPlayerFactory.create(); - // } if (!!config.playbackController && !playbackController) { playbackController = config.playbackController; @@ -110,8 +102,11 @@ function AlternativeMpdController() { function initialize() { eventBus.on(MediaPlayerEvents.MANIFEST_LOADED, _onManifestLoaded, this); + eventBus.on(Events.ALTERNATIVE_EVENT_RECEIVED, _onAlternativeEventeReceived, this); + if (altPlayer) { altPlayer.on(MediaPlayerEvents.MANIFEST_LOADED, _onManifestLoaded, this); + altPlayer.on(Events.ALTERNATIVE_EVENT_RECEIVED, _onAlternativeEventeReceived, this); } document.addEventListener('fullscreenchange', () => { @@ -132,27 +127,29 @@ function AlternativeMpdController() { videoModel.getElement().parentNode.insertBefore(fullscreenDiv, videoModel.getElement()); fullscreenDiv.appendChild(videoModel.getElement()); } - - eventBus.on(Constants.ALTERNATIVE_MPD.URI, _onAlternativeLoad); } - function _onAlternativeLoad(e) { - console.log(e); - console.log('I\'m coming from an alternative based event'); + function _onManifestLoaded(e) { + const manifest = e.data + manifestInfo.type = manifest.type; + manifestInfo.originalUrl = manifest.originalUrl; + + scheduledEvents.forEach((scheduledEvent) => { + if (scheduledEvent.alternativeMPD.url == manifestInfo.originalUrl) { + scheduledEvent.type = manifestInfo.type; + } + }); } - function _onManifestLoaded(e) { - const manifest = e.data; - const events = _parseAlternativeMPDEvents(manifest) + function _onAlternativeEventeReceived(event) { + const alternativeEvent = _parseAlternativeMPDEvent(event) if (scheduledEvents && scheduledEvents.length > 0) { - scheduledEvents.push(...events) + scheduledEvents.push(alternativeEvent) } else { - scheduledEvents = events + scheduledEvents = [alternativeEvent] } - scheduledEvents.forEach((d) => { if (d.alternativeMPD.uri == e.data.originalUrl) { d.type = e.data.type } }); - - switch (manifest.type) { + switch (manifestInfo.type) { case 'dynamic': if (!currentEvent && !altPlayer) { isMainDynamic = true; @@ -234,7 +231,7 @@ function AlternativeMpdController() { function _initializeAlternativePlayer(event) { // Initialize alternative player altPlayer = MediaPlayer().create(); - altPlayer.initialize(altVideoElement, event.alternativeMPD.uri, false, NaN, alternativeContext); + altPlayer.initialize(altVideoElement, event.alternativeMPD.url, false, NaN, alternativeContext); altPlayer.setAutoPlay(false); altPlayer.on(Events.ERROR, (e) => { @@ -242,41 +239,26 @@ function AlternativeMpdController() { }, this); } - function _parseAlternativeMPDEvents(manifest) { - const events = []; - const periods = manifest.Period || []; - - periods.forEach(period => { - const eventStreams = period.EventStream || []; - eventStreams.forEach(eventStream => { - if (eventStream.schemeIdUri === dashConstants.ALTERNATIVE_MPD_SCHEME_ID) { - const timescale = eventStream.timescale || 1; - const eventsArray = eventStream.Event || []; - eventsArray.forEach(ev => { - if (ev && ev.AlternativeMPD) { - const alternativeMPDNode = ev.AlternativeMPD; - const mode = alternativeMPDNode.mode || 'insert'; - const eventObj = { //This should be casted using an AlternativeMpdObject - presentationTime: ev.presentationTime / timescale, - duration: ev.duration / timescale, - alternativeMPD: { - uri: alternativeMPDNode.uri, - earliestResolutionTimeOffset: parseInt(alternativeMPDNode.earliestResolutionTimeOffset || '0', 10) / 1000, - }, - mode: mode, - returnOffset: parseInt(alternativeMPDNode.returnOffset || '0', 10) / 1000, - triggered: false, - watched: false, - type: 'static' - }; - events.push(eventObj); - } - }); - } - }); - }); - - return events; + function _parseAlternativeMPDEvent(event) { + if (event.alternativeMpd) { + const timescale = event.eventStream.timescale || 1; + const alternativeMpdNode = event.alternativeMpd; + const mode = alternativeMpdNode.mode || 'insert'; + + return { + presentationTime: event.presentationTime / timescale, + duration: event.duration, + alternativeMPD: { + url: alternativeMpdNode.url, + earliestResolutionTimeOffset: parseInt(alternativeMpdNode.earliestResolutionTimeOffset || '0', 10) / 1000, + }, + mode: mode, + returnOffset: parseInt(alternativeMpdNode.returnOffset || '0', 10) / 1000, + triggered: false, + watched: false, + type: 'static' + }; + } } function _scheduleAlternativeMPDEvents() { @@ -437,6 +419,7 @@ function AlternativeMpdController() { if (altPlayer) { altPlayer.off(MediaPlayerEvents.MANIFEST_LOADED, _onManifestLoaded, this); + altPlayer.off(Events.ALTERNATIVE_EVENT_RECEIVED, _onAlternativeEventeReceived, this); altPlayer.reset(); altPlayer = null; } @@ -454,6 +437,7 @@ function AlternativeMpdController() { currentEvent = null; eventBus.off(MediaPlayerEvents.MANIFEST_LOADED, _onManifestLoaded, this); + eventBus.off(Events.ALTERNATIVE_EVENT_RECEIVED, _onAlternativeEventeReceived, this); } instance = { diff --git a/src/streaming/controllers/EventController.js b/src/streaming/controllers/EventController.js index e86d5ca149..d5fd9b1911 100644 --- a/src/streaming/controllers/EventController.js +++ b/src/streaming/controllers/EventController.js @@ -34,6 +34,8 @@ import Debug from '../../core/Debug.js'; import EventBus from '../../core/EventBus.js'; import MediaPlayerEvents from '../../streaming/MediaPlayerEvents.js'; import XHRLoader from '../net/XHRLoader.js'; +import Constants from '../constants/Constants.js'; +import Events from '../../core/events/Events.js' function EventController() { @@ -296,6 +298,17 @@ function EventController() { events[schemeIdUri] = []; } + if ( + schemeIdUri === Constants.ALTERNATIVE_MPD.URIS.REPLACE || + schemeIdUri === Constants.ALTERNATIVE_MPD.URIS.INSERT + ) { + // "type" is reserved for the eventBus + delete event.type + eventBus.trigger(Events.ALTERNATIVE_EVENT_RECEIVED, event); + eventState = EVENT_HANDLED_STATES.DISCARDED; + return eventState; + } + const indexOfExistingEvent = events[schemeIdUri].findIndex((e) => { return ((!value || (e.eventStream.value && e.eventStream.value === value)) && (e.id === id)); });