UNPKG

@l5i/dashjs

Version:

A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.

254 lines (222 loc) 9.46 kB
/** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ import FactoryMaker from '../../core/FactoryMaker'; import Debug from '../../core/Debug'; import EventBus from '../../core/EventBus'; import Events from '../../core/events/Events'; function EventController() { const MPD_RELOAD_SCHEME = 'urn:mpeg:dash:event:2012'; const MPD_RELOAD_VALUE = 1; const context = this.context; const eventBus = EventBus(context).getInstance(); let instance, logger, inlineEvents, // Holds all Inline Events not triggered yet inbandEvents, // Holds all Inband Events not triggered yet activeEvents, // Holds all Events currently running eventInterval, // variable holding the setInterval refreshDelay, // refreshTime for the setInterval presentationTimeThreshold, manifestUpdater, playbackController, isStarted; function setup() { logger = Debug(context).getInstance().getLogger(instance); resetInitialSettings(); } function resetInitialSettings() { isStarted = false; inlineEvents = {}; inbandEvents = {}; activeEvents = {}; eventInterval = null; refreshDelay = 100; presentationTimeThreshold = refreshDelay / 1000; } function checkSetConfigCall() { if (!manifestUpdater || !playbackController) { throw new Error('setConfig function has to be called previously'); } } function stop() { if (eventInterval !== null && isStarted) { clearInterval(eventInterval); eventInterval = null; isStarted = false; } } function start() { checkSetConfigCall(); logger.debug('Start Event Controller'); if (!isStarted && !isNaN(refreshDelay)) { isStarted = true; eventInterval = setInterval(onEventTimer, refreshDelay); } } /** * Add events to the eventList. Events that are not in the mpd anymore but not triggered yet will still be deleted * @param {Array.<Object>} values */ function addInlineEvents(values) { checkSetConfigCall(); inlineEvents = {}; if (values) { for (var i = 0; i < values.length; i++) { var event = values[i]; inlineEvents[event.id] = event; logger.debug('Add inline event with id ' + event.id); } } logger.debug('Added ' + values.length + ' inline events'); } /** * i.e. processing of any one event message box with the same id is sufficient * @param {Array.<Object>} values */ function addInbandEvents(values) { checkSetConfigCall(); for (var i = 0; i < values.length; i++) { var event = values[i]; if (!(event.id in inbandEvents)) { if (event.eventStream.schemeIdUri === MPD_RELOAD_SCHEME && inbandEvents[event.id] === undefined) { handleManifestReloadEvent(event); } inbandEvents[event.id] = event; logger.debug('Add inband event with id ' + event.id); } else { logger.debug('Repeated event with id ' + event.id); } } } function handleManifestReloadEvent(event) { if (event.eventStream.value == MPD_RELOAD_VALUE) { const timescale = event.eventStream.timescale || 1; const validUntil = event.presentationTime / timescale; let newDuration; if (event.presentationTime == 0xFFFFFFFF) {//0xFF... means remaining duration unknown newDuration = NaN; } else { newDuration = (event.presentationTime + event.duration) / timescale; } logger.info('Manifest validity changed: Valid until: ' + validUntil + '; remaining duration: ' + newDuration); eventBus.trigger(Events.MANIFEST_VALIDITY_CHANGED, { id: event.id, validUntil: validUntil, newDuration: newDuration, newManifestValidAfter: NaN //event.message_data - this is an arraybuffer with a timestring in it, but not used yet }); } } /** * Remove events which are over from the list */ function removeEvents() { if (activeEvents) { var currentVideoTime = playbackController.getTime(); var eventIds = Object.keys(activeEvents); for (var i = 0; i < eventIds.length; i++) { var eventId = eventIds[i]; var curr = activeEvents[eventId]; if (curr !== null && (curr.duration + curr.presentationTime) / curr.eventStream.timescale < currentVideoTime) { logger.debug('Remove Event ' + eventId + ' at time ' + currentVideoTime); curr = null; delete activeEvents[eventId]; } } } } /** * Iterate through the eventList and trigger/remove the events */ function onEventTimer() { triggerEvents(inbandEvents); triggerEvents(inlineEvents); removeEvents(); } function refreshManifest() { checkSetConfigCall(); manifestUpdater.refreshManifest(); } function triggerEvents(events) { var currentVideoTime = playbackController.getTime(); var presentationTime; /* == Trigger events that are ready == */ if (events) { var eventIds = Object.keys(events); for (var i = 0; i < eventIds.length; i++) { var eventId = eventIds[i]; var curr = events[eventId]; if (curr !== undefined) { presentationTime = curr.presentationTime / curr.eventStream.timescale; if (presentationTime === 0 || (presentationTime <= currentVideoTime && presentationTime + presentationTimeThreshold > currentVideoTime)) { logger.debug('Start Event ' + eventId + ' at ' + currentVideoTime); if (curr.duration > 0) { activeEvents[eventId] = curr; } if (curr.eventStream.schemeIdUri == MPD_RELOAD_SCHEME && curr.eventStream.value == MPD_RELOAD_VALUE) { if (curr.duration !== 0 || curr.presentationTimeDelta !== 0) { //If both are set to zero, it indicates the media is over at this point. Don't reload the manifest. refreshManifest(); } } else { eventBus.trigger(curr.eventStream.schemeIdUri, {event: curr}); } delete events[eventId]; } } } } } function setConfig(config) { if (!config) return; if (config.manifestUpdater) { manifestUpdater = config.manifestUpdater; } if (config.playbackController) { playbackController = config.playbackController; } } function reset() { stop(); resetInitialSettings(); } instance = { addInlineEvents: addInlineEvents, addInbandEvents: addInbandEvents, stop: stop, start: start, setConfig: setConfig, reset: reset }; setup(); return instance; } EventController.__dashjs_factory_name = 'EventController'; export default FactoryMaker.getClassFactory(EventController);