diff --git a/dist/dash.all.debug.js b/dist/dash.all.debug.js index ff60d1d9dd..3935fca569 100644 --- a/dist/dash.all.debug.js +++ b/dist/dash.all.debug.js @@ -16101,7 +16101,7 @@ var _streamingConstantsConstants = _dereq_(108); var _streamingConstantsConstants2 = _interopRequireDefault(_streamingConstantsConstants); -var _streamingVoMetricsHTTPRequest = _dereq_(242); +var _streamingVoMetricsHTTPRequest = _dereq_(241); /** @module Settings * @description Define the configuration parameters of Dash.js MediaPlayer. @@ -16155,7 +16155,8 @@ var _streamingVoMetricsHTTPRequest = _dereq_(242); * IndexSegment: 1000, * MediaSegment: 1000, * BitstreamSwitchingSegment: 1000, - * other: 1000 + * other: 1000, + * lowLatencyReductionFactor: 10 * }, * retryAttempts: { * MPD: 3, @@ -16164,7 +16165,8 @@ var _streamingVoMetricsHTTPRequest = _dereq_(242); * IndexSegment: 3, * MediaSegment: 3, * BitstreamSwitchingSegment: 3, - * other: 3 + * other: 3, + * lowLatencyMultiplyFactor: 5 * }, * abr: { * movingAverageMethod: Constants.MOVING_AVERAGE_SLIDING_WINDOW, @@ -16361,13 +16363,22 @@ var _streamingVoMetricsHTTPRequest = _dereq_(242); * * Note: Catch-up mechanism is only applied when playing low latency live streams. * @property {number} [liveCatchUpPlaybackRate=0.5] - * Use this method to set the maximum catch up rate, as a percentage, for low latency live streams. In low latency mode, + * Use this parameter to set the maximum catch up rate, as a percentage, for low latency live streams. In low latency mode, * when measured latency is higher/lower than the target one, * dash.js increases/decreases playback rate respectively up to (+/-) the percentage defined with this method until target is reached. * * Valid values for catch up rate are in range 0-0.5 (0-50%). Set it to 0 to turn off live catch up feature. * * Note: Catch-up mechanism is only applied when playing low latency live streams. + * @property {number} [liveCatchupLatencyThreshold=NaN] + * Use this parameter to set the maximum threshold for which live catch up is applied. For instance, if this value is set to 8 seconds, + * then live catchup is only applied if the current live latency is equal or below 8 seconds. The reason behind this parameter is to avoid an increase + * of the playback rate if the user seeks within the DVR window. + * + * If no value is specified this will be twice the maximum live delay. The maximum live delay is either specified in the manifest as part of a ServiceDescriptor or calculated the following: + * maximumLiveDelay = targetDelay + liveCatchupMinDrift + * + * Note: Catch-up mechanism is only applied when playing low latency live streams. * @property {module:Settings~CachingInfoSettings} [lastBitrateCachingInfo={enabled: true, ttl: 360000}] * Set to false if you would like to disable the last known bit rate from being stored during playback and used * to set the initial bit rate for subsequent playback within the expiration window. @@ -16383,8 +16394,8 @@ var _streamingVoMetricsHTTPRequest = _dereq_(242); * @property {module:Settings~AudioVideoSettings} [cacheLoadThresholds={video: 50, audio: 5}] * For a given media type, the threshold which defines if the response to a fragment * request is coming from browser cache or not. - * @property {module:Settings~RequestTypeSettings} [retryIntervals] Time in milliseconds of which to reload a failed file load attempt. - * @property {module:Settings~RequestTypeSettings} [retryAttempts] Total number of retry attempts that will occur on a file load before it fails. + * @property {module:Settings~RequestTypeSettings} [retryIntervals] Time in milliseconds of which to reload a failed file load attempt. For low latency mode these values are divided by lowLatencyReductionFactor. + * @property {module:Settings~RequestTypeSettings} [retryAttempts] Total number of retry attempts that will occur on a file load before it fails. For low latency mode these values are multiplied by lowLatencyMultiplyFactor. * @property {module:Settings~AbrSettings} abr Adaptive Bitrate algorithm related settings. * @property {module:Settings~CmcdSettings} cmcd Settings related to Common Media Client Data reporting. */ @@ -16464,11 +16475,12 @@ function Settings() { liveCatchUpMinDrift: 0.02, liveCatchUpMaxDrift: 0, liveCatchUpPlaybackRate: 0.5, + liveCatchupLatencyThreshold: NaN, lastBitrateCachingInfo: { enabled: true, ttl: 360000 }, lastMediaSettingsCachingInfo: { enabled: true, ttl: 360000 }, cacheLoadThresholds: { video: 50, audio: 5 }, - retryIntervals: (_retryIntervals = {}, _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.MPD_TYPE, 500), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.XLINK_EXPANSION_TYPE, 500), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.MEDIA_SEGMENT_TYPE, 1000), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.INIT_SEGMENT_TYPE, 1000), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.BITSTREAM_SWITCHING_SEGMENT_TYPE, 1000), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.INDEX_SEGMENT_TYPE, 1000), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.OTHER_TYPE, 1000), _retryIntervals), - retryAttempts: (_retryAttempts = {}, _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.MPD_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.XLINK_EXPANSION_TYPE, 1), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.MEDIA_SEGMENT_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.INIT_SEGMENT_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.BITSTREAM_SWITCHING_SEGMENT_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.INDEX_SEGMENT_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.OTHER_TYPE, 3), _retryAttempts), + retryIntervals: (_retryIntervals = {}, _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.MPD_TYPE, 500), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.XLINK_EXPANSION_TYPE, 500), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.MEDIA_SEGMENT_TYPE, 1000), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.INIT_SEGMENT_TYPE, 1000), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.BITSTREAM_SWITCHING_SEGMENT_TYPE, 1000), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.INDEX_SEGMENT_TYPE, 1000), _defineProperty(_retryIntervals, _streamingVoMetricsHTTPRequest.HTTPRequest.OTHER_TYPE, 1000), _defineProperty(_retryIntervals, 'lowLatencyReductionFactor', 10), _retryIntervals), + retryAttempts: (_retryAttempts = {}, _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.MPD_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.XLINK_EXPANSION_TYPE, 1), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.MEDIA_SEGMENT_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.INIT_SEGMENT_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.BITSTREAM_SWITCHING_SEGMENT_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.INDEX_SEGMENT_TYPE, 3), _defineProperty(_retryAttempts, _streamingVoMetricsHTTPRequest.HTTPRequest.OTHER_TYPE, 3), _defineProperty(_retryAttempts, 'lowLatencyMultiplyFactor', 5), _retryAttempts), abr: { movingAverageMethod: _streamingConstantsConstants2['default'].MOVING_AVERAGE_SLIDING_WINDOW, ABRStrategy: _streamingConstantsConstants2['default'].ABR_STRATEGY_DYNAMIC, @@ -16562,7 +16574,7 @@ var factory = _FactoryMaker2['default'].getSingletonFactory(Settings); exports['default'] = factory; module.exports = exports['default']; -},{"108":108,"242":242,"45":45,"47":47,"49":49}],49:[function(_dereq_,module,exports){ +},{"108":108,"241":241,"45":45,"47":47,"49":49}],49:[function(_dereq_,module,exports){ /** * 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 @@ -16726,7 +16738,7 @@ Object.defineProperty(exports, '__esModule', { value: true }); exports.getVersionString = getVersionString; -var VERSION = '3.1.1'; +var VERSION = '3.1.2'; function getVersionString() { return VERSION; @@ -17030,7 +17042,7 @@ var CoreEvents = (function (_EventsBase) { this.INBAND_EVENTS = 'inbandEvents'; this.INITIALIZATION_LOADED = 'initializationLoaded'; this.INIT_FRAGMENT_LOADED = 'initFragmentLoaded'; - this.INIT_REQUESTED = 'initRequested'; + this.INIT_FRAGMENT_NEEDED = 'initFragmentNeeded'; this.INTERNAL_MANIFEST_LOADED = 'internalManifestLoaded'; this.ORIGINAL_MANIFEST_LOADED = 'originalManifestLoaded'; this.LIVE_EDGE_SEARCH_COMPLETED = 'liveEdgeSearchCompleted'; @@ -17040,6 +17052,7 @@ var CoreEvents = (function (_EventsBase) { this.LOADING_ABANDONED = 'loadingAborted'; this.MANIFEST_UPDATED = 'manifestUpdated'; this.MEDIA_FRAGMENT_LOADED = 'mediaFragmentLoaded'; + this.MEDIA_FRAGMENT_NEEDED = 'mediaFragmentNeeded'; this.QUOTA_EXCEEDED = 'quotaExceeded'; this.REPRESENTATION_UPDATE_STARTED = 'representationUpdateStarted'; this.REPRESENTATION_UPDATE_COMPLETED = 'representationUpdateCompleted'; @@ -17051,7 +17064,6 @@ var CoreEvents = (function (_EventsBase) { this.STREAM_BUFFERING_COMPLETED = 'streamBufferingCompleted'; this.STREAM_COMPLETED = 'streamCompleted'; this.TEXT_TRACKS_QUEUE_INITIALIZED = 'textTracksQueueInitialized'; - this.TIMED_TEXT_REQUESTED = 'timedTextRequested'; this.TIME_SYNCHRONIZATION_COMPLETED = 'timeSynchronizationComplete'; this.URL_RESOLUTION_FAILED = 'urlResolutionFailed'; this.VIDEO_CHUNK_RECEIVED = 'videoChunkReceived'; @@ -17060,6 +17072,8 @@ var CoreEvents = (function (_EventsBase) { this.XLINK_READY = 'xlinkReady'; this.SEGMENTBASE_INIT_REQUEST_NEEDED = 'segmentBaseInitRequestNeeded'; this.SEGMENTBASE_SEGMENTSLIST_REQUEST_NEEDED = 'segmentBaseSegmentsListRequestNeeded'; + this.SEEK_TARGET = 'seekTarget'; + this.DYNAMIC_STREAM_COMPLETED = 'dynamicStreamCompleted'; } return CoreEvents; @@ -17281,6 +17295,10 @@ var _modelsDashManifestModel = _dereq_(65); var _modelsDashManifestModel2 = _interopRequireDefault(_modelsDashManifestModel); +/** + * @module DashAdapter + */ + function DashAdapter() { var instance = undefined, dashManifestModel = undefined, @@ -17329,6 +17347,14 @@ function DashAdapter() { } } + /** + * Creates an instance of RepresentationInfo based on a representation value object + * @param {object} voRepresentation + * @returns {RepresentationInfo|null} representationInfo + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function convertRepresentationToRepresentationInfo(voRepresentation) { if (voRepresentation) { var representationInfo = new _voRepresentationInfo2['default'](); @@ -17350,6 +17376,14 @@ function DashAdapter() { } } + /** + * Returns a MediaInfo object for a given media type. + * @param {object} streamInfo + * @param {MediaType }type + * @returns {null|MediaInfo} mediaInfo + * @memberOf module:DashAdapter + * @instance + */ function getMediaInfoForType(streamInfo, type) { if (voPeriods.length === 0 || !streamInfo) { return null; @@ -17368,12 +17402,28 @@ function DashAdapter() { return convertAdaptationToMediaInfo(voAdaptations[periodId][idx]); } + /** + * Checks if the role of the specified AdaptationSet is set to main + * @param {object} adaptation + * @returns {boolean} + * @memberOf module:DashAdapter + * @instance + */ function getIsMain(adaptation) { return dashManifestModel.getRolesForAdaptation(adaptation).filter(function (role) { return role.value === _constantsDashConstants2['default'].MAIN; })[0]; } + /** + * Returns the AdaptationSet for a given period and a given mediaType. + * @param {number} periodIndex + * @param {MediaType} type + * @param {object} streamInfo + * @returns {null|object} adaptation + * @memberOf module:DashAdapter + * @instance + */ function getAdaptationForType(periodIndex, type, streamInfo) { var adaptations = dashManifestModel.getAdaptationsForType(voPeriods[0].mpd.manifest, periodIndex, type); @@ -17400,6 +17450,15 @@ function DashAdapter() { return adaptations[0]; } + /** + * Returns the mediaInfo for a given mediaType + * @param {object} streamInfo + * @param {MediaType} type + * @param {object} externalManifest Set to null or undefined if no external manifest is to be used + * @returns {Array} mediaArr + * @memberOf module:DashAdapter + * @instance + */ function getAllMediaInfoForType(streamInfo, type, externalManifest) { var voLocalPeriods = voPeriods; var manifest = externalManifest; @@ -17488,6 +17547,13 @@ function DashAdapter() { return mediaArr; } + /** + * @param {object} newManifest + * @returns {*} + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function updatePeriods(newManifest) { if (!newManifest) return null; @@ -17498,6 +17564,14 @@ function DashAdapter() { voAdaptations = {}; } + /** + * @param {object} externalManifest + * @param {number} maxStreamsInfo + * @returns {Array} streams + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function getStreamsInfo(externalManifest, maxStreamsInfo) { var streams = []; var voLocalPeriods = voPeriods; @@ -17520,6 +17594,14 @@ function DashAdapter() { return streams; } + /** + * + * @param {object} streamInfo + * @param {object} mediaInfo + * @returns {object} realAdaptation + * @memberOf module:DashAdapter + * @instance + */ function getRealAdaptation(streamInfo, mediaInfo) { var id = undefined, realAdaptation = undefined; @@ -17535,6 +17617,13 @@ function DashAdapter() { return realAdaptation; } + /** + * Returns all voRepresentations for a given mediaInfo + * @param {object} mediaInfo + * @returns {Array} voReps + * @memberOf module:DashAdapter + * @instance + */ function getVoRepresentations(mediaInfo) { var voReps = undefined; @@ -17544,6 +17633,16 @@ function DashAdapter() { return voReps; } + /** + * + * @param {object} eventBox + * @param {Array} eventStreams + * @param {number} startTime + * @returns {null|Event} + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function getEvent(eventBox, eventStreams, startTime) { if (!eventBox || !eventStreams) { return null; @@ -17572,6 +17671,15 @@ function DashAdapter() { return event; } + /** + * + * @param {object} info + * @param {object} voRepresentation + * @returns {Array} + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function getEventsFor(info, voRepresentation) { var events = []; @@ -17590,82 +17698,209 @@ function DashAdapter() { return events; } + /** + * + * @param {number} streamId + * @param {MediaType} type + * @param {object} mediaInfo + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function setCurrentMediaInfo(streamId, type, mediaInfo) { currentMediaInfo[streamId] = currentMediaInfo[streamId] || {}; currentMediaInfo[streamId][type] = currentMediaInfo[streamId][type] || {}; currentMediaInfo[streamId][type] = mediaInfo; } + /** + * + * @param {String} type + * @returns {boolean} + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function getIsTextTrack(type) { return dashManifestModel.getIsTextTrack(type); } + /** + * Returns the UTC Timing Sources specified in the manifest + * @returns {Array} utcTimingSources + * @memberOf module:DashAdapter + * @instance + */ function getUTCTimingSources() { var manifest = getManifest(); return dashManifestModel.getUTCTimingSources(manifest); } + /** + * Returns the suggestedPresentationDelay as specified in the manifest + * @returns {String} suggestedPresentationDelay + * @memberOf module:DashAdapter + * @instance + */ function getSuggestedPresentationDelay() { var mpd = voPeriods.length > 0 ? voPeriods[0].mpd : null; return dashManifestModel.getSuggestedPresentationDelay(mpd); } + /** + * Returns the availabilityStartTime as specified in the manifest + * @param {object} externalManifest Omit this value if no external manifest should be used + * @returns {string} availabilityStartTime + * @memberOf module:DashAdapter + * @instance + */ function getAvailabilityStartTime(externalManifest) { var mpd = getMpd(externalManifest); return dashManifestModel.getAvailabilityStartTime(mpd); } + /** + * Returns a boolean indicating if the manifest is dynamic or not + * @param {object} externalManifest Omit this value if no external manifest should be used + * @returns {boolean} + * @memberOf module:DashAdapter + * @instance + */ function getIsDynamic(externalManifest) { var manifest = getManifest(externalManifest); return dashManifestModel.getIsDynamic(manifest); } + /** + * Returns the duration of the MPD + * @param {object} externalManifest Omit this value if no external manifest should be used + * @returns {number} duration + * @memberOf module:DashAdapter + * @instance + */ function getDuration(externalManifest) { var manifest = getManifest(externalManifest); return dashManifestModel.getDuration(manifest); } + /** + * Returns all periods of the MPD + * @param {object} externalManifest Omit this value if no external manifest should be used + * @returns {Array} periods + * @memberOf module:DashAdapter + * @instance + */ function getRegularPeriods(externalManifest) { var mpd = getMpd(externalManifest); return dashManifestModel.getRegularPeriods(mpd); } + /** + * Returns an MPD object + * @param {object} externalManifest Omit this value if no external manifest should be used + * @returns {object} MPD + * @memberOf module:DashAdapter + * @instance + */ function getMpd(externalManifest) { var manifest = getManifest(externalManifest); return dashManifestModel.getMpd(manifest); } + /** + * Returns the location element of the MPD + * @param {object} manifest + * @returns {String} location + * @memberOf module:DashAdapter + * @instance + */ function getLocation(manifest) { return dashManifestModel.getLocation(manifest); } + /** + * Returns the manifest update period used for dynamic manifests + * @param {object} manifest + * @param {number} latencyOfLastUpdate + * @returns {NaN|number} manifestUpdatePeriod + * @memberOf module:DashAdapter + * @instance + */ function getManifestUpdatePeriod(manifest) { var latencyOfLastUpdate = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; return dashManifestModel.getManifestUpdatePeriod(manifest, latencyOfLastUpdate); } + /** + * + * @param {object} mediaInfo + * @returns {boolean} + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function getUseCalculatedLiveEdgeTimeForMediaInfo(mediaInfo) { var voAdaptation = getAdaptationForMediaInfo(mediaInfo); return dashManifestModel.getUseCalculatedLiveEdgeTimeForAdaptation(voAdaptation); } + /** + * Checks if the manifest has a DVB profile + * @param {object} manifest + * @returns {boolean} + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function getIsDVB(manifest) { return dashManifestModel.hasProfile(manifest, PROFILE_DVB); } + /** + * + * @param {object} node + * @returns {Array} + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function getBaseURLsFromElement(node) { return dashManifestModel.getBaseURLsFromElement(node); } + /** + * + * @returns {*} + * @memberOf module:DashAdapter + * @instance + * @ignore + */ function getRepresentationSortFunction() { return dashManifestModel.getRepresentationSortFunction(); } + /** + * Returns the codec for a given adaptation set and a given representation id. + * @param {object} adaptation + * @param {number} representationId + * @param {boolean} addResolutionInfo Defines whether to include resolution information in the output + * @returns {String} codec + * @memberOf module:DashAdapter + * @instance + */ function getCodec(adaptation, representationId, addResolutionInfo) { return dashManifestModel.getCodec(adaptation, representationId, addResolutionInfo); } + /** + * Returns the bandwidth for a given representation id + * @param {number} representationId + * @param {number} periodId + * @returns {number} bandwidth + * @memberOf module:DashAdapter + * @instance + */ function getBandwidthForRepresentation(representationId, periodId) { var representation = undefined; var period = getPeriod(periodId); @@ -17676,10 +17911,12 @@ function DashAdapter() { } /** - * + * Returns the index for a given representation id * @param {string} representationId * @param {number} periodIdx - * @returns {*} + * @returns {number} index + * @memberOf module:DashAdapter + * @instance */ function getIndexForRepresentation(representationId, periodIdx) { var period = getPeriod(periodIdx); @@ -17707,6 +17944,7 @@ function DashAdapter() { voAdaptations = {}; currentMediaInfo = {}; } + // #endregion PUBLIC FUNCTIONS // #region PRIVATE FUNCTIONS @@ -17833,6 +18071,7 @@ function DashAdapter() { manifestInfo.duration = dashManifestModel.getDuration(mpd.manifest); manifestInfo.isDynamic = dashManifestModel.getIsDynamic(mpd.manifest); manifestInfo.serviceDescriptions = dashManifestModel.getServiceDescriptions(mpd.manifest); + manifestInfo.protocol = mpd.manifest.protocol; return manifestInfo; } @@ -17901,6 +18140,7 @@ function DashAdapter() { return -1; } + // #endregion PRIVATE FUNCTIONS instance = { @@ -17986,11 +18226,11 @@ Object.defineProperty(exports, '__esModule', { function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -var _streamingVoFragmentRequest = _dereq_(228); +var _streamingVoFragmentRequest = _dereq_(227); var _streamingVoFragmentRequest2 = _interopRequireDefault(_streamingVoFragmentRequest); -var _streamingVoMetricsHTTPRequest = _dereq_(242); +var _streamingVoMetricsHTTPRequest = _dereq_(241); var _coreFactoryMaker = _dereq_(47); @@ -18026,6 +18266,7 @@ function DashHandler(config) { requestedTime = undefined, currentTime = undefined, isDynamicManifest = undefined, + dynamicStreamCompleted = undefined, selectedMimeType = undefined, segmentsController = undefined; @@ -18038,10 +18279,12 @@ function DashHandler(config) { eventBus.on(events.INITIALIZATION_LOADED, onInitializationLoaded, instance); eventBus.on(events.SEGMENTS_LOADED, onSegmentsLoaded, instance); eventBus.on(events.REPRESENTATION_UPDATE_STARTED, onRepresentationUpdateStarted, instance); + eventBus.on(events.DYNAMIC_STREAM_COMPLETED, onDynamicStreamCompleted, instance); } function initialize(isDynamic) { isDynamicManifest = isDynamic; + dynamicStreamCompleted = false; segmentsController.initialize(isDynamic); } @@ -18088,6 +18331,7 @@ function DashHandler(config) { eventBus.off(events.INITIALIZATION_LOADED, onInitializationLoaded, instance); eventBus.off(events.SEGMENTS_LOADED, onSegmentsLoaded, instance); eventBus.off(events.REPRESENTATION_UPDATE_STARTED, onRepresentationUpdateStarted, instance); + eventBus.off(events.DYNAMIC_STREAM_COMPLETED, onDynamicStreamCompleted, instance); } function setRequestUrl(request, destination, representation) { @@ -18216,7 +18460,9 @@ function DashHandler(config) { isFinished = true; } } else { - if (lastSegment) { + if (dynamicStreamCompleted) { + isFinished = true; + } else if (lastSegment) { var time = parseFloat((lastSegment.presentationStartTime - representation.adaptation.period.start).toFixed(5)); var endTime = lastSegment.duration > 0 ? time + 1.5 * lastSegment.duration : time; var duration = representation.adaptation.period.duration; @@ -18284,7 +18530,7 @@ function DashHandler(config) { // check that there is a segment in this index var segment = segmentsController.getSegmentByIndex(representation, indexToRequest, lastSegment ? lastSegment.mediaStartTime : -1); - if (!segment && isEndlessMedia(representation)) { + if (!segment && isEndlessMedia(representation) && !dynamicStreamCompleted) { logger.debug('No segment found at index: ' + indexToRequest + '. Wait for next loop'); return null; } else { @@ -18373,6 +18619,11 @@ function DashHandler(config) { eventBus.trigger(events.REPRESENTATION_UPDATE_COMPLETED, { sender: this, representation: representation }); } + function onDynamicStreamCompleted() { + logger.debug('Dynamic stream complete'); + dynamicStreamCompleted = true; + } + instance = { initialize: initialize, getType: getType, //need to be public in order to be used by logger @@ -18400,7 +18651,7 @@ DashHandler.__dashjs_factory_name = 'DashHandler'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(DashHandler); module.exports = exports['default']; -},{"228":228,"242":242,"47":47,"64":64,"80":80}],58:[function(_dereq_,module,exports){ +},{"227":227,"241":241,"47":47,"64":64,"80":80}],58:[function(_dereq_,module,exports){ /** * 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 @@ -18443,7 +18694,7 @@ var _streamingConstantsConstants = _dereq_(108); var _streamingConstantsConstants2 = _interopRequireDefault(_streamingConstantsConstants); -var _streamingVoMetricsHTTPRequest = _dereq_(242); +var _streamingVoMetricsHTTPRequest = _dereq_(241); var _coreFactoryMaker = _dereq_(47); @@ -18461,11 +18712,10 @@ var _streamingModelsMetricsModel = _dereq_(152); var _streamingModelsMetricsModel2 = _interopRequireDefault(_streamingModelsMetricsModel); -var _streamingVoMetricsPlayList = _dereq_(244); +var _streamingVoMetricsPlayList = _dereq_(243); /** * @module DashMetrics - * @ignore * @param {object} config */ @@ -18493,7 +18743,7 @@ function DashMetrics(config) { } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @returns {*} * @memberof module:DashMetrics * @instance @@ -18504,21 +18754,23 @@ function DashMetrics(config) { } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @param {Date} t time of the switch event * @param {Date} mt media presentation time * @param {string} to id of representation * @param {string} lto if present, subrepresentation reference * @memberof module:DashMetrics * @instance + * @ignore */ function addRepresentationSwitch(mediaType, t, mt, to, lto) { metricsModel.addRepresentationSwitch(mediaType, t, mt, to, lto); } /** - * @param {string} type + * @param {MediaType} type * @returns {number} + * @memberof module:DashMetrics * @instance */ function getCurrentBufferState(type) { @@ -18527,7 +18779,7 @@ function DashMetrics(config) { } /** - * @param {string} type + * @param {MediaType} type * @returns {number} * @memberof module:DashMetrics * @instance @@ -18544,22 +18796,24 @@ function DashMetrics(config) { } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @param {number} t * @param {number} level * @memberof module:DashMetrics * @instance + * @ignore */ function addBufferLevel(mediaType, t, level) { metricsModel.addBufferLevel(mediaType, t, level); } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @param {string} state * @param {number} target * @memberof module:DashMetrics * @instance + * @ignore */ function addBufferState(mediaType, state, target) { metricsModel.addBufferState(mediaType, state, target); @@ -18568,13 +18822,14 @@ function DashMetrics(config) { /** * @memberof module:DashMetrics * @instance + * @ignore */ function clearAllCurrentMetrics() { metricsModel.clearAllCurrentMetrics(); } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @returns {*} * @memberof module:DashMetrics * @instance @@ -18608,7 +18863,7 @@ function DashMetrics(config) { } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @returns {*} * @memberof module:DashMetrics * @instance @@ -18623,11 +18878,12 @@ function DashMetrics(config) { } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @param {Array} loadingRequests * @param {Array} executedRequests * @memberof module:DashMetrics * @instance + * @ignore */ function addRequestsQueue(mediaType, loadingRequests, executedRequests) { metricsModel.addRequestsQueue(mediaType, loadingRequests, executedRequests); @@ -18652,6 +18908,7 @@ function DashMetrics(config) { * @returns {*} * @memberof module:DashMetrics * @instance + * @ignore */ function getCurrentDroppedFrames() { var metrics = metricsModel.getMetricsFor(_streamingConstantsConstants2['default'].VIDEO, true); @@ -18662,13 +18919,14 @@ function DashMetrics(config) { * @param {number} quality * @memberof module:DashMetrics * @instance + * @ignore */ function addDroppedFrames(quality) { metricsModel.addDroppedFrames(_streamingConstantsConstants2['default'].VIDEO, quality); } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @returns {*} * @memberof module:DashMetrics * @instance @@ -18683,6 +18941,7 @@ function DashMetrics(config) { * @param {string} state * @memberof module:DashMetrics * @instance + * @ignore */ function addSchedulingInfo(request, state) { metricsModel.addSchedulingInfo(request.mediaType, new Date(), request.type, request.startTime, request.availabilityStartTime, request.duration, request.quality, request.range, state); @@ -18702,6 +18961,7 @@ function DashMetrics(config) { * @param {object} updatedFields fields to be updated * @memberof module:DashMetrics * @instance + * @ignore */ function updateManifestUpdateInfo(updatedFields) { var manifestUpdate = this.getCurrentManifestUpdate(); @@ -18712,6 +18972,7 @@ function DashMetrics(config) { * @param {object} streamInfo * @memberof module:DashMetrics * @instance + * @ignore */ function addManifestUpdateStreamInfo(streamInfo) { if (streamInfo) { @@ -18724,6 +18985,7 @@ function DashMetrics(config) { * @param {object} request * @memberof module:DashMetrics * @instance + * @ignore */ function addManifestUpdate(request) { metricsModel.addManifestUpdate(_streamingConstantsConstants2['default'].STREAM, request.type, request.requestStartDate, request.requestEndDate); @@ -18737,6 +18999,7 @@ function DashMetrics(config) { * @param {object} traces * @memberof module:DashMetrics * @instance + * @ignore */ function addHttpRequest(request, responseURL, responseStatus, responseHeaders, traces) { metricsModel.addHttpRequest(request.mediaType, null, request.type, request.url, request.quality, responseURL, request.serviceLocation || null, request.range || null, request.requestStartDate, request.firstByteDate, request.requestEndDate, responseStatus, request.duration, responseHeaders, traces); @@ -18744,9 +19007,10 @@ function DashMetrics(config) { /** * @param {object} representation - * @param {string} mediaType + * @param {MediaType} mediaType * @memberof module:DashMetrics * @instance + * @ignore */ function addManifestUpdateRepresentationInfo(representation, mediaType) { if (representation) { @@ -18756,7 +19020,7 @@ function DashMetrics(config) { } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @returns {*} * @memberof module:DashMetrics * @instance @@ -18767,12 +19031,13 @@ function DashMetrics(config) { } /** - * @param {string} mediaType + * @param {MediaType} mediaType * @param {Date} currentTime time of the switch event * @param {object} mpd mpd reference * @param {object} range range of the dvr info * @memberof module:DashMetrics * @instance + * @ignore */ function addDVRInfo(mediaType, currentTime, mpd, range) { metricsModel.addDVRInfo(mediaType, currentTime, mpd, range); @@ -18842,6 +19107,7 @@ function DashMetrics(config) { /** * @memberof module:DashMetrics * @instance + * @ignore */ function addPlayList() { if (playListMetrics) { @@ -18866,7 +19132,7 @@ function DashMetrics(config) { playListTraceMetrics.representationid = representationId; playListTraceMetrics.start = new Date(); playListTraceMetrics.mstart = mediaStartTime; - playListTraceMetrics.playbackspeed = speed; + playListTraceMetrics.playbackspeed = speed !== null ? speed.toString() : null; } } @@ -18893,6 +19159,7 @@ function DashMetrics(config) { * @param {object} errors * @memberof module:DashMetrics * @instance + * @ignore */ function addDVBErrors(errors) { metricsModel.addDVBErrors(errors); @@ -18940,7 +19207,7 @@ DashMetrics.__dashjs_factory_name = 'DashMetrics'; exports['default'] = _coreFactoryMaker2['default'].getSingletonFactory(DashMetrics); module.exports = exports['default']; -},{"108":108,"109":109,"152":152,"242":242,"244":244,"47":47,"78":78}],59:[function(_dereq_,module,exports){ +},{"108":108,"109":109,"152":152,"241":241,"243":243,"47":47,"78":78}],59:[function(_dereq_,module,exports){ /** * 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 @@ -18983,7 +19250,7 @@ var _voSegment = _dereq_(94); var _voSegment2 = _interopRequireDefault(_voSegment); -var _streamingVoDashJSError = _dereq_(226); +var _streamingVoDashJSError = _dereq_(225); var _streamingVoDashJSError2 = _interopRequireDefault(_streamingVoDashJSError); @@ -18991,7 +19258,7 @@ var _coreFactoryMaker = _dereq_(47); var _coreFactoryMaker2 = _interopRequireDefault(_coreFactoryMaker); -var _streamingVoFragmentRequest = _dereq_(228); +var _streamingVoFragmentRequest = _dereq_(227); var _streamingVoFragmentRequest2 = _interopRequireDefault(_streamingVoFragmentRequest); @@ -19334,7 +19601,7 @@ SegmentBaseLoader.__dashjs_factory_name = 'SegmentBaseLoader'; exports['default'] = _coreFactoryMaker2['default'].getSingletonFactory(SegmentBaseLoader); module.exports = exports['default']; -},{"158":158,"226":226,"228":228,"47":47,"94":94}],60:[function(_dereq_,module,exports){ +},{"158":158,"225":225,"227":227,"47":47,"94":94}],60:[function(_dereq_,module,exports){ 'use strict'; Object.defineProperty(exports, '__esModule', { @@ -19343,7 +19610,7 @@ Object.defineProperty(exports, '__esModule', { function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -var _streamingUtilsEBMLParser = _dereq_(212); +var _streamingUtilsEBMLParser = _dereq_(211); var _streamingUtilsEBMLParser2 = _interopRequireDefault(_streamingUtilsEBMLParser); @@ -19359,7 +19626,7 @@ var _voSegment = _dereq_(94); var _voSegment2 = _interopRequireDefault(_voSegment); -var _streamingVoFragmentRequest = _dereq_(228); +var _streamingVoFragmentRequest = _dereq_(227); var _streamingVoFragmentRequest2 = _interopRequireDefault(_streamingVoFragmentRequest); @@ -19367,7 +19634,7 @@ var _streamingNetURLLoader = _dereq_(158); var _streamingNetURLLoader2 = _interopRequireDefault(_streamingNetURLLoader); -var _streamingVoDashJSError = _dereq_(226); +var _streamingVoDashJSError = _dereq_(225); var _streamingVoDashJSError2 = _interopRequireDefault(_streamingVoDashJSError); @@ -19794,7 +20061,7 @@ WebmSegmentBaseLoader.__dashjs_factory_name = 'WebmSegmentBaseLoader'; exports['default'] = _coreFactoryMaker2['default'].getSingletonFactory(WebmSegmentBaseLoader); module.exports = exports['default']; -},{"108":108,"158":158,"212":212,"226":226,"228":228,"47":47,"94":94}],61:[function(_dereq_,module,exports){ +},{"108":108,"158":158,"211":211,"225":225,"227":227,"47":47,"94":94}],61:[function(_dereq_,module,exports){ /** * 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 @@ -19911,6 +20178,7 @@ var DashConstants = (function () { this.CONTENTPROTECTION_ASARRAY = 'ContentProtection_asArray'; this.MAIN = 'main'; this.DYNAMIC = 'dynamic'; + this.STATIC = 'static'; this.MEDIA_PRESENTATION_DURATION = 'mediaPresentationDuration'; this.MINIMUM_UPDATE_PERIOD = 'minimumUpdatePeriod'; this.CODEC_PRIVATE_DATA = 'codecPrivateData'; @@ -19997,7 +20265,7 @@ var _streamingConstantsConstants = _dereq_(108); var _streamingConstantsConstants2 = _interopRequireDefault(_streamingConstantsConstants); -var _streamingVoDashJSError = _dereq_(226); +var _streamingVoDashJSError = _dereq_(225); var _streamingVoDashJSError2 = _interopRequireDefault(_streamingVoDashJSError); @@ -20148,7 +20416,10 @@ function RepresentationController(config) { for (var i = 0, ln = voAvailableRepresentations.length; i < ln; i++) { updateRepresentation(voAvailableRepresentations[i], isDynamic); if (notifyUpdate) { - eventBus.trigger(events.REPRESENTATION_UPDATE_STARTED, { sender: instance, representation: voAvailableRepresentations[i] }); + eventBus.trigger(events.REPRESENTATION_UPDATE_STARTED, { + sender: instance, + representation: voAvailableRepresentations[i] + }); } } } @@ -20208,10 +20479,8 @@ function RepresentationController(config) { repSwitch = undefined; if (r.adaptation.period.mpd.manifest.type === dashConstants.DYNAMIC && !r.adaptation.period.mpd.manifest.ignorePostponeTimePeriod) { - var segmentAvailabilityTimePeriod = r.segmentAvailabilityRange.end - r.segmentAvailabilityRange.start; // We must put things to sleep unless till e.g. the startTime calculation in ScheduleController.onLiveEdgeSearchCompleted fall after the segmentAvailabilityRange.start - var liveDelay = playbackController.computeLiveDelay(currentVoRepresentation.segmentDuration, streamInfo.manifestInfo.DVRWindowSize); - postponeTimePeriod = (liveDelay - segmentAvailabilityTimePeriod) * 1000; + postponeTimePeriod = getRepresentationUpdatePostponeTimePeriod(r, streamInfo); } if (postponeTimePeriod > 0) { @@ -20248,6 +20517,26 @@ function RepresentationController(config) { } } + function getRepresentationUpdatePostponeTimePeriod(representation, streamInfo) { + try { + var streamController = playbackController.getStreamController(); + var activeStreamInfo = streamController.getActiveStreamInfo(); + var startTimeAnchor = representation.segmentAvailabilityRange.start; + + if (activeStreamInfo && activeStreamInfo.id && activeStreamInfo.id !== streamInfo.id) { + // We need to consider the currently playing period if a period switch is performed. + startTimeAnchor = Math.min(playbackController.getTime(), startTimeAnchor); + } + + var segmentAvailabilityTimePeriod = representation.segmentAvailabilityRange.end - startTimeAnchor; + var liveDelay = playbackController.getLiveDelay(); + + return (liveDelay - segmentAvailabilityTimePeriod) * 1000; + } catch (e) { + return 0; + } + } + function onWallclockTimeUpdated(e) { if (e.isDynamic) { updateAvailabilityWindow(e.isDynamic); @@ -20291,7 +20580,7 @@ RepresentationController.__dashjs_factory_name = 'RepresentationController'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(RepresentationController); module.exports = exports['default']; -},{"108":108,"226":226,"47":47}],63:[function(_dereq_,module,exports){ +},{"108":108,"225":225,"47":47}],63:[function(_dereq_,module,exports){ /** * 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 @@ -20641,11 +20930,11 @@ var _voEventStream = _dereq_(87); var _voEventStream2 = _interopRequireDefault(_voEventStream); -var _streamingUtilsObjectUtils = _dereq_(217); +var _streamingUtilsObjectUtils = _dereq_(216); var _streamingUtilsObjectUtils2 = _interopRequireDefault(_streamingUtilsObjectUtils); -var _streamingUtilsURLUtils = _dereq_(221); +var _streamingUtilsURLUtils = _dereq_(220); var _streamingUtilsURLUtils2 = _interopRequireDefault(_streamingUtilsURLUtils); @@ -20657,7 +20946,7 @@ var _coreDebug = _dereq_(45); var _coreDebug2 = _interopRequireDefault(_coreDebug); -var _streamingVoDashJSError = _dereq_(226); +var _streamingVoDashJSError = _dereq_(225); var _streamingVoDashJSError2 = _interopRequireDefault(_streamingVoDashJSError); @@ -20665,7 +20954,7 @@ var _coreErrorsErrors = _dereq_(51); var _coreErrorsErrors2 = _interopRequireDefault(_coreErrorsErrors); -var _streamingThumbnailThumbnailTracks = _dereq_(205); +var _streamingThumbnailThumbnailTracks = _dereq_(204); function DashManifestModel() { var instance = undefined, @@ -20897,6 +21186,11 @@ function DashManifestModel() { } } + // If the codec contains a profiles parameter we remove it. Otherwise it will cause problems when checking for codec capabilities of the platform + if (codec) { + codec = codec.replace(/\sprofiles=[^;]*/g, ''); + } + return codec; } @@ -21155,6 +21449,12 @@ function DashManifestModel() { // SegmentTemplate @duration attribute. We need to find out if @maxSegmentDuration should be used instead of calculated duration if the the duration // exceeds @maxSegmentDuration voRepresentation.segmentDuration = segmentInfo.duration / voRepresentation.timescale; + } else if (realRepresentation.hasOwnProperty(_constantsDashConstants2['default'].SEGMENT_TEMPLATE)) { + segmentInfo = realRepresentation.SegmentTemplate; + + if (segmentInfo.hasOwnProperty(_constantsDashConstants2['default'].SEGMENT_TIMELINE)) { + voRepresentation.segmentDuration = calcSegmentDuration(segmentInfo.SegmentTimeline) / voRepresentation.timescale; + } } if (segmentInfo.hasOwnProperty(_constantsDashConstants2['default'].MEDIA)) { voRepresentation.media = segmentInfo.media; @@ -21189,6 +21489,12 @@ function DashManifestModel() { return voRepresentations; } + function calcSegmentDuration(segmentTimeline) { + var s0 = segmentTimeline.S_asArray[0]; + var s1 = segmentTimeline.S_asArray[1]; + return s0.hasOwnProperty('d') ? s0.d : s1.t - s0.t; + } + function calcMSETimeOffset(representation) { // The MSEOffset is offset from AST for media. It is Period@start - presentationTimeOffset var presentationOffset = representation.presentationTimeOffset; @@ -21823,7 +22129,7 @@ DashManifestModel.__dashjs_factory_name = 'DashManifestModel'; exports['default'] = _coreFactoryMaker2['default'].getSingletonFactory(DashManifestModel); module.exports = exports['default']; -},{"108":108,"205":205,"217":217,"221":221,"226":226,"45":45,"47":47,"51":51,"61":61,"84":84,"85":85,"86":86,"87":87,"90":90,"91":91,"92":92,"96":96}],66:[function(_dereq_,module,exports){ +},{"108":108,"204":204,"216":216,"220":220,"225":225,"45":45,"47":47,"51":51,"61":61,"84":84,"85":85,"86":86,"87":87,"90":90,"91":91,"92":92,"96":96}],66:[function(_dereq_,module,exports){ /** * 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 @@ -21866,10 +22172,6 @@ var _coreFactoryMaker = _dereq_(47); var _coreFactoryMaker2 = _interopRequireDefault(_coreFactoryMaker); -var _coreDebug = _dereq_(45); - -var _coreDebug2 = _interopRequireDefault(_coreDebug); - var _objectiron = _dereq_(76); var _objectiron2 = _interopRequireDefault(_objectiron); @@ -21902,9 +22204,11 @@ var _mapsSegmentValuesMap = _dereq_(70); var _mapsSegmentValuesMap2 = _interopRequireDefault(_mapsSegmentValuesMap); -function DashParser() { +function DashParser(config) { + config = config || {}; var context = this.context; + var debug = config.debug; var instance = undefined, logger = undefined, @@ -21913,7 +22217,7 @@ function DashParser() { objectIron = undefined; function setup() { - logger = (0, _coreDebug2['default'])(context).getInstance().getLogger(instance); + logger = debug.getLogger(instance); matchers = [new _matchersDurationMatcher2['default'](), new _matchersDateTimeMatcher2['default'](), new _matchersNumericMatcher2['default'](), new _matchersStringMatcher2['default']() // last in list to take precedence over NumericMatcher ]; @@ -21958,6 +22262,8 @@ function DashParser() { var ironedTime = window.performance.now(); logger.info('Parsing complete: ( xml2json: ' + (jsonTime - startTime).toPrecision(3) + 'ms, objectiron: ' + (ironedTime - jsonTime).toPrecision(3) + 'ms, total: ' + ((ironedTime - startTime) / 1000).toPrecision(3) + 's)'); + manifest.protocol = 'DASH'; + return manifest; } @@ -21976,7 +22282,7 @@ DashParser.__dashjs_factory_name = 'DashParser'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(DashParser); module.exports = exports['default']; -},{"3":3,"45":45,"47":47,"69":69,"70":70,"72":72,"73":73,"74":74,"75":75,"76":76}],67:[function(_dereq_,module,exports){ +},{"3":3,"47":47,"69":69,"70":70,"72":72,"73":73,"74":74,"75":75,"76":76}],67:[function(_dereq_,module,exports){ /** * 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 @@ -23644,16 +23950,30 @@ var _coreFactoryMaker = _dereq_(47); var _coreFactoryMaker2 = _interopRequireDefault(_coreFactoryMaker); +var _constantsDashConstants = _dereq_(61); + +var _constantsDashConstants2 = _interopRequireDefault(_constantsDashConstants); + +var _modelsDashManifestModel = _dereq_(65); + +var _modelsDashManifestModel2 = _interopRequireDefault(_modelsDashManifestModel); + function TimelineConverter() { var context = this.context; var eventBus = (0, _coreEventBus2['default'])(context).getInstance(); var instance = undefined, + dashManifestModel = undefined, clientServerTimeShift = undefined, isClientServerTimeSyncCompleted = undefined, expectedLiveEdge = undefined; + function setup() { + dashManifestModel = (0, _modelsDashManifestModel2['default'])(context).getInstance(); + reset(); + } + function initialize() { resetInitialSettings(); eventBus.on(_coreEventsEvents2['default'].TIME_SYNCHRONIZATION_COMPLETED, onTimeSyncComplete, this); @@ -23759,6 +24079,12 @@ function TimelineConverter() { // Dynamic Range Finder var d = voRepresentation.segmentDuration || (voRepresentation.segments && voRepresentation.segments.length ? voRepresentation.segments[voRepresentation.segments.length - 1].duration : 0); + + // Specific use case of SegmentTimeline without timeShiftBufferDepth + if (voRepresentation.segmentInfoType === _constantsDashConstants2['default'].SEGMENT_TIMELINE && voPeriod.mpd.timeShiftBufferDepth === Number.POSITIVE_INFINITY) { + return calcSegmentAvailabilityRangeFromTimeline(voRepresentation); + } + var now = calcPresentationTimeFromWallTime(new Date(), voPeriod); var periodEnd = voPeriod.start + voPeriod.duration; range.start = Math.max(now - voPeriod.mpd.timeShiftBufferDepth, voPeriod.start); @@ -23770,6 +24096,36 @@ function TimelineConverter() { return range; } + function calcSegmentAvailabilityRangeFromTimeline(voRepresentation) { + var adaptation = voRepresentation.adaptation.period.mpd.manifest.Period_asArray[voRepresentation.adaptation.period.index].AdaptationSet_asArray[voRepresentation.adaptation.index]; + var representation = dashManifestModel.getRepresentationFor(voRepresentation.index, adaptation); + + var timeline = representation.SegmentTemplate.SegmentTimeline; + var timescale = representation.SegmentTemplate.timescale; + var segments = timeline.S_asArray; + var range = { start: 0, end: 0 }; + var d = 0; + var segment = undefined, + repeat = undefined, + i = undefined, + len = undefined; + + range.start = segments[0].t / timescale; + + for (i = 0, len = segments.length; i < len; i++) { + segment = segments[i]; + repeat = 0; + if (segment.hasOwnProperty('r')) { + repeat = segment.r; + } + d += segment.d / timescale * (1 + repeat); + } + + range.end = range.start + d; + + return range; + } + function getPeriodEnd(voRepresentation, isDynamic) { // Static Range Finder var voPeriod = voRepresentation.adaptation.period; @@ -23842,6 +24198,7 @@ function TimelineConverter() { reset: reset }; + setup(); return instance; } @@ -23849,7 +24206,7 @@ TimelineConverter.__dashjs_factory_name = 'TimelineConverter'; exports['default'] = _coreFactoryMaker2['default'].getSingletonFactory(TimelineConverter); module.exports = exports['default']; -},{"46":46,"47":47,"54":54}],83:[function(_dereq_,module,exports){ +},{"46":46,"47":47,"54":54,"61":61,"65":65}],83:[function(_dereq_,module,exports){ /** * 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 @@ -24968,11 +25325,11 @@ var _netURLLoader = _dereq_(158); var _netURLLoader2 = _interopRequireDefault(_netURLLoader); -var _voHeadRequest = _dereq_(229); +var _voHeadRequest = _dereq_(228); var _voHeadRequest2 = _interopRequireDefault(_voHeadRequest); -var _voDashJSError = _dereq_(226); +var _voDashJSError = _dereq_(225); var _voDashJSError2 = _interopRequireDefault(_voDashJSError); @@ -25104,7 +25461,7 @@ FragmentLoader.__dashjs_factory_name = 'FragmentLoader'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(FragmentLoader); module.exports = exports['default']; -},{"108":108,"158":158,"226":226,"229":229,"47":47}],98:[function(_dereq_,module,exports){ +},{"108":108,"158":158,"225":225,"228":228,"47":47}],98:[function(_dereq_,module,exports){ /** * 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 @@ -25159,19 +25516,19 @@ var _netURLLoader = _dereq_(158); var _netURLLoader2 = _interopRequireDefault(_netURLLoader); -var _utilsURLUtils = _dereq_(221); +var _utilsURLUtils = _dereq_(220); var _utilsURLUtils2 = _interopRequireDefault(_utilsURLUtils); -var _voTextRequest = _dereq_(233); +var _voTextRequest = _dereq_(232); var _voTextRequest2 = _interopRequireDefault(_voTextRequest); -var _voDashJSError = _dereq_(226); +var _voDashJSError = _dereq_(225); var _voDashJSError2 = _interopRequireDefault(_voDashJSError); -var _voMetricsHTTPRequest = _dereq_(242); +var _voMetricsHTTPRequest = _dereq_(241); var _coreEventBus = _dereq_(46); @@ -25193,14 +25550,11 @@ var _dashParserDashParser = _dereq_(66); var _dashParserDashParser2 = _interopRequireDefault(_dashParserDashParser); -var _coreDebug = _dereq_(45); - -var _coreDebug2 = _interopRequireDefault(_coreDebug); - function ManifestLoader(config) { config = config || {}; var context = this.context; + var debug = config.debug; var eventBus = (0, _coreEventBus2['default'])(context).getInstance(); var urlUtils = (0, _utilsURLUtils2['default'])(context).getInstance(); @@ -25214,7 +25568,7 @@ function ManifestLoader(config) { var errHandler = config.errHandler; function setup() { - logger = (0, _coreDebug2['default'])(context).getInstance().getLogger(instance); + logger = debug.getLogger(instance); eventBus.on(_coreEventsEvents2['default'].XLINK_READY, onXlinkReady, instance); urlLoader = (0, _netURLLoader2['default'])(context).create({ @@ -25257,7 +25611,7 @@ function ManifestLoader(config) { } return parser; } else if (data.indexOf('MPD') > -1) { - return (0, _dashParserDashParser2['default'])(context).create(); + return (0, _dashParserDashParser2['default'])(context).create({ debug: debug }); } else { return parser; } @@ -25392,7 +25746,7 @@ var factory = _coreFactoryMaker2['default'].getClassFactory(ManifestLoader); exports['default'] = factory; module.exports = exports['default']; -},{"108":108,"123":123,"158":158,"221":221,"226":226,"233":233,"242":242,"45":45,"46":46,"47":47,"51":51,"54":54,"61":61,"66":66}],99:[function(_dereq_,module,exports){ +},{"108":108,"123":123,"158":158,"220":220,"225":225,"232":232,"241":241,"46":46,"47":47,"51":51,"54":54,"61":61,"66":66}],99:[function(_dereq_,module,exports){ /** * 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 @@ -25451,6 +25805,10 @@ var _coreErrorsErrors = _dereq_(51); var _coreErrorsErrors2 = _interopRequireDefault(_coreErrorsErrors); +var _dashConstantsDashConstants = _dereq_(61); + +var _dashConstantsDashConstants2 = _interopRequireDefault(_dashConstantsDashConstants); + function ManifestUpdater() { var context = this.context; @@ -25555,6 +25913,13 @@ function ManifestUpdater() { function update(manifest) { + // See DASH-IF IOP v4.3 section 4.6.4 "Transition Phase between Live and On-Demand" + // Stop manifest update, ignore static manifest and signal end of dynamic stream to detect end of stream + if (manifestModel.getValue() && manifestModel.getValue().type === _dashConstantsDashConstants2['default'].DYNAMIC && manifest.type === _dashConstantsDashConstants2['default'].STATIC) { + eventBus.trigger(_coreEventsEvents2['default'].DYNAMIC_STREAM_COMPLETED); + return; + } + manifestModel.setValue(manifest); var date = new Date(); @@ -25622,7 +25987,7 @@ ManifestUpdater.__dashjs_factory_name = 'ManifestUpdater'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(ManifestUpdater); module.exports = exports['default']; -},{"45":45,"46":46,"47":47,"51":51,"54":54}],100:[function(_dereq_,module,exports){ +},{"45":45,"46":46,"47":47,"51":51,"54":54,"61":61}],100:[function(_dereq_,module,exports){ /** * 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 @@ -25697,23 +26062,23 @@ var _ManifestLoader = _dereq_(98); var _ManifestLoader2 = _interopRequireDefault(_ManifestLoader); -var _utilsErrorHandler = _dereq_(213); +var _utilsErrorHandler = _dereq_(212); var _utilsErrorHandler2 = _interopRequireDefault(_utilsErrorHandler); -var _utilsCapabilities = _dereq_(208); +var _utilsCapabilities = _dereq_(207); var _utilsCapabilities2 = _interopRequireDefault(_utilsCapabilities); -var _textTextTracks = _dereq_(203); +var _textTextTracks = _dereq_(202); var _textTextTracks2 = _interopRequireDefault(_textTextTracks); -var _utilsRequestModifier = _dereq_(218); +var _utilsRequestModifier = _dereq_(217); var _utilsRequestModifier2 = _interopRequireDefault(_utilsRequestModifier); -var _textTextController = _dereq_(201); +var _textTextController = _dereq_(200); var _textTextController2 = _interopRequireDefault(_textTextController); @@ -25745,7 +26110,7 @@ var _modelsCmcdModel = _dereq_(148); var _modelsCmcdModel2 = _interopRequireDefault(_modelsCmcdModel); -var _utilsDOMStorage = _dereq_(210); +var _utilsDOMStorage = _dereq_(209); var _utilsDOMStorage2 = _interopRequireDefault(_utilsDOMStorage); @@ -25797,7 +26162,7 @@ var _dashUtilsTimelineConverter = _dereq_(82); var _dashUtilsTimelineConverter2 = _interopRequireDefault(_dashUtilsTimelineConverter); -var _voMetricsHTTPRequest = _dereq_(242); +var _voMetricsHTTPRequest = _dereq_(241); var _externalsBase64 = _dereq_(1); @@ -25807,37 +26172,21 @@ var _codemIsoboxer = _dereq_(9); var _codemIsoboxer2 = _interopRequireDefault(_codemIsoboxer); -var _voDashJSError = _dereq_(226); +var _voDashJSError = _dereq_(225); var _voDashJSError2 = _interopRequireDefault(_voDashJSError); -var _utilsSupervisorTools = _dereq_(219); +var _utilsSupervisorTools = _dereq_(218); var _ManifestUpdater = _dereq_(99); var _ManifestUpdater2 = _interopRequireDefault(_ManifestUpdater); -var _streamingUtilsURLUtils = _dereq_(221); +var _streamingUtilsURLUtils = _dereq_(220); var _streamingUtilsURLUtils2 = _interopRequireDefault(_streamingUtilsURLUtils); -var _dashDashHandler = _dereq_(57); - -var _dashDashHandler2 = _interopRequireDefault(_dashDashHandler); - -var _dashControllersRepresentationController = _dereq_(62); - -var _dashControllersRepresentationController2 = _interopRequireDefault(_dashControllersRepresentationController); - -var _modelsFragmentModel = _dereq_(149); - -var _modelsFragmentModel2 = _interopRequireDefault(_modelsFragmentModel); - -var _FragmentLoader = _dereq_(97); - -var _FragmentLoader2 = _interopRequireDefault(_FragmentLoader); - -var _utilsBoxParser = _dereq_(207); +var _utilsBoxParser = _dereq_(206); var _utilsBoxParser2 = _interopRequireDefault(_utilsBoxParser); @@ -25891,18 +26240,17 @@ function MediaPlayer() { source = undefined, protectionData = undefined, mediaPlayerInitialized = undefined, - offlineControllerInitialized = undefined, streamingInitialized = undefined, playbackInitialized = undefined, autoPlay = undefined, abrController = undefined, schemeLoaderFactory = undefined, - offlineController = undefined, timelineConverter = undefined, mediaController = undefined, protectionController = undefined, metricsReportingController = undefined, mssHandler = undefined, + offlineController = undefined, adapter = undefined, mediaPlayerModel = undefined, errHandler = undefined, @@ -25927,7 +26275,6 @@ function MediaPlayer() { function setup() { logger = debug.getLogger(instance); mediaPlayerInitialized = false; - offlineControllerInitialized = false; playbackInitialized = false; streamingInitialized = false; autoPlay = true; @@ -26082,6 +26429,9 @@ function MediaPlayer() { restoreDefaultUTCTimingSources(); setAutoPlay(AutoPlay !== undefined ? AutoPlay : true); + // Detect and initialize offline module to support offline contents playback + detectOffline(); + if (view) { attachView(view); } @@ -26090,9 +26440,6 @@ function MediaPlayer() { attachSource(source); } - if (!offlineControllerInitialized) { - createOfflineControllers(); - } logger.info('[dash.js ' + getVersion() + '] ' + 'MediaPlayer has been initialized'); } @@ -26124,9 +26471,7 @@ function MediaPlayer() { if (offlineController) { offlineController.reset(); - offlineControllerInitialized = false; - - eventBus.off(dashjs.OfflineController.events.DASH_ELEMENTS_CREATION_NEEDED, onDashElementsNeeded, instance); /* jshint ignore:line */ + offlineController = null; } } @@ -26856,233 +27201,13 @@ function MediaPlayer() { --------------------------------------------------------------------------- */ - /** Loads downloads from storage - * This methos has to be called first, to be sure that all downloads have been loaded - * @return {Promise} asynchronously resolved - * @memberof module:MediaPlayer - */ - function loadDownloadsFromStorage() { - if (!offlineControllerInitialized) { - createOfflineControllers(); - } - return offlineController ? offlineController.loadDownloadsFromStorage() : Promise.reject(); - } - - /** - * Creates a new download object in storage - * - * @param {string} manifestURL - url of manifest - * @return {Promise} asynchronously resolved with identifier of download - * @memberof module:MediaPlayer - * @instance - */ - function createDownload(manifestURL) { - if (!offlineControllerInitialized) { - createOfflineControllers(); - } - return offlineController ? offlineController.createDownload(manifestURL) : Promise.reject(); - } - - /** - * Initialise download and gets manifest from url - * - * @param {string} id - identifier of download - * @memberof module:MediaPlayer - * @instance - */ - function initDownload(id) { - if (offlineController) { - offlineController.initDownload(id); - } - } - - /** - * Start download of choosen representations - * - * @param {string} id - identifier of download - * @param {object} selectedRepresentations - choosen representations - * @memberof module:MediaPlayer - * @instance - */ - function startDownload(id, selectedRepresentations) { - if (selectedRepresentations && offlineController) { - offlineController.startDownload(id, selectedRepresentations); - } - } - - /** - * Delete download - * - * @param {string} id - identifier of download - * @memberof module:MediaPlayer - * @instance - */ - function deleteDownload(id) { - if (!offlineControllerInitialized) { - createOfflineControllers(); - } - return offlineController ? offlineController.deleteDownload(id) : Promise.reject(); - } - - /** - * Stop download - * - * @param {string} id - identifier of download - * @memberof module:MediaPlayer - * @instance - */ - function stopDownload(id) { - if (offlineControllerInitialized && offlineController) { - offlineController.stopDownload(id); - } - } - /** - * Resume download - * - * @param {string} id - identifier of download + * Detects if Offline is included and returns an instance of OfflineController.js * @memberof module:MediaPlayer * @instance */ - function resumeDownload(id) { - if (offlineControllerInitialized && offlineController) { - offlineController.resumeDownload(id); - } - } - - /** - * Get progression of download - * - * @param {string} id - identifier of download - * @return {number} progression - * @memberof module:MediaPlayer - * @instance - */ - function getDownloadProgression(id) { - if (offlineControllerInitialized) { - return offlineController ? offlineController.getDownloadProgression(id) : 0; - } - } - - /** - * Get all saved downloads - * - * @return {Promise} asynchronously resolved with saved downloads - * @memberof module:MediaPlayer - * @instance - */ - function getAllDownloads() { - if (!offlineControllerInitialized) { - createOfflineControllers(); - } - return offlineController ? offlineController.getAllDownloads() : Promise.reject(); - } - - function onDashElementsNeeded(eventObj) { - var requestModifier = (0, _utilsRequestModifier2['default'])(context).getInstance(); - var handler = (0, _dashDashHandler2['default'])(context).create({ - type: eventObj.config.type, - mediaPlayerModel: mediaPlayerModel, - mimeType: eventObj.config.mimeType, - baseURLController: baseURLController, - streamInfo: eventObj.config.streamInfo, - errHandler: errHandler, - timelineConverter: timelineConverter, - settings: settings, - dashMetrics: dashMetrics, - eventBus: eventBus, - events: _coreEventsEvents2['default'], - errors: _coreErrorsErrors2['default'], - debug: debug, - dashConstants: _dashConstantsDashConstants2['default'], - requestModifier: requestModifier, - urlUtils: (0, _streamingUtilsURLUtils2['default'])(context).getInstance(), - boxParser: (0, _utilsBoxParser2['default'])(context).getInstance() - }); - var repController = (0, _dashControllersRepresentationController2['default'])(context).create({ - abrController: abrController, - dashMetrics: dashMetrics, - playbackController: playbackController, - timelineConverter: timelineConverter, - type: eventObj.config.type, - eventBus: eventBus, - events: _coreEventsEvents2['default'], - errors: _coreErrorsErrors2['default'], - dashConstants: _dashConstantsDashConstants2['default'], - streamId: eventObj.config.streamInfo ? eventObj.config.streamInfo.id : null - }); - - var fragLoader = (0, _FragmentLoader2['default'])(context).create({ - mediaPlayerModel: mediaPlayerModel, - errHandler: errHandler, - requestModifier: requestModifier, - settings: settings, - dashMetrics: dashMetrics, - eventBus: eventBus, - events: _coreEventsEvents2['default'], - errors: _coreErrorsErrors2['default'], - dashConstants: _dashConstantsDashConstants2['default'], - urlUtils: (0, _streamingUtilsURLUtils2['default'])(context).getInstance() - }); - - var fragModel = (0, _modelsFragmentModel2['default'])(context).create({ - dashMetrics: dashMetrics, - fragmentLoader: fragLoader, - eventBus: eventBus, - events: _coreEventsEvents2['default'], - debug: debug - }); - eventObj.sender.setDashElements(handler, fragModel, repController); - } - - function createOfflineControllers() { - if (!mediaPlayerInitialized) { - throw MEDIA_PLAYER_NOT_INITIALIZED_ERROR; - } - - var OfflineController = dashjs.OfflineController; /* jshint ignore:line */ - - if (typeof OfflineController !== 'function') { - //TODO need a better way to register/detect plugin components - return; - } - - offlineController = OfflineController(context).create(); - - eventBus.on(OfflineController.events.DASH_ELEMENTS_CREATION_NEEDED, onDashElementsNeeded, instance); - - _MediaPlayerEvents2['default'].extend(OfflineController.events, { - publicOnly: true - }); - _coreErrorsErrors2['default'].extend(OfflineController.errors); - - var manifestLoader = createManifestLoader(); - var manifestUpdater = (0, _ManifestUpdater2['default'])(context).create(); - - manifestUpdater.setConfig({ - manifestModel: manifestModel, - adapter: adapter, - manifestLoader: manifestLoader, - errHandler: errHandler - }); - - offlineController.setConfig({ - debug: debug, - manifestUpdater: manifestUpdater, - baseURLController: baseURLController, - manifestLoader: manifestLoader, - manifestModel: manifestModel, - adapter: adapter, - errHandler: errHandler, - schemeLoaderFactory: schemeLoaderFactory, - eventBus: eventBus, - events: _coreEventsEvents2['default'], - constants: _constantsConstants2['default'], - dashConstants: _dashConstantsDashConstants2['default'], - urlUtils: (0, _streamingUtilsURLUtils2['default'])(context).getInstance() - }); - - offlineControllerInitialized = true; + function getOfflineController() { + return detectOffline(); } /* @@ -27114,12 +27239,13 @@ function MediaPlayer() { * @param {string} lang - default language * @memberof module:MediaPlayer * @instance + * @deprecated will be removed in version 3.2.0. Please use setInitialMediaSettingsFor("fragmentedText", { lang: lang }) instead */ function setTextDefaultLanguage(lang) { + logger.warn('setTextDefaultLanguage is deprecated and will be removed in version 3.2.0. Please use setInitialMediaSettingsFor("fragmentedText", { lang: lang }) instead'); if (textController === undefined) { textController = (0, _textTextController2['default'])(context).getInstance(); } - textController.setTextDefaultLanguage(lang); } @@ -27129,8 +27255,10 @@ function MediaPlayer() { * @return {string} the default language if it has been set using setTextDefaultLanguage * @memberof module:MediaPlayer * @instance + * @deprecated will be removed in version 3.2.0. Please use getInitialMediaSettingsFor("fragmentedText").lang instead */ function getTextDefaultLanguage() { + logger.warn('getTextDefaultLanguage is deprecated and will be removed in version 3.2.0. Please use getInitialMediaSettingsFor("fragmentedText").lang instead'); if (textController === undefined) { textController = (0, _textTextController2['default'])(context).getInstance(); } @@ -27442,11 +27570,14 @@ function MediaPlayer() { * @throws {@link module:MediaPlayer~MEDIA_PLAYER_NOT_INITIALIZED_ERROR MEDIA_PLAYER_NOT_INITIALIZED_ERROR} if called before initialize function * @instance */ - function setQualityForSettingsFor(type, value) { + function setInitialMediaSettingsFor(type, value) { if (!mediaPlayerInitialized) { throw MEDIA_PLAYER_NOT_INITIALIZED_ERROR; } mediaController.setInitialSettings(type, value); + if (type === _constantsConstants2['default'].FRAGMENTED_TEXT) { + textController.setInitialSettings(value); + } } /** @@ -27563,6 +27694,8 @@ function MediaPlayer() { --------------------------------------------------------------------------- PROTECTION MANAGEMENT --------------------------------------------------------------------------- + */ + /** * Detects if Protection is included and returns an instance of ProtectionController.js * @memberof module:MediaPlayer @@ -27609,31 +27742,36 @@ function MediaPlayer() { */ /** - * Return the thumbnail at time position. - * @returns {Thumbnail|null} - Thumbnail for the given time position. It returns null in case there are is not a thumbnails representation or - * if it doesn't contain a thumbnail for the given time position. + * Provide the thumbnail at time position. This can be asynchronous, so you must provide a callback ro retrieve thumbnails informations * @param {number} time - A relative time, in seconds, based on the return value of the {@link module:MediaPlayer#duration duration()} method is expected - * @param {function} callback - A Callback function provided when retrieving thumbnail + * @param {function} callback - A Callback function provided when retrieving thumbnail the given time position. Thumbnail object is null in case there are is not a thumbnails representation or + * if it doesn't contain a thumbnail for the given time position. * @memberof module:MediaPlayer * @instance */ - function getThumbnail(time, callback) { + function provideThumbnail(time, callback) { + if (typeof callback !== 'function') { + return; + } if (time < 0) { - return null; + callback(null); + return; } var s = playbackController.getIsDynamic() ? getDVRSeekOffset(time) : time; var stream = streamController.getStreamForTime(s); if (stream === null) { - return null; + callback(null); + return; } var thumbnailController = stream.getThumbnailController(); if (!thumbnailController) { - return null; + callback(null); + return; } var timeInPeriod = streamController.getTimeRelativeToStreamId(s, stream.getId()); - return thumbnailController.get(timeInPeriod, callback); + return thumbnailController.provide(timeInPeriod, callback); } /* @@ -27947,6 +28085,7 @@ function MediaPlayer() { function createManifestLoader() { return (0, _ManifestLoader2['default'])(context).create({ + debug: debug, errHandler: errHandler, dashMetrics: dashMetrics, mediaPlayerModel: mediaPlayerModel, @@ -28042,6 +28181,64 @@ function MediaPlayer() { } } + function detectOffline() { + if (!mediaPlayerInitialized) { + throw MEDIA_PLAYER_NOT_INITIALIZED_ERROR; + } + + if (offlineController) { + return offlineController; + } + + // do not require Offline as dependencies as this is optional and intended to be loaded separately + var OfflineController = dashjs.OfflineController; /* jshint ignore:line */ + + if (typeof OfflineController === 'function') { + //TODO need a better way to register/detect plugin components + _coreEventsEvents2['default'].extend(OfflineController.events); + _MediaPlayerEvents2['default'].extend(OfflineController.events, { + publicOnly: true + }); + _coreErrorsErrors2['default'].extend(OfflineController.errors); + + var manifestLoader = createManifestLoader(); + var manifestUpdater = (0, _ManifestUpdater2['default'])(context).create(); + + manifestUpdater.setConfig({ + manifestModel: manifestModel, + adapter: adapter, + manifestLoader: manifestLoader, + errHandler: errHandler + }); + + offlineController = OfflineController(context).create({ + debug: debug, + manifestUpdater: manifestUpdater, + baseURLController: baseURLController, + manifestLoader: manifestLoader, + manifestModel: manifestModel, + mediaPlayerModel: mediaPlayerModel, + abrController: abrController, + playbackController: playbackController, + adapter: adapter, + errHandler: errHandler, + dashMetrics: dashMetrics, + timelineConverter: timelineConverter, + schemeLoaderFactory: schemeLoaderFactory, + eventBus: eventBus, + events: _coreEventsEvents2['default'], + errors: _coreErrorsErrors2['default'], + constants: _constantsConstants2['default'], + settings: settings, + dashConstants: _dashConstantsDashConstants2['default'], + urlUtils: (0, _streamingUtilsURLUtils2['default'])(context).getInstance() + }); + return offlineController; + } + + return null; + } + function getAsUTC(valToConvert) { var metric = dashMetrics.getCurrentDVRInfo(); var availableFrom = undefined, @@ -28056,8 +28253,9 @@ function MediaPlayer() { } function initializePlayback() { - if (offlineControllerInitialized) { - offlineController.resetDownloads(); + + if (offlineController) { + offlineController.resetRecords(); } if (!streamingInitialized && source) { @@ -28148,7 +28346,7 @@ function MediaPlayer() { getTracksFor: getTracksFor, getTracksForTypeFromManifest: getTracksForTypeFromManifest, getCurrentTrackFor: getCurrentTrackFor, - setInitialMediaSettingsFor: setQualityForSettingsFor, + setInitialMediaSettingsFor: setInitialMediaSettingsFor, getInitialMediaSettingsFor: getInitialMediaSettingsFor, setCurrentTrack: setCurrentTrack, getTrackSwitchModeFor: getTrackSwitchModeFor, @@ -28172,20 +28370,12 @@ function MediaPlayer() { displayCaptionsOnTop: displayCaptionsOnTop, attachTTMLRenderingDiv: attachTTMLRenderingDiv, getCurrentTextTrackIndex: getCurrentTextTrackIndex, - getThumbnail: getThumbnail, - loadDownloadsFromStorage: loadDownloadsFromStorage, - createDownload: createDownload, + provideThumbnail: provideThumbnail, getDashAdapter: getDashAdapter, + getOfflineController: getOfflineController, getSettings: getSettings, updateSettings: updateSettings, resetSettings: resetSettings, - stopDownload: stopDownload, - getAllDownloads: getAllDownloads, - deleteDownload: deleteDownload, - resumeDownload: resumeDownload, - getDownloadProgression: getDownloadProgression, - startDownload: startDownload, - initDownload: initDownload, reset: reset }; @@ -28203,7 +28393,7 @@ _coreFactoryMaker2['default'].updateClassFactory(MediaPlayer.__dashjs_factory_na exports['default'] = factory; module.exports = exports['default']; -},{"1":1,"101":101,"108":108,"109":109,"111":111,"112":112,"117":117,"119":119,"121":121,"148":148,"149":149,"150":150,"151":151,"153":153,"154":154,"157":157,"2":2,"201":201,"203":203,"207":207,"208":208,"210":210,"213":213,"218":218,"219":219,"221":221,"226":226,"242":242,"45":45,"46":46,"47":47,"48":48,"50":50,"51":51,"54":54,"56":56,"57":57,"58":58,"61":61,"62":62,"63":63,"82":82,"9":9,"97":97,"98":98,"99":99}],101:[function(_dereq_,module,exports){ +},{"1":1,"101":101,"108":108,"109":109,"111":111,"112":112,"117":117,"119":119,"121":121,"148":148,"150":150,"151":151,"153":153,"154":154,"157":157,"2":2,"200":200,"202":202,"206":206,"207":207,"209":209,"212":212,"217":217,"218":218,"220":220,"225":225,"241":241,"45":45,"46":46,"47":47,"48":48,"50":50,"51":51,"54":54,"56":56,"58":58,"61":61,"63":63,"82":82,"9":9,"98":98,"99":99}],101:[function(_dereq_,module,exports){ /** * 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 @@ -28969,7 +29159,7 @@ var _coreDebug = _dereq_(45); var _coreDebug2 = _interopRequireDefault(_coreDebug); -var _voDashJSError = _dereq_(226); +var _voDashJSError = _dereq_(225); var _voDashJSError2 = _interopRequireDefault(_voDashJSError); @@ -28985,7 +29175,7 @@ var _coreFactoryMaker = _dereq_(47); var _coreFactoryMaker2 = _interopRequireDefault(_coreFactoryMaker); -var _textTextController = _dereq_(201); +var _textTextController = _dereq_(200); var _textTextController2 = _interopRequireDefault(_textTextController); @@ -29326,7 +29516,7 @@ var factory = _coreFactoryMaker2['default'].getClassFactory(SourceBufferSink); exports['default'] = factory; module.exports = exports['default']; -},{"201":201,"226":226,"45":45,"46":46,"47":47,"51":51,"54":54}],105:[function(_dereq_,module,exports){ +},{"200":200,"225":225,"45":45,"46":46,"47":47,"51":51,"54":54}],105:[function(_dereq_,module,exports){ /** * 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 @@ -29381,7 +29571,7 @@ var _controllersFragmentController = _dereq_(116); var _controllersFragmentController2 = _interopRequireDefault(_controllersFragmentController); -var _thumbnailThumbnailController = _dereq_(204); +var _thumbnailThumbnailController = _dereq_(203); var _thumbnailThumbnailController2 = _interopRequireDefault(_thumbnailThumbnailController); @@ -29405,15 +29595,15 @@ var _coreFactoryMaker = _dereq_(47); var _coreFactoryMaker2 = _interopRequireDefault(_coreFactoryMaker); -var _voDashJSError = _dereq_(226); +var _voDashJSError = _dereq_(225); var _voDashJSError2 = _interopRequireDefault(_voDashJSError); -var _utilsBoxParser = _dereq_(207); +var _utilsBoxParser = _dereq_(206); var _utilsBoxParser2 = _interopRequireDefault(_utilsBoxParser); -var _utilsURLUtils = _dereq_(221); +var _utilsURLUtils = _dereq_(220); var _utilsURLUtils2 = _interopRequireDefault(_utilsURLUtils); @@ -29442,10 +29632,13 @@ function Stream(config) { var instance = undefined, logger = undefined, + streamInfo = undefined, streamProcessors = undefined, + isStreamInitialized = undefined, isStreamActivated = undefined, isMediaInitialized = undefined, - streamInfo = undefined, + hasVideoTrack = undefined, + hasAudioTrack = undefined, updateError = undefined, isUpdating = undefined, protectionController = undefined, @@ -29597,6 +29790,9 @@ function Stream(config) { function resetInitialSettings() { deactivate(); streamInfo = null; + isStreamInitialized = false; + hasVideoTrack = false; + hasAudioTrack = false; updateError = {}; isUpdating = false; } @@ -29629,6 +29825,18 @@ function Stream(config) { return streamInfo ? streamInfo.start : NaN; } + function getLiveStartTime() { + if (!streamInfo.manifestInfo.isDynamic) return NaN; + // Get live start time of the video stream (1st in array of streams) + // or audio if no video stream + for (var i = 0; i < streamProcessors.length; i++) { + if (streamProcessors[i].getType() === _constantsConstants2['default'].AUDIO || streamProcessors[i].getType() === _constantsConstants2['default'].VIDEO) { + return streamProcessors[i].getLiveStartTime(); + } + } + return NaN; + } + function getId() { return streamInfo ? streamInfo.id : null; } @@ -29637,6 +29845,14 @@ function Stream(config) { return streamInfo; } + function getHasAudioTrack() { + return hasAudioTrack; + } + + function getHasVideoTrack() { + return hasVideoTrack; + } + function getThumbnailController() { return thumbnailController; } @@ -29716,6 +29932,7 @@ function Stream(config) { logger.debug('Stream - Update stream controller'); if (manifest.refreshManifestOnSwitchTrack) { + // Applies only for MSS streams logger.debug('Stream - Refreshing manifest for switch track'); trackChangedEvent = e; manifestUpdater.refreshManifest(); @@ -29751,14 +29968,13 @@ function Stream(config) { abrController: abrController, playbackController: playbackController, mediaController: mediaController, - streamController: config.streamController, textController: textController, errHandler: errHandler, settings: settings, boxParser: boxParser }); - streamProcessor.initialize(mediaSource); + streamProcessor.initialize(mediaSource, hasVideoTrack); abrController.updateTopQualityIndex(mediaInfo); if (optionalSettings) { @@ -29798,6 +30014,14 @@ function Stream(config) { return; } + if (type === _constantsConstants2['default'].VIDEO) { + hasVideoTrack = true; + } + + if (type === _constantsConstants2['default'].AUDIO) { + hasAudioTrack = true; + } + for (var i = 0, ln = allMediaForType.length; i < ln; i++) { mediaInfo = allMediaForType[i]; @@ -29827,14 +30051,8 @@ function Stream(config) { return; } - if (type !== _constantsConstants2['default'].FRAGMENTED_TEXT || type === _constantsConstants2['default'].FRAGMENTED_TEXT && textController.getTextDefaultEnabled()) { - mediaController.checkInitialMediaSettingsForType(type, streamInfo); - initialMediaInfo = mediaController.getCurrentTrackFor(type, streamInfo); - } - - if (type === _constantsConstants2['default'].FRAGMENTED_TEXT && !textController.getTextDefaultEnabled()) { - initialMediaInfo = mediaController.getTracksFor(type, streamInfo)[0]; - } + mediaController.checkInitialMediaSettingsForType(type, streamInfo); + initialMediaInfo = mediaController.getCurrentTrackFor(type, streamInfo); eventBus.trigger(_coreEventsEvents2['default'].STREAM_INITIALIZING, { streamInfo: streamInfo, @@ -29961,11 +30179,22 @@ function Stream(config) { if (error) { errHandler.error(error); - } else { + } else if (!isStreamInitialized) { + isStreamInitialized = true; + timelineConverter.setTimeSyncCompleted(true); + eventBus.trigger(_coreEventsEvents2['default'].STREAM_INITIALIZED, { - streamInfo: streamInfo + streamInfo: streamInfo, + liveStartTime: getLiveStartTime() }); } + + // (Re)start ScheduleController: + // - in case stream initialization has been completed after 'play' event (case for SegmentBase streams) + // - in case stream is complete but a track switch has been requested + for (var i = 0; i < ln && streamProcessors[i]; i++) { + streamProcessors[i].getScheduleController().start(); + } } function getMediaInfo(type) { @@ -29994,9 +30223,7 @@ function Stream(config) { } function onBufferingCompleted(e) { - if (e.streamInfo !== streamInfo) { - return; - } + if (e.streamId !== streamInfo.id) return; var processors = getProcessors(); var ln = processors.length; @@ -30029,7 +30256,7 @@ function Stream(config) { } function onInbandEvents(e) { - if (!streamInfo || e.streamInfo.id !== streamInfo.id) return; + if (!streamInfo || e.sender.getStreamInfo().id !== streamInfo.id) return; addInbandEvents(e.events); } @@ -30231,6 +30458,8 @@ function Stream(config) { getStartTime: getStartTime, getId: getId, getStreamInfo: getStreamInfo, + getHasAudioTrack: getHasAudioTrack, + getHasVideoTrack: getHasVideoTrack, preload: preload, getThumbnailController: getThumbnailController, getBitrateListFor: getBitrateListFor, @@ -30251,7 +30480,7 @@ Stream.__dashjs_factory_name = 'Stream'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(Stream); module.exports = exports['default']; -},{"106":106,"108":108,"116":116,"204":204,"207":207,"221":221,"226":226,"45":45,"46":46,"47":47,"51":51,"54":54,"61":61}],106:[function(_dereq_,module,exports){ +},{"106":106,"108":108,"116":116,"203":203,"206":206,"220":220,"225":225,"45":45,"46":46,"47":47,"51":51,"54":54,"61":61}],106:[function(_dereq_,module,exports){ /** * 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 @@ -30298,11 +30527,19 @@ var _dashConstantsDashConstants = _dereq_(61); var _dashConstantsDashConstants2 = _interopRequireDefault(_dashConstantsDashConstants); +var _constantsMetricsConstants = _dereq_(109); + +var _constantsMetricsConstants2 = _interopRequireDefault(_constantsMetricsConstants); + +var _modelsFragmentModel = _dereq_(149); + +var _modelsFragmentModel2 = _interopRequireDefault(_modelsFragmentModel); + var _controllersBufferController = _dereq_(114); var _controllersBufferController2 = _interopRequireDefault(_controllersBufferController); -var _textTextBufferController = _dereq_(200); +var _textTextBufferController = _dereq_(199); var _textTextBufferController2 = _interopRequireDefault(_textTextBufferController); @@ -30314,11 +30551,15 @@ var _dashControllersRepresentationController = _dereq_(62); var _dashControllersRepresentationController2 = _interopRequireDefault(_dashControllersRepresentationController); +var _utilsLiveEdgeFinder = _dereq_(215); + +var _utilsLiveEdgeFinder2 = _interopRequireDefault(_utilsLiveEdgeFinder); + var _coreFactoryMaker = _dereq_(47); var _coreFactoryMaker2 = _interopRequireDefault(_coreFactoryMaker); -var _utilsSupervisorTools = _dereq_(219); +var _utilsSupervisorTools = _dereq_(218); var _coreEventBus = _dereq_(46); @@ -30336,7 +30577,7 @@ var _coreErrorsErrors = _dereq_(51); var _coreErrorsErrors2 = _interopRequireDefault(_coreErrorsErrors); -var _voDashJSError = _dereq_(226); +var _voDashJSError = _dereq_(225); var _voDashJSError2 = _interopRequireDefault(_voDashJSError); @@ -30344,14 +30585,24 @@ var _coreDebug = _dereq_(45); var _coreDebug2 = _interopRequireDefault(_coreDebug); -var _utilsRequestModifier = _dereq_(218); +var _utilsRequestModifier = _dereq_(217); var _utilsRequestModifier2 = _interopRequireDefault(_utilsRequestModifier); -var _streamingUtilsURLUtils = _dereq_(221); +var _streamingUtilsURLUtils = _dereq_(220); var _streamingUtilsURLUtils2 = _interopRequireDefault(_streamingUtilsURLUtils); +var _utilsBoxParser = _dereq_(206); + +var _utilsBoxParser2 = _interopRequireDefault(_utilsBoxParser); + +var _voFragmentRequest = _dereq_(227); + +var _voFragmentRequest2 = _interopRequireDefault(_voFragmentRequest); + +var _voMetricsPlayList = _dereq_(243); + function StreamProcessor(config) { config = config || {}; @@ -30369,7 +30620,6 @@ function StreamProcessor(config) { var fragmentModel = config.fragmentModel; var abrController = config.abrController; var playbackController = config.playbackController; - var streamController = config.streamController; var mediaController = config.mediaController; var textController = config.textController; var dashMetrics = config.dashMetrics; @@ -30377,21 +30627,33 @@ function StreamProcessor(config) { var boxParser = config.boxParser; var instance = undefined, + logger = undefined, + isDynamic = undefined, mediaInfo = undefined, mediaInfoArr = undefined, bufferController = undefined, scheduleController = undefined, representationController = undefined, - indexHandler = undefined; + liveEdgeFinder = undefined, + indexHandler = undefined, + streamInitialized = undefined; function setup() { + logger = (0, _coreDebug2['default'])(context).getInstance().getLogger(instance); resetInitialSettings(); + eventBus.on(_coreEventsEvents2['default'].DATA_UPDATE_COMPLETED, onDataUpdateCompleted, instance, _coreEventBus2['default'].EVENT_PRIORITY_HIGH); // High priority to be notified before Stream + eventBus.on(_coreEventsEvents2['default'].QUALITY_CHANGE_REQUESTED, onQualityChanged, instance); + eventBus.on(_coreEventsEvents2['default'].INIT_FRAGMENT_NEEDED, onInitFragmentNeeded, instance); + eventBus.on(_coreEventsEvents2['default'].MEDIA_FRAGMENT_NEEDED, onMediaFragmentNeeded, instance); + eventBus.on(_coreEventsEvents2['default'].MEDIA_FRAGMENT_LOADED, onMediaFragmentLoaded, instance); eventBus.on(_coreEventsEvents2['default'].BUFFER_LEVEL_UPDATED, onBufferLevelUpdated, instance); - eventBus.on(_coreEventsEvents2['default'].DATA_UPDATE_COMPLETED, onDataUpdateCompleted, instance); + eventBus.on(_coreEventsEvents2['default'].BUFFER_LEVEL_STATE_CHANGED, onBufferLevelStateChanged, instance); + eventBus.on(_coreEventsEvents2['default'].BUFFER_CLEARED, onBufferCleared, instance); + eventBus.on(_coreEventsEvents2['default'].SEEK_TARGET, onSeekTarget, instance); } - function initialize(mediaSource) { + function initialize(mediaSource, hasVideoTrack) { indexHandler = (0, _dashDashHandler2['default'])(context).create({ streamInfo: streamInfo, type: type, @@ -30412,28 +30674,18 @@ function StreamProcessor(config) { urlUtils: (0, _streamingUtilsURLUtils2['default'])(context).getInstance() }); - // initialize controllers - indexHandler.initialize(playbackController.getIsDynamic()); + // Create live edge finder for dynamic streams + isDynamic = streamInfo.manifestInfo.isDynamic; + if (isDynamic) { + liveEdgeFinder = (0, _utilsLiveEdgeFinder2['default'])(context).create({ + timelineConverter: timelineConverter + }); + } + + // Create/initialize controllers + indexHandler.initialize(isDynamic); abrController.registerStreamType(type, instance); - bufferController = createBufferControllerForType(type); - scheduleController = (0, _controllersScheduleController2['default'])(context).create({ - streamId: streamInfo.id, - type: type, - mimeType: mimeType, - adapter: adapter, - dashMetrics: dashMetrics, - timelineConverter: timelineConverter, - mediaPlayerModel: mediaPlayerModel, - fragmentModel: fragmentModel, - abrController: abrController, - playbackController: playbackController, - streamController: streamController, - textController: textController, - streamProcessor: instance, - mediaController: mediaController, - settings: settings - }); representationController = (0, _dashControllersRepresentationController2['default'])(context).create({ streamId: streamInfo.id, type: type, @@ -30446,10 +30698,31 @@ function StreamProcessor(config) { eventBus: eventBus, errors: _coreErrorsErrors2['default'] }); + + bufferController = createBufferControllerForType(type); if (bufferController) { bufferController.initialize(mediaSource); } - scheduleController.initialize(); + + scheduleController = (0, _controllersScheduleController2['default'])(context).create({ + streamId: streamInfo.id, + type: type, + mimeType: mimeType, + adapter: adapter, + dashMetrics: dashMetrics, + mediaPlayerModel: mediaPlayerModel, + fragmentModel: fragmentModel, + abrController: abrController, + playbackController: playbackController, + textController: textController, + mediaController: mediaController, + bufferController: bufferController, + settings: settings + }); + + scheduleController.initialize(hasVideoTrack); + + streamInitialized = false; } function resetInitialSettings() { @@ -30458,7 +30731,9 @@ function StreamProcessor(config) { } function reset(errored, keepBuffers) { - indexHandler.reset(); + if (indexHandler) { + indexHandler.reset(); + } if (bufferController) { bufferController.reset(errored, keepBuffers); @@ -30475,12 +30750,24 @@ function StreamProcessor(config) { representationController = null; } + if (liveEdgeFinder) { + liveEdgeFinder.reset(); + liveEdgeFinder = null; + } + if (abrController) { abrController.unRegisterStreamType(type); } - eventBus.off(_coreEventsEvents2['default'].BUFFER_LEVEL_UPDATED, onBufferLevelUpdated, instance); eventBus.off(_coreEventsEvents2['default'].DATA_UPDATE_COMPLETED, onDataUpdateCompleted, instance); + eventBus.off(_coreEventsEvents2['default'].QUALITY_CHANGE_REQUESTED, onQualityChanged, instance); + eventBus.off(_coreEventsEvents2['default'].INIT_FRAGMENT_NEEDED, onInitFragmentNeeded, instance); + eventBus.off(_coreEventsEvents2['default'].MEDIA_FRAGMENT_NEEDED, onMediaFragmentNeeded, instance); + eventBus.off(_coreEventsEvents2['default'].MEDIA_FRAGMENT_LOADED, onMediaFragmentLoaded, instance); + eventBus.off(_coreEventsEvents2['default'].BUFFER_LEVEL_UPDATED, onBufferLevelUpdated, instance); + eventBus.off(_coreEventsEvents2['default'].BUFFER_LEVEL_STATE_CHANGED, onBufferLevelStateChanged, instance); + eventBus.off(_coreEventsEvents2['default'].BUFFER_CLEARED, onBufferCleared, instance); + eventBus.off(_coreEventsEvents2['default'].SEEK_TARGET, onSeekTarget, instance); resetInitialSettings(); type = null; @@ -30492,19 +30779,57 @@ function StreamProcessor(config) { } function onDataUpdateCompleted(e) { - if (e.sender.getType() !== getType() || e.sender.getStreamId() !== streamInfo.id || !e.error || e.error.code !== _coreErrorsErrors2['default'].SEGMENTS_UPDATE_FAILED_ERROR_CODE) return; + if (e.sender.getType() !== getType() || e.sender.getStreamId() !== streamInfo.id) return; + + if (!e.error) { + // Update representation if no error + scheduleController.setCurrentRepresentation(adapter.convertDataToRepresentationInfo(e.currentRepresentation)); + } + if (!e.error || e.error.code === _coreErrorsErrors2['default'].SEGMENTS_UPDATE_FAILED_ERROR_CODE) { + // Update has been postponed, update nevertheless DVR info + addDVRMetric(); + } + } - addDVRMetric(); + function onQualityChanged(e) { + if (type !== e.mediaType || streamInfo.id !== e.streamInfo.id) return; + var representationInfo = getRepresentationInfo(e.newQuality); + scheduleController.setCurrentRepresentation(representationInfo); + dashMetrics.pushPlayListTraceMetrics(new Date(), _voMetricsPlayList.PlayListTrace.REPRESENTATION_SWITCH_STOP_REASON); + dashMetrics.createPlaylistTraceMetrics(representationInfo.id, playbackController.getTime() * 1000, playbackController.getPlaybackRate()); } function onBufferLevelUpdated(e) { - if (e.sender.getStreamProcessor() !== instance) return; - var manifest = manifestModel.getValue(); - if (!manifest.doNotUpdateDVRWindowOnBufferUpdated) { + if (e.streamId !== streamInfo.id || e.mediaType !== type) return; + + dashMetrics.addBufferLevel(type, new Date(), e.bufferLevel * 1000); + + if (!manifestModel.getValue().doNotUpdateDVRWindowOnBufferUpdated) { addDVRMetric(); } } + function onBufferLevelStateChanged(e) { + if (e.streamId !== streamInfo.id || e.mediaType !== type) return; + + dashMetrics.addBufferState(type, e.state, scheduleController.getBufferTarget()); + if (e.state === _constantsMetricsConstants2['default'].BUFFER_EMPTY && !playbackController.isSeeking()) { + // logger.info('Buffer is empty! Stalling!'); + dashMetrics.pushPlayListTraceMetrics(new Date(), _voMetricsPlayList.PlayListTrace.REBUFFERING_REASON); + } + } + + function onBufferCleared(e) { + if (e.streamId !== streamInfo.id || e.mediaType !== type) return; + + if (e.unintended) { + // There was an unintended buffer remove, probably creating a gap in the buffer, remove every saved request + fragmentModel.removeExecutedRequestsAfterTime(e.from); + } else { + fragmentModel.syncExecutedRequestsWithBufferedRange(bufferController.getBuffer().getAllBufferRanges(), streamInfo.duration); + } + } + function addDVRMetric() { var manifestInfo = streamInfo.manifestInfo; var isDynamic = manifestInfo.isDynamic; @@ -30603,7 +30928,7 @@ function StreamProcessor(config) { } function setMediaSource(mediaSource) { - bufferController.setMediaSource(mediaSource, getMediaInfo()); + bufferController.setMediaSource(mediaSource, getMediaInfoArr()); } function dischargePreBuffer() { @@ -30640,14 +30965,144 @@ function StreamProcessor(config) { return bufferController ? bufferController.getBufferLevel() : 0; } - function switchInitData(representationId, bufferResetEnabled) { + function onInitFragmentNeeded(e) { + if (!e.sender || e.mediaType !== type || e.streamId !== streamInfo.id) return; + + if (adapter.getIsTextTrack(mimeType) && !textController.isTextEnabled()) return; + + if (bufferController && e.representationId) { + if (!bufferController.appendInitSegment(e.representationId)) { + // Init segment not in cache, send new request + var request = indexHandler ? indexHandler.getInitRequest(getMediaInfo(), representationController.getCurrentRepresentation()) : null; + scheduleController.processInitRequest(request); + } + } + } + + function onMediaFragmentNeeded(e) { + if (!e.sender || e.mediaType !== type || e.streamId !== streamInfo.id) return; + + var request = undefined; + + // Don't schedule next fragments while pruning to avoid buffer inconsistencies + if (!bufferController.getIsPruningInProgress()) { + request = findNextRequest(e.seekTarget, e.replacement); + if (request) { + scheduleController.setSeekTarget(NaN); + if (!e.replacement) { + if (!isNaN(request.startTime + request.duration)) { + setIndexHandlerTime(request.startTime + request.duration); + } + request.delayLoadingTime = new Date().getTime() + scheduleController.getTimeToLoadDelay(); + scheduleController.setTimeToLoadDelay(0); + } + } + } + + scheduleController.processMediaRequest(request); + } + + function findNextRequest(seekTarget, requestToReplace) { + var representationInfo = getRepresentationInfo(); + var hasSeekTarget = !isNaN(seekTarget); + var currentTime = playbackController.getNormalizedTime(); + var time = hasSeekTarget ? seekTarget : getIndexHandlerTime(); + var bufferIsDivided = false; + var request = undefined; + + if (isNaN(time) || getType() === _constantsConstants2['default'].FRAGMENTED_TEXT && !textController.isTextEnabled()) { + return null; + } + /** + * This is critical for IE/Safari/EDGE + * */ if (bufferController) { - bufferController.switchInitData(streamInfo.id, representationId, bufferResetEnabled); + var range = bufferController.getRangeAt(time); + var playingRange = bufferController.getRangeAt(currentTime); + if ((range !== null || playingRange !== null) && !hasSeekTarget) { + if (!range || playingRange && playingRange.start != range.start && playingRange.end != range.end) { + var hasDiscontinuities = bufferController.getBuffer().hasDiscontinuitiesAfter(currentTime); + if (hasDiscontinuities && getType() !== _constantsConstants2['default'].FRAGMENTED_TEXT) { + fragmentModel.removeExecutedRequestsAfterTime(playingRange.end); + bufferIsDivided = true; + } + } + } } + + if (requestToReplace) { + time = requestToReplace.startTime + requestToReplace.duration / 2; + request = getFragmentRequest(representationInfo, time, { + timeThreshold: 0, + ignoreIsFinished: true + }); + } else { + // Use time just whenever is strictly needed + request = getFragmentRequest(representationInfo, hasSeekTarget || bufferIsDivided ? time : undefined, { + keepIdx: !hasSeekTarget && !bufferIsDivided + }); + + // Then, check if this request was downloaded or not + while (request && request.action !== _voFragmentRequest2['default'].ACTION_COMPLETE && fragmentModel.isFragmentLoaded(request)) { + // loop until we found not loaded fragment, or no fragment + request = getFragmentRequest(representationInfo); + } + } + + return request; + } + + function onMediaFragmentLoaded(e) { + var chunk = e.chunk; + if (chunk.streamId !== streamInfo.id || chunk.mediaInfo.type != type) return; + + var bytes = chunk.bytes; + var quality = chunk.quality; + var currentRepresentation = getRepresentationInfo(quality); + + var voRepresentation = representationController && currentRepresentation ? representationController.getRepresentationForQuality(currentRepresentation.quality) : null; + var eventStreamMedia = adapter.getEventsFor(currentRepresentation.mediaInfo); + var eventStreamTrack = adapter.getEventsFor(currentRepresentation, voRepresentation); + + if (eventStreamMedia && eventStreamMedia.length > 0 || eventStreamTrack && eventStreamTrack.length > 0) { + var request = fragmentModel.getRequests({ + state: _modelsFragmentModel2['default'].FRAGMENT_MODEL_EXECUTED, + quality: quality, + index: chunk.index + })[0]; + + var events = handleInbandEvents(bytes, request, eventStreamMedia, eventStreamTrack); + eventBus.trigger(_coreEventsEvents2['default'].INBAND_EVENTS, { sender: instance, events: events }); + } + } + + function handleInbandEvents(data, request, mediaInbandEvents, trackInbandEvents) { + var fragmentStartTime = Math.max(!request || isNaN(request.startTime) ? 0 : request.startTime, 0); + var eventStreams = []; + var events = []; + + /* Extract the possible schemeIdUri : 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 */ + var inbandEvents = mediaInbandEvents.concat(trackInbandEvents); + for (var i = 0, ln = inbandEvents.length; i < ln; i++) { + eventStreams[inbandEvents[i].schemeIdUri + '/' + inbandEvents[i].value] = inbandEvents[i]; + } + + var isoFile = (0, _utilsBoxParser2['default'])(context).getInstance().parse(data); + var eventBoxes = isoFile.getBoxes('emsg'); + + for (var i = 0, ln = eventBoxes.length; i < ln; i++) { + var _event = adapter.getEvent(eventBoxes[i], eventStreams, fragmentStartTime); + + if (_event) { + events.push(_event); + } + } + + return events; } function createBuffer(previousBuffers) { - return getBuffer() || bufferController ? bufferController.createBuffer(mediaInfo, previousBuffers) : null; + return getBuffer() || bufferController ? bufferController.createBuffer(mediaInfoArr, previousBuffers) : null; } function switchTrackAsked() { @@ -30664,39 +31119,35 @@ function StreamProcessor(config) { if (type === _constantsConstants2['default'].VIDEO || type === _constantsConstants2['default'].AUDIO) { controller = (0, _controllersBufferController2['default'])(context).create({ - streamId: streamInfo.id, + streamInfo: streamInfo, type: type, - dashMetrics: dashMetrics, mediaPlayerModel: mediaPlayerModel, manifestModel: manifestModel, fragmentModel: fragmentModel, errHandler: errHandler, - streamController: streamController, mediaController: mediaController, + representationController: representationController, adapter: adapter, textController: textController, abrController: abrController, playbackController: playbackController, - streamProcessor: instance, settings: settings }); } else { controller = (0, _textTextBufferController2['default'])(context).create({ - streamId: streamInfo.id, + streamInfo: streamInfo, type: type, mimeType: mimeType, - dashMetrics: dashMetrics, mediaPlayerModel: mediaPlayerModel, manifestModel: manifestModel, fragmentModel: fragmentModel, errHandler: errHandler, - streamController: streamController, mediaController: mediaController, + representationController: representationController, adapter: adapter, textController: textController, abrController: abrController, playbackController: playbackController, - streamProcessor: instance, settings: settings }); } @@ -30704,6 +31155,70 @@ function StreamProcessor(config) { return controller; } + function getLiveStartTime() { + if (!isDynamic) return NaN; + if (!liveEdgeFinder) return NaN; + + var liveStartTime = NaN; + var currentRepresentationInfo = getRepresentationInfo(); + var liveEdge = liveEdgeFinder.getLiveEdge(currentRepresentationInfo); + var request = findRequestForLiveEdge(liveEdge, currentRepresentationInfo); + + if (request) { + // When low latency mode is selected but browser doesn't support fetch + // start at the beginning of the segment to avoid consuming the whole buffer + if (settings.get().streaming.lowLatencyEnabled) { + liveStartTime = request.duration < mediaPlayerModel.getLiveDelay() ? request.startTime : request.startTime + request.duration - mediaPlayerModel.getLiveDelay(); + } else { + liveStartTime = request.startTime; + } + } + + return liveStartTime; + } + + function findRequestForLiveEdge(liveEdge, currentRepresentationInfo) { + try { + var request = null; + var liveDelay = playbackController.getLiveDelay(); + var dvrWindowSize = !isNaN(streamInfo.manifestInfo.DVRWindowSize) ? streamInfo.manifestInfo.DVRWindowSize : liveDelay; + var dvrWindowSafetyMargin = 0.1 * dvrWindowSize; + var startTime = undefined; + + // Make sure that we have at least a valid request for the end of the DVR window, otherwise we might try forever + if (!isFinite(dvrWindowSize) || getFragmentRequest(currentRepresentationInfo, liveEdge - dvrWindowSize + dvrWindowSafetyMargin, { + ignoreIsFinished: true + })) { + + // Try to find a request as close as possible to the targeted live edge + while (!request && liveDelay <= dvrWindowSize) { + startTime = liveEdge - liveDelay; + request = getFragmentRequest(currentRepresentationInfo, startTime, { + ignoreIsFinished: true + }); + if (!request) { + liveDelay += 1; // Increase by one second for each iteration + } + } + } + + if (request) { + playbackController.setLiveDelay(liveDelay, true); + } + logger.debug('live edge: ' + liveEdge + ', live delay: ' + liveDelay + ', live target: ' + startTime); + return request; + } catch (e) { + return null; + } + } + + function onSeekTarget(e) { + if (e.mediaType !== type || e.streamId !== streamInfo.id) return; + + setIndexHandlerTime(e.time); + scheduleController.setSeekTarget(e.time); + } + function setIndexHandlerTime(value) { if (indexHandler) { indexHandler.setCurrentTime(value); @@ -30722,9 +31237,7 @@ function StreamProcessor(config) { function getInitRequest(quality) { (0, _utilsSupervisorTools.checkInteger)(quality); - var representation = representationController ? representationController.getRepresentationForQuality(quality) : null; - return indexHandler ? indexHandler.getInitRequest(getMediaInfo(), representation) : null; } @@ -30746,6 +31259,10 @@ function StreamProcessor(config) { return fragRequest; } + function finalisePlayList(time, reason) { + dashMetrics.pushPlayListTraceMetrics(time, reason); + } + instance = { initialize: initialize, isUpdating: isUpdating, @@ -30756,13 +31273,13 @@ function StreamProcessor(config) { getRepresentationController: getRepresentationController, getRepresentationInfo: getRepresentationInfo, getBufferLevel: getBufferLevel, - switchInitData: switchInitData, isBufferingCompleted: isBufferingCompleted, createBuffer: createBuffer, updateStreamInfo: updateStreamInfo, getStreamInfo: getStreamInfo, selectMediaInfo: selectMediaInfo, addMediaInfo: addMediaInfo, + getLiveStartTime: getLiveStartTime, switchTrackAsked: switchTrackAsked, getMediaInfoArr: getMediaInfoArr, getMediaInfo: getMediaInfo, @@ -30776,6 +31293,7 @@ function StreamProcessor(config) { resetIndexHandler: resetIndexHandler, getInitRequest: getInitRequest, getFragmentRequest: getFragmentRequest, + finalisePlayList: finalisePlayList, reset: reset }; @@ -30783,11 +31301,12 @@ function StreamProcessor(config) { return instance; } + StreamProcessor.__dashjs_factory_name = 'StreamProcessor'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(StreamProcessor); module.exports = exports['default']; -},{"108":108,"114":114,"120":120,"200":200,"218":218,"219":219,"221":221,"226":226,"45":45,"46":46,"47":47,"51":51,"54":54,"57":57,"61":61,"62":62}],107:[function(_dereq_,module,exports){ +},{"108":108,"109":109,"114":114,"120":120,"149":149,"199":199,"206":206,"215":215,"217":217,"218":218,"220":220,"225":225,"227":227,"243":243,"45":45,"46":46,"47":47,"51":51,"54":54,"57":57,"61":61,"62":62}],107:[function(_dereq_,module,exports){ /** * 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 @@ -30826,7 +31345,7 @@ Object.defineProperty(exports, '__esModule', { function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -var _voDashJSError = _dereq_(226); +var _voDashJSError = _dereq_(225); var _voDashJSError2 = _interopRequireDefault(_voDashJSError); @@ -30834,9 +31353,9 @@ var _netURLLoader = _dereq_(158); var _netURLLoader2 = _interopRequireDefault(_netURLLoader); -var _voMetricsHTTPRequest = _dereq_(242); +var _voMetricsHTTPRequest = _dereq_(241); -var _voTextRequest = _dereq_(233); +var _voTextRequest = _dereq_(232); var _voTextRequest2 = _interopRequireDefault(_voTextRequest); @@ -30923,7 +31442,7 @@ XlinkLoader.__dashjs_factory_name = 'XlinkLoader'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(XlinkLoader); module.exports = exports['default']; -},{"158":158,"226":226,"233":233,"242":242,"46":46,"47":47,"51":51,"54":54}],108:[function(_dereq_,module,exports){ +},{"158":158,"225":225,"232":232,"241":241,"46":46,"47":47,"51":51,"54":54}],108:[function(_dereq_,module,exports){ /** * 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 @@ -31343,7 +31862,7 @@ var _constantsMetricsConstants = _dereq_(109); var _constantsMetricsConstants2 = _interopRequireDefault(_constantsMetricsConstants); -var _voBitrateInfo = _dereq_(225); +var _voBitrateInfo = _dereq_(224); var _voBitrateInfo2 = _interopRequireDefault(_voBitrateInfo); @@ -31387,9 +31906,9 @@ var _coreDebug = _dereq_(45); var _coreDebug2 = _interopRequireDefault(_coreDebug); -var _voMetricsHTTPRequest = _dereq_(242); +var _voMetricsHTTPRequest = _dereq_(241); -var _utilsSupervisorTools = _dereq_(219); +var _utilsSupervisorTools = _dereq_(218); var DEFAULT_VIDEO_BITRATE = 1000; var DEFAULT_AUDIO_BITRATE = 100; @@ -32012,7 +32531,7 @@ _coreFactoryMaker2['default'].updateSingletonFactory(AbrController.__dashjs_fact exports['default'] = factory; module.exports = exports['default']; -},{"108":108,"109":109,"149":149,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"219":219,"225":225,"242":242,"45":45,"46":46,"47":47,"54":54}],112:[function(_dereq_,module,exports){ +},{"108":108,"109":109,"149":149,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"218":218,"224":224,"241":241,"45":45,"46":46,"47":47,"54":54}],112:[function(_dereq_,module,exports){ /** * 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 @@ -32056,11 +32575,11 @@ var _modelsBaseURLTreeModel = _dereq_(147); var _modelsBaseURLTreeModel2 = _interopRequireDefault(_modelsBaseURLTreeModel); -var _utilsBaseURLSelector = _dereq_(206); +var _utilsBaseURLSelector = _dereq_(205); var _utilsBaseURLSelector2 = _interopRequireDefault(_utilsBaseURLSelector); -var _utilsURLUtils = _dereq_(221); +var _utilsURLUtils = _dereq_(220); var _utilsURLUtils2 = _interopRequireDefault(_utilsURLUtils); @@ -32180,7 +32699,7 @@ BaseURLController.__dashjs_factory_name = 'BaseURLController'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(BaseURLController); module.exports = exports['default']; -},{"147":147,"206":206,"221":221,"46":46,"47":47,"54":54,"85":85}],113:[function(_dereq_,module,exports){ +},{"147":147,"205":205,"220":220,"46":46,"47":47,"54":54,"85":85}],113:[function(_dereq_,module,exports){ /** * 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 @@ -32359,10 +32878,6 @@ var _coreEventsEvents = _dereq_(54); var _coreEventsEvents2 = _interopRequireDefault(_coreEventsEvents); -var _utilsBoxParser = _dereq_(207); - -var _utilsBoxParser2 = _interopRequireDefault(_utilsBoxParser); - var _coreFactoryMaker = _dereq_(47); var _coreFactoryMaker2 = _interopRequireDefault(_coreFactoryMaker); @@ -32371,11 +32886,11 @@ var _coreDebug = _dereq_(45); var _coreDebug2 = _interopRequireDefault(_coreDebug); -var _utilsInitCache = _dereq_(214); +var _utilsInitCache = _dereq_(213); var _utilsInitCache2 = _interopRequireDefault(_utilsInitCache); -var _voDashJSError = _dereq_(226); +var _voDashJSError = _dereq_(225); var _voDashJSError2 = _interopRequireDefault(_voDashJSError); @@ -32383,7 +32898,7 @@ var _coreErrorsErrors = _dereq_(51); var _coreErrorsErrors2 = _interopRequireDefault(_coreErrorsErrors); -var _voMetricsHTTPRequest = _dereq_(242); +var _voMetricsHTTPRequest = _dereq_(241); var STALL_THRESHOLD = 0.5; var BUFFER_END_THRESHOLD = 0.5; @@ -32397,18 +32912,16 @@ function BufferController(config) { config = config || {}; var context = this.context; var eventBus = (0, _coreEventBus2['default'])(context).getInstance(); - var dashMetrics = config.dashMetrics; var errHandler = config.errHandler; var fragmentModel = config.fragmentModel; - var streamController = config.streamController; + var representationController = config.representationController; var mediaController = config.mediaController; var adapter = config.adapter; var textController = config.textController; var abrController = config.abrController; var playbackController = config.playbackController; - var streamId = config.streamId; + var streamInfo = config.streamInfo; var type = config.type; - var streamProcessor = config.streamProcessor; var settings = config.settings; var instance = undefined, @@ -32428,10 +32941,10 @@ function BufferController(config) { isPruningInProgress = undefined, isQuotaExceeded = undefined, initCache = undefined, - seekStartTime = undefined, + seekTarget = undefined, seekClearedBufferingCompleted = undefined, pendingPruningRanges = undefined, - bufferResetInProgress = undefined, + replacingBuffer = undefined, mediaChunk = undefined; function setup() { @@ -32467,8 +32980,13 @@ function BufferController(config) { eventBus.on(_coreEventsEvents2['default'].SOURCEBUFFER_REMOVE_COMPLETED, onRemoved, this); } - function createBuffer(mediaInfo, oldBuffers) { - if (!initCache || !mediaInfo || !streamProcessor) return null; + function getRepresentationInfo(quality) { + return adapter.convertDataToRepresentationInfo(representationController.getRepresentationForQuality(quality)); + } + + function createBuffer(mediaInfoArr, oldBuffers) { + if (!initCache || !mediaInfoArr) return null; + var mediaInfo = mediaInfoArr[0]; if (mediaSource) { try { if (oldBuffers && oldBuffers[type]) { @@ -32477,7 +32995,7 @@ function BufferController(config) { buffer = (0, _SourceBufferSink2['default'])(context).create(mediaSource, mediaInfo, onAppended.bind(this), settings.get().streaming.useAppendWindowEnd); } if (typeof buffer.getBuffer().initialize === 'function') { - buffer.getBuffer().initialize(type, streamProcessor); + buffer.getBuffer().initialize(type, streamInfo, mediaInfoArr, fragmentModel); } } catch (e) { logger.fatal('Caught error on create SourceBuffer: ' + e); @@ -32486,7 +33004,7 @@ function BufferController(config) { } else { buffer = (0, _PreBufferSink2['default'])(context).create(onAppended.bind(this)); } - updateBufferTimestampOffset(streamProcessor.getRepresentationInfo(requiredQuality).MSETimeOffset); + updateBufferTimestampOffset(this.getRepresentationInfo(requiredQuality)); return buffer; } @@ -32523,12 +33041,8 @@ function BufferController(config) { } } - function isActive() { - return streamProcessor && streamController && streamProcessor.getStreamInfo(); - } - function onInitFragmentLoaded(e) { - if (e.chunk.streamId !== streamId || e.chunk.mediaInfo.type !== type) return; + if (e.chunk.streamId !== streamInfo.id || e.chunk.mediaInfo.type !== type) return; logger.info('Init fragment finished loading saving to', type + '\'s init cache'); initCache.save(e.chunk); @@ -32536,41 +33050,26 @@ function BufferController(config) { appendToBuffer(e.chunk); } - function switchInitData(streamId, representationId, bufferResetEnabled) { - var chunk = initCache.extract(streamId, representationId); - bufferResetInProgress = bufferResetEnabled === true ? bufferResetEnabled : false; - if (chunk) { - logger.info('Append Init fragment', type, ' with representationId:', chunk.representationId, ' and quality:', chunk.quality, ', data size:', chunk.bytes.byteLength); - appendToBuffer(chunk); - } else { - eventBus.trigger(_coreEventsEvents2['default'].INIT_REQUESTED, { mediaType: type, sender: instance }); + function appendInitSegment(representationId) { + // Get init segment from cache + var chunk = initCache.extract(streamInfo.id, representationId); + + if (!chunk) { + // Init segment not in cache, shall be requested + return false; } + + // Append init segment into buffer + logger.info('Append Init fragment', type, ' with representationId:', chunk.representationId, ' and quality:', chunk.quality, ', data size:', chunk.bytes.byteLength); + appendToBuffer(chunk); + return true; } function onMediaFragmentLoaded(e) { - if (e.chunk.streamId !== streamId || e.chunk.mediaInfo.type !== type) return; - var chunk = e.chunk; - var bytes = chunk.bytes; - var quality = chunk.quality; - var currentRepresentation = streamProcessor.getRepresentationInfo(quality); - var representationController = streamProcessor.getRepresentationController(); - var voRepresentation = representationController && currentRepresentation ? representationController.getRepresentationForQuality(currentRepresentation.quality) : null; - var eventStreamMedia = adapter.getEventsFor(currentRepresentation.mediaInfo); - var eventStreamTrack = adapter.getEventsFor(currentRepresentation, voRepresentation); + if (chunk.streamId !== streamInfo.id || chunk.mediaInfo.type != type) return; - if (eventStreamMedia && eventStreamMedia.length > 0 || eventStreamTrack && eventStreamTrack.length > 0) { - var request = fragmentModel.getRequests({ - state: _modelsFragmentModel2['default'].FRAGMENT_MODEL_EXECUTED, - quality: quality, - index: chunk.index - })[0]; - - var events = handleInbandEvents(bytes, request, eventStreamMedia, eventStreamTrack); - eventBus.trigger(_coreEventsEvents2['default'].INBAND_EVENTS, { sender: instance, streamInfo: streamProcessor.getStreamInfo(), events: events }); - } - - if (bufferResetInProgress) { + if (replacingBuffer) { mediaChunk = chunk; var ranges = buffer && buffer.getAllBufferRanges(); if (ranges && ranges.length > 0 && playbackController.getTimeToStreamEnd() > STALL_THRESHOLD) { @@ -32590,14 +33089,14 @@ function BufferController(config) { buffer.append(chunk); if (chunk.mediaInfo.type === _constantsConstants2['default'].VIDEO) { - eventBus.trigger(_coreEventsEvents2['default'].VIDEO_CHUNK_RECEIVED, { chunk: chunk }); + triggerEvent(_coreEventsEvents2['default'].VIDEO_CHUNK_RECEIVED, { chunk: chunk }); } } function showBufferRanges(ranges) { if (ranges && ranges.length > 0) { for (var i = 0, len = ranges.length; i < len; i++) { - logger.debug('Buffered Range', ranges.start(i), ' - ', ranges.end(i), ' currentTime = ', playbackController.getTime()); + logger.debug('Buffered range: ' + ranges.start(i) + ' - ' + ranges.end(i) + ', currentTime = ', playbackController.getTime()); } } } @@ -32620,7 +33119,7 @@ function BufferController(config) { } if (e.error.code === QUOTA_EXCEEDED_ERROR_CODE || !hasEnoughSpaceToAppend()) { logger.warn('Clearing playback buffer to overcome quota exceed situation'); - eventBus.trigger(_coreEventsEvents2['default'].QUOTA_EXCEEDED, { sender: instance, criticalBufferLevel: criticalBufferLevel }); //Tells ScheduleController to stop scheduling. + triggerEvent(_coreEventsEvents2['default'].QUOTA_EXCEEDED, { criticalBufferLevel: criticalBufferLevel }); //Tells ScheduleController to stop scheduling. pruneAllSafely(); // Then we clear the buffer and onCleared event will tell ScheduleController to start scheduling again. } return; @@ -32637,40 +33136,70 @@ function BufferController(config) { if (appendedBytesInfo.segmentType === _voMetricsHTTPRequest.HTTPRequest.MEDIA_SEGMENT_TYPE) { showBufferRanges(ranges); onPlaybackProgression(); - } else { - if (bufferResetInProgress) { - var currentTime = playbackController.getTime(); - logger.debug('AppendToBuffer seek target should be ' + currentTime); - streamProcessor.getScheduleController().setSeekTarget(currentTime); - streamProcessor.setIndexHandlerTime(currentTime); - } + adjustSeekTarget(); + } else if (replacingBuffer) { + // When replacing buffer due to switch track, and once new initialization segment has been appended + // (and previous buffered data removed) then seek stream to current time + var currentTime = playbackController.getTime(); + logger.debug('AppendToBuffer seek target should be ' + currentTime); + triggerEvent(_coreEventsEvents2['default'].SEEK_TARGET, { time: currentTime }); } - var dataEvent = { - sender: instance, - quality: appendedBytesInfo.quality, - startTime: appendedBytesInfo.start, - index: appendedBytesInfo.index, - bufferedRanges: ranges - }; - if (appendedBytesInfo && !appendedBytesInfo.endFragment) { - eventBus.trigger(_coreEventsEvents2['default'].BYTES_APPENDED, dataEvent); - } else if (appendedBytesInfo) { - eventBus.trigger(_coreEventsEvents2['default'].BYTES_APPENDED_END_FRAGMENT, dataEvent); + if (appendedBytesInfo) { + triggerEvent(appendedBytesInfo.endFragment ? _coreEventsEvents2['default'].BYTES_APPENDED_END_FRAGMENT : _coreEventsEvents2['default'].BYTES_APPENDED, { + quality: appendedBytesInfo.quality, + startTime: appendedBytesInfo.start, + index: appendedBytesInfo.index, + bufferedRanges: ranges, + mediaType: type + }); + } + } + + function adjustSeekTarget() { + // Check buffered data only for audio and video + if (type !== _constantsConstants2['default'].AUDIO && type !== _constantsConstants2['default'].VIDEO) return; + if (isNaN(seekTarget)) return; + + // Check if current buffered range already contains seek target (and current video element time) + var currentTime = playbackController.getTime(); + var range = getRangeAt(seekTarget, 0); + if (currentTime === seekTarget && range) return; + + // Get buffered range corresponding to the seek target + var segmentDuration = representationController.getCurrentRepresentation().segmentDuration; + range = getRangeAt(seekTarget, segmentDuration); + if (!range) return; + + if (Math.abs(currentTime - seekTarget) > segmentDuration) { + // If current video model time is decorrelated from seek target (and appended buffer) then seek video element + // (in case of live streams on some browsers/devices for which we can't set video element time at unavalaible range) + + // Check if appended segment is not anterior from seek target (segments timeline/template tolerance) + if (seekTarget <= range.end) { + // Seek video element to seek target or range start if appended buffer starts after seek target (segments timeline/template tolerance) + playbackController.seek(Math.max(seekTarget, range.start), false, true); + seekTarget = NaN; + } + } else if (currentTime < range.start) { + // If appended buffer starts after seek target (segments timeline/template tolerance) then seek to range start + playbackController.seek(range.start, false, true); + seekTarget = NaN; } } function onQualityChanged(e) { - if (requiredQuality === e.newQuality || type !== e.mediaType || streamProcessor.getStreamInfo().id !== e.streamInfo.id) return; + if (e.streamInfo.id != streamInfo.id || e.mediaType !== type || requiredQuality === e.newQuality) return; - updateBufferTimestampOffset(streamProcessor.getRepresentationInfo(e.newQuality).MSETimeOffset); + updateBufferTimestampOffset(this.getRepresentationInfo(e.newQuality)); requiredQuality = e.newQuality; } //********************************************************************** // START Buffer Level, State & Sufficiency Handling. //********************************************************************** - function onPlaybackSeeking() /*e*/{ + function onPlaybackSeeking(e) { + seekTarget = e.seekTime; if (isBufferingCompleted) { seekClearedBufferingCompleted = true; isBufferingCompleted = false; @@ -32686,7 +33215,7 @@ function BufferController(config) { } function onPlaybackSeeked() { - setSeekStartTime(undefined); + seekTarget = NaN; } // Prune full buffer but what is around current time position @@ -32766,23 +33295,12 @@ function BufferController(config) { } function getWorkingTime() { - // This function returns current working time for buffer (either start time or current time if playback has started) - var ret = playbackController.getTime(); - - if (seekStartTime) { - // if there is a seek start time, the first buffer data will be available on maximum value between first buffer range value and seek start time. - var ranges = buffer.getAllBufferRanges(); - if (ranges && ranges.length) { - ret = Math.max(ranges.start(0), seekStartTime); - } - } - return ret; + return isNaN(seekTarget) ? playbackController.getTime() : seekTarget; } function onPlaybackProgression() { - if (!bufferResetInProgress || type === _constantsConstants2['default'].FRAGMENTED_TEXT && textController.isTextEnabled()) { + if (!replacingBuffer || type === _constantsConstants2['default'].FRAGMENTED_TEXT && textController.isTextEnabled()) { updateBufferLevel(); - addBufferMetrics(); } } @@ -32791,9 +33309,7 @@ function BufferController(config) { } function onPlaybackPlaying() { - if (seekStartTime !== undefined) { - setSeekStartTime(undefined); - } + seekTarget = NaN; checkIfSufficientBuffer(); } @@ -32807,7 +33323,7 @@ function BufferController(config) { var len = undefined, i = undefined; - var toler = tolerance || 0.15; + var toler = !isNaN(tolerance) ? tolerance : 0.15; if (ranges !== null && ranges !== undefined) { for (i = 0, len = ranges.length; i < len; i++) { @@ -32864,23 +33380,17 @@ function BufferController(config) { function updateBufferLevel() { if (playbackController) { bufferLevel = getBufferLength(getWorkingTime() || 0); - eventBus.trigger(_coreEventsEvents2['default'].BUFFER_LEVEL_UPDATED, { sender: instance, bufferLevel: bufferLevel }); + triggerEvent(_coreEventsEvents2['default'].BUFFER_LEVEL_UPDATED, { bufferLevel: bufferLevel }); checkIfSufficientBuffer(); } } - function addBufferMetrics() { - if (!isActive()) return; - dashMetrics.addBufferState(type, bufferState, streamProcessor.getScheduleController().getBufferTarget()); - dashMetrics.addBufferLevel(type, new Date(), bufferLevel * 1000); - } - function checkIfBufferingCompleted() { var isLastIdxAppended = maxAppendedIndex >= lastIndex - 1; // Handles 0 and non 0 based request index if (isLastIdxAppended && !isBufferingCompleted && buffer.discharge === undefined) { isBufferingCompleted = true; logger.debug('checkIfBufferingCompleted trigger BUFFERING_COMPLETED'); - eventBus.trigger(_coreEventsEvents2['default'].BUFFERING_COMPLETED, { sender: instance, streamInfo: streamProcessor.getStreamInfo() }); + triggerEvent(_coreEventsEvents2['default'].BUFFERING_COMPLETED); } } @@ -32892,7 +33402,7 @@ function BufferController(config) { seekClearedBufferingCompleted = false; isBufferingCompleted = true; logger.debug('checkIfSufficientBuffer trigger BUFFERING_COMPLETED'); - eventBus.trigger(_coreEventsEvents2['default'].BUFFERING_COMPLETED, { sender: instance, streamInfo: streamProcessor.getStreamInfo() }); + triggerEvent(_coreEventsEvents2['default'].BUFFERING_COMPLETED); } // When the player is working in low latency mode, the buffer is often below STALL_THRESHOLD. @@ -32901,7 +33411,7 @@ function BufferController(config) { if ((!settings.get().streaming.lowLatencyEnabled && bufferLevel < STALL_THRESHOLD || bufferLevel === 0) && !isBufferingCompleted) { notifyBufferStateChanged(_constantsMetricsConstants2['default'].BUFFER_EMPTY); } else { - if (isBufferingCompleted || bufferLevel >= streamProcessor.getStreamInfo().manifestInfo.minBufferTime) { + if (isBufferingCompleted || bufferLevel >= streamInfo.manifestInfo.minBufferTime) { notifyBufferStateChanged(_constantsMetricsConstants2['default'].BUFFER_LOADED); } } @@ -32914,38 +33424,12 @@ function BufferController(config) { } bufferState = state; - addBufferMetrics(); - eventBus.trigger(_coreEventsEvents2['default'].BUFFER_LEVEL_STATE_CHANGED, { sender: instance, state: state, mediaType: type, streamInfo: streamProcessor.getStreamInfo() }); - eventBus.trigger(state === _constantsMetricsConstants2['default'].BUFFER_LOADED ? _coreEventsEvents2['default'].BUFFER_LOADED : _coreEventsEvents2['default'].BUFFER_EMPTY, { mediaType: type }); + triggerEvent(_coreEventsEvents2['default'].BUFFER_LEVEL_STATE_CHANGED, { state: state }); + triggerEvent(state === _constantsMetricsConstants2['default'].BUFFER_LOADED ? _coreEventsEvents2['default'].BUFFER_LOADED : _coreEventsEvents2['default'].BUFFER_EMPTY); logger.debug(state === _constantsMetricsConstants2['default'].BUFFER_LOADED ? 'Got enough buffer to start' : 'Waiting for more buffer before starting playback'); } - function handleInbandEvents(data, request, mediaInbandEvents, trackInbandEvents) { - var fragmentStartTime = Math.max(!request || isNaN(request.startTime) ? 0 : request.startTime, 0); - var eventStreams = []; - var events = []; - - /* Extract the possible schemeIdUri : 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 */ - var inbandEvents = mediaInbandEvents.concat(trackInbandEvents); - for (var i = 0, ln = inbandEvents.length; i < ln; i++) { - eventStreams[inbandEvents[i].schemeIdUri + '/' + inbandEvents[i].value] = inbandEvents[i]; - } - - var isoFile = (0, _utilsBoxParser2['default'])(context).getInstance().parse(data); - var eventBoxes = isoFile.getBoxes('emsg'); - - for (var i = 0, ln = eventBoxes.length; i < ln; i++) { - var _event = adapter.getEvent(eventBoxes[i], eventStreams, fragmentStartTime); - - if (_event) { - events.push(_event); - } - } - - return events; - } - /* prune buffer on our own in background to avoid browsers pruning buffer silently */ function pruneBuffer() { if (!buffer || type === _constantsConstants2['default'].FRAGMENTED_TEXT) { @@ -33050,10 +33534,6 @@ function BufferController(config) { if (currentTime < range.end) { isBufferingCompleted = false; maxAppendedIndex = 0; - if (!bufferResetInProgress) { - streamProcessor.getScheduleController().setSeekTarget(currentTime); - streamProcessor.setIndexHandlerTime(currentTime); - } } buffer.remove(range.start, range.end, range.force); @@ -33073,49 +33553,56 @@ function BufferController(config) { if (e.unintended) { logger.warn('Detected unintended removal from:', e.from, 'to', e.to, 'setting index handler time to', e.from); - streamProcessor.setIndexHandlerTime(e.from); + triggerEvent(_coreEventsEvents2['default'].SEEK_TARGET, { time: e.from }); } if (isPruningInProgress) { clearNextRange(); } else { - if (!bufferResetInProgress) { - logger.debug('onRemoved : call updateBufferLevel'); + if (!replacingBuffer) { updateBufferLevel(); - addBufferMetrics(); } else { - bufferResetInProgress = false; + replacingBuffer = false; if (mediaChunk) { appendToBuffer(mediaChunk); } } - eventBus.trigger(_coreEventsEvents2['default'].BUFFER_CLEARED, { sender: instance, from: e.from, to: e.to, unintended: e.unintended, hasEnoughSpaceToAppend: hasEnoughSpaceToAppend(), quotaExceeded: isQuotaExceeded }); + triggerEvent(_coreEventsEvents2['default'].BUFFER_CLEARED, { + from: e.from, + to: e.to, + unintended: e.unintended, + hasEnoughSpaceToAppend: hasEnoughSpaceToAppend(), + quotaExceeded: isQuotaExceeded }); } //TODO - REMEMBER removed a timerout hack calling clearBuffer after manifestInfo.minBufferTime * 1000 if !hasEnoughSpaceToAppend() Aug 04 2016 } - function updateBufferTimestampOffset(MSETimeOffset) { + function updateBufferTimestampOffset(representationInfo) { + if (!representationInfo || representationInfo.MSETimeOffset === undefined) return; // Each track can have its own @presentationTimeOffset, so we should set the offset // if it has changed after switching the quality or updating an mpd if (buffer && buffer.updateTimestampOffset) { - buffer.updateTimestampOffset(MSETimeOffset); + buffer.updateTimestampOffset(representationInfo.MSETimeOffset); } } function onDataUpdateCompleted(e) { - if (e.sender.getType() !== streamProcessor.getType() || e.sender.getStreamId() !== streamProcessor.getStreamInfo().id || e.error) return; - updateBufferTimestampOffset(e.currentRepresentation.MSETimeOffset); + if (e.sender.getStreamId() !== streamInfo.id || e.sender.getType() !== type) return; + if (e.error) return; + updateBufferTimestampOffset(e.currentRepresentation); } function onStreamCompleted(e) { - if (e.request.mediaInfo.streamInfo.id !== streamId || e.request.mediaType !== type) return; + if (e.request.mediaInfo.streamInfo.id !== streamInfo.id || e.request.mediaType !== type) return; lastIndex = e.request.index; checkIfBufferingCompleted(); } function onCurrentTrackChanged(e) { + if (e.newMediaInfo.streamInfo.id !== streamInfo.id || e.newMediaInfo.type !== type) return; + var ranges = buffer && buffer.getAllBufferRanges(); - if (!ranges || e.newMediaInfo.type !== type || e.newMediaInfo.streamInfo.id !== streamProcessor.getStreamInfo().id) return; + if (!ranges) return; logger.info('Track change asked'); if (mediaController.getSwitchMode(type) === _MediaController2['default'].TRACK_SWITCH_MODE_ALWAYS_REPLACE) { @@ -33143,14 +33630,6 @@ function BufferController(config) { return type; } - function getStreamProcessor() { - return streamProcessor; - } - - function setSeekStartTime(value) { - seekStartTime = value; - } - function getBuffer() { return buffer; } @@ -33178,6 +33657,10 @@ function BufferController(config) { return mediaSource; } + function replaceBuffer() { + replacingBuffer = true; + } + function getIsBufferingCompleted() { return isBufferingCompleted; } @@ -33206,6 +33689,14 @@ function BufferController(config) { return totalBufferedTime < criticalBufferLevel; } + function triggerEvent(eventType, data) { + var payload = data || {}; + payload.sender = instance; + payload.mediaType = type; + payload.streamId = streamInfo.id; + eventBus.trigger(eventType, payload); + } + function resetInitialSettings(errored, keepBuffers) { criticalBufferLevel = Number.POSITIVE_INFINITY; bufferState = undefined; @@ -33220,6 +33711,7 @@ function BufferController(config) { bufferLevel = 0; wallclockTicked = 0; pendingPruningRanges = []; + seekTarget = NaN; if (buffer) { if (!errored) { @@ -33229,16 +33721,15 @@ function BufferController(config) { buffer = null; } - bufferResetInProgress = false; + replacingBuffer = false; } function reset(errored, keepBuffers) { eventBus.off(_coreEventsEvents2['default'].DATA_UPDATE_COMPLETED, onDataUpdateCompleted, this); - eventBus.off(_coreEventsEvents2['default'].QUALITY_CHANGE_REQUESTED, onQualityChanged, this); eventBus.off(_coreEventsEvents2['default'].INIT_FRAGMENT_LOADED, onInitFragmentLoaded, this); eventBus.off(_coreEventsEvents2['default'].MEDIA_FRAGMENT_LOADED, onMediaFragmentLoaded, this); + eventBus.off(_coreEventsEvents2['default'].QUALITY_CHANGE_REQUESTED, onQualityChanged, this); eventBus.off(_coreEventsEvents2['default'].STREAM_COMPLETED, onStreamCompleted, this); - eventBus.off(_coreEventsEvents2['default'].CURRENT_TRACK_CHANGED, onCurrentTrackChanged, this); eventBus.off(_coreEventsEvents2['default'].PLAYBACK_PLAYING, onPlaybackPlaying, this); eventBus.off(_coreEventsEvents2['default'].PLAYBACK_PROGRESS, onPlaybackProgression, this); eventBus.off(_coreEventsEvents2['default'].PLAYBACK_TIME_UPDATED, onPlaybackProgression, this); @@ -33247,6 +33738,7 @@ function BufferController(config) { eventBus.off(_coreEventsEvents2['default'].PLAYBACK_SEEKED, onPlaybackSeeked, this); eventBus.off(_coreEventsEvents2['default'].PLAYBACK_STALLED, onPlaybackStalled, this); eventBus.off(_coreEventsEvents2['default'].WALLCLOCK_TIME_UPDATED, onWallclockTimeUpdated, this); + eventBus.off(_coreEventsEvents2['default'].CURRENT_TRACK_CHANGED, onCurrentTrackChanged, this); eventBus.off(_coreEventsEvents2['default'].SOURCEBUFFER_REMOVE_COMPLETED, onRemoved, this); resetInitialSettings(errored, keepBuffers); @@ -33254,20 +33746,20 @@ function BufferController(config) { instance = { getBufferControllerType: getBufferControllerType, + getRepresentationInfo: getRepresentationInfo, initialize: initialize, createBuffer: createBuffer, dischargePreBuffer: dischargePreBuffer, getType: getType, - getStreamProcessor: getStreamProcessor, - setSeekStartTime: setSeekStartTime, getBuffer: getBuffer, setBuffer: setBuffer, getBufferLevel: getBufferLevel, getRangeAt: getRangeAt, setMediaSource: setMediaSource, getMediaSource: getMediaSource, + appendInitSegment: appendInitSegment, + replaceBuffer: replaceBuffer, getIsBufferingCompleted: getIsBufferingCompleted, - switchInitData: switchInitData, getIsPruningInProgress: getIsPruningInProgress, reset: reset }; @@ -33280,7 +33772,7 @@ BufferController.__dashjs_factory_name = BUFFER_CONTROLLER_TYPE; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(BufferController); module.exports = exports['default']; -},{"103":103,"104":104,"108":108,"109":109,"111":111,"117":117,"149":149,"207":207,"214":214,"226":226,"242":242,"45":45,"46":46,"47":47,"51":51,"54":54}],115:[function(_dereq_,module,exports){ +},{"103":103,"104":104,"108":108,"109":109,"111":111,"117":117,"149":149,"213":213,"225":225,"241":241,"45":45,"46":46,"47":47,"51":51,"54":54}],115:[function(_dereq_,module,exports){ /** * 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 @@ -33749,7 +34241,7 @@ var _constantsConstants = _dereq_(108); var _constantsConstants2 = _interopRequireDefault(_constantsConstants); -var _voDataChunk = _dereq_(227); +var _voDataChunk = _dereq_(226); var _voDataChunk2 = _interopRequireDefault(_voDataChunk); @@ -33761,7 +34253,7 @@ var _FragmentLoader = _dereq_(97); var _FragmentLoader2 = _interopRequireDefault(_FragmentLoader); -var _utilsRequestModifier = _dereq_(218); +var _utilsRequestModifier = _dereq_(217); var _utilsRequestModifier2 = _interopRequireDefault(_utilsRequestModifier); @@ -33909,7 +34401,7 @@ FragmentController.__dashjs_factory_name = 'FragmentController'; exports['default'] = _coreFactoryMaker2['default'].getClassFactory(FragmentController); module.exports = exports['default']; -},{"108":108,"149":149,"218":218,"227":227,"45":45,"46":46,"47":47,"51":51,"54":54,"97":97}],117:[function(_dereq_,module,exports){ +},{"108":108,"149":149,"217":217,"226":226,"45":45,"46":46,"47":47,"51":51,"54":54,"97":97}],117:[function(_dereq_,module,exports){ /** * 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 @@ -34006,12 +34498,6 @@ function MediaController() { var tracksForType = getTracksFor(type, streamInfo); var tracks = []; - if (type === _constantsConstants2['default'].FRAGMENTED_TEXT) { - // Choose the first track - setTrack(tracksForType[0]); - return; - } - if (!settings) { settings = domStorage.getSavedMediaSettings(type); setInitialSettings(type, settings); @@ -34028,10 +34514,10 @@ function MediaController() { } if (tracks.length === 0) { - setTrack(selectInitialTrack(tracksForType)); + setTrack(selectInitialTrack(type, tracksForType), true); } else { if (tracks.length > 1) { - setTrack(selectInitialTrack(tracks)); + setTrack(selectInitialTrack(type, tracks)); } else { setTrack(tracks[0]); } @@ -34113,9 +34599,10 @@ function MediaController() { /** * @param {MediaInfo} track + * @param {boolean} noSettingsSave specify if settings must be not be saved * @memberof MediaController# */ - function setTrack(track) { + function setTrack(track, noSettingsSave) { if (!track || !track.streamInfo) return; var type = track.type; @@ -34127,28 +34614,31 @@ function MediaController() { tracks[id][type].current = track; - if (tracks[id][type].current) { + if (tracks[id][type].current && !(noSettingsSave && type === _constantsConstants2['default'].FRAGMENTED_TEXT)) { eventBus.trigger(_coreEventsEvents2['default'].CURRENT_TRACK_CHANGED, { oldMediaInfo: current, newMediaInfo: track, switchMode: switchMode[type] }); } - var settings = extractSettings(track); + if (!noSettingsSave) { - if (!settings || !tracks[id][type].storeLastSettings) return; + var settings = extractSettings(track); - if (settings.roles) { - settings.role = settings.roles[0]; - delete settings.roles; - } + if (!settings || !tracks[id][type].storeLastSettings) return; - if (settings.accessibility) { - settings.accessibility = settings.accessibility[0]; - } + if (settings.roles) { + settings.role = settings.roles[0]; + delete settings.roles; + } - if (settings.audioChannelConfiguration) { - settings.audioChannelConfiguration = settings.audioChannelConfiguration[0]; - } + if (settings.accessibility) { + settings.accessibility = settings.accessibility[0]; + } - domStorage.setSavedMediaSettings(type, settings); + if (settings.audioChannelConfiguration) { + settings.audioChannelConfiguration = settings.audioChannelConfiguration[0]; + } + + domStorage.setSavedMediaSettings(type, settings); + } } /** @@ -34173,6 +34663,13 @@ function MediaController() { return initialSettings[type]; } + /** + * @memberof MediaController# + */ + function saveTextSettingsDisabled() { + domStorage.setSavedMediaSettings(_constantsConstants2['default'].FRAGMENTED_TEXT, null); + } + /** * @param {string} type * @param {string} mode @@ -34310,13 +34807,17 @@ function MediaController() { function resetInitialSettings() { initialSettings = { audio: null, - video: null + video: null, + fragmentedText: null }; } - function selectInitialTrack(tracks) { + function selectInitialTrack(type, tracks) { + if (type === _constantsConstants2['default'].FRAGMENTED_TEXT) return tracks[0]; + var mode = getSelectionModeForInitialTrack(); var tmpArr = []; + var getTracksWithHighestBitrate = function getTracksWithHighestBitrate(trackArr) { var max = 0; var result = []; @@ -34424,6 +34925,8 @@ function MediaController() { getSelectionModeForInitialTrack: getSelectionModeForInitialTrack, isMultiTrackSupportedByType: isMultiTrackSupportedByType, isTracksEqual: isTracksEqual, + matchSettings: matchSettings, + saveTextSettingsDisabled: saveTextSettingsDisabled, setConfig: setConfig, reset: reset }; @@ -34657,18 +35160,17 @@ function PlaybackController() { adapter = undefined, videoModel = undefined, timelineConverter = undefined, - liveStartTime = undefined, + streamSwitch = undefined, + streamSeekTime = undefined, wallclockTimeIntervalId = undefined, - earliestTime = undefined, liveDelay = undefined, - bufferedRange = undefined, streamInfo = undefined, isDynamic = undefined, mediaPlayerModel = undefined, playOnceInitialized = undefined, lastLivePlaybackTime = undefined, availabilityStartTime = undefined, - compatibleWithPreviousStream = undefined, + seekTarget = undefined, isLowLatencySeekingInProgress = undefined, playbackStalled = undefined, minPlaybackRateChange = undefined, @@ -34681,14 +35183,14 @@ function PlaybackController() { reset(); } - function initialize(StreamInfo, compatible) { + function initialize(StreamInfo, periodSwitch, seekTime) { streamInfo = StreamInfo; addAllListeners(); isDynamic = streamInfo.manifestInfo.isDynamic; isLowLatencySeekingInProgress = false; playbackStalled = false; - liveStartTime = streamInfo.start; - compatibleWithPreviousStream = compatible; + streamSwitch = periodSwitch === true; + streamSeekTime = seekTime; var ua = typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase() : ''; @@ -34696,11 +35198,10 @@ function PlaybackController() { var isSafari = /safari/.test(ua) && !/chrome/.test(ua); minPlaybackRateChange = isSafari ? 0.25 : 0.02; + eventBus.on(_coreEventsEvents2['default'].STREAM_INITIALIZED, onStreamInitialized, this); eventBus.on(_coreEventsEvents2['default'].DATA_UPDATE_COMPLETED, onDataUpdateCompleted, this); - eventBus.on(_coreEventsEvents2['default'].BYTES_APPENDED_END_FRAGMENT, onBytesAppended, this); eventBus.on(_coreEventsEvents2['default'].LOADING_PROGRESS, onFragmentLoadProgress, this); eventBus.on(_coreEventsEvents2['default'].BUFFER_LEVEL_STATE_CHANGED, onBufferLevelStateChanged, this); - eventBus.on(_coreEventsEvents2['default'].PERIOD_SWITCH_STARTED, onPeriodSwitchStarted, this); eventBus.on(_coreEventsEvents2['default'].PLAYBACK_PROGRESS, onPlaybackProgression, this); eventBus.on(_coreEventsEvents2['default'].PLAYBACK_TIME_UPDATED, onPlaybackProgression, this); eventBus.on(_coreEventsEvents2['default'].PLAYBACK_ENDED, onPlaybackEnded, this); @@ -34712,10 +35213,49 @@ function PlaybackController() { } } - function onPeriodSwitchStarted(e) { - if (!isDynamic && e.fromStreamInfo && earliestTime[e.fromStreamInfo.id] !== undefined) { - delete bufferedRange[e.fromStreamInfo.id]; - delete earliestTime[e.fromStreamInfo.id]; + function onStreamInitialized(e) { + // Seamless period switch + if (streamSwitch && isNaN(streamSeekTime)) return; + + // Seek new stream in priority order: + // - at seek time (streamSeekTime) when switching period + // - at start time provided in URI parameters + // - at stream/period start time (for static streams) or live start time (for dynamic streams) + var startTime = streamSeekTime; + if (isNaN(startTime)) { + if (isDynamic) { + // For dynamic stream, start by default at (live edge - live delay) + startTime = e.liveStartTime; + // If start time in URI, take min value between live edge time and time from URI (capped by DVR window range) + var dvrInfo = dashMetrics.getCurrentDVRInfo(); + var dvrWindow = dvrInfo ? dvrInfo.range : null; + if (dvrWindow) { + // #t shall be relative to period start + var startTimeFromUri = getStartTimeFromUriParameters(streamInfo.start, true); + if (!isNaN(startTimeFromUri)) { + logger.info('Start time from URI parameters: ' + startTimeFromUri); + startTime = Math.max(Math.min(startTime, startTimeFromUri), dvrWindow.start); + } + } + } else { + // For static stream, start by default at period start + startTime = streamInfo.start; + // If start time in URI, take max value between period start and time from URI (if in period range) + var startTimeFromUri = getStartTimeFromUriParameters(streamInfo.start, false); + if (!isNaN(startTimeFromUri) && startTimeFromUri < startTime + streamInfo.duration) { + logger.info('Start time from URI parameters: ' + startTimeFromUri); + startTime = Math.max(startTime, startTimeFromUri); + } + } + } + + if (!isNaN(startTime) && startTime !== videoModel.getTime()) { + // Trigger PLAYBACK_SEEKING event for controllers + eventBus.trigger(_coreEventsEvents2['default'].PLAYBACK_SEEKING, { + seekTime: startTime + }); + // Seek video model + seek(startTime, false, true); } } @@ -34724,9 +35264,7 @@ function PlaybackController() { } function getStreamEndTime() { - var startTime = getStreamStartTime(true); - var offset = isDynamic && streamInfo ? startTime - streamInfo.start : 0; - return startTime + (streamInfo ? streamInfo.duration - offset : offset); + return streamInfo.start + streamInfo.duration; } function play() { @@ -34752,24 +35290,22 @@ function PlaybackController() { } function seek(time, stickToBuffered, internalSeek) { - if (streamInfo && videoModel) { - if (internalSeek === true) { - if (time !== videoModel.getTime()) { - // Internal seek = seek video model only (disable 'seeking' listener), - // buffer(s) are already appended at given time (see onBytesAppended()) - videoModel.removeEventListener('seeking', onPlaybackSeeking); - logger.info('Requesting internal seek to time: ' + time); - videoModel.setCurrentTime(time, stickToBuffered); - } - } else { - eventBus.trigger(_coreEventsEvents2['default'].PLAYBACK_SEEK_ASKED); - if (streamInfo) { - delete bufferedRange[streamInfo.id]; - delete earliestTime[streamInfo.id]; - } - logger.info('Requesting seek to time: ' + time); - videoModel.setCurrentTime(time, stickToBuffered); - } + if (!streamInfo || !videoModel) return; + + var currentTime = !isNaN(seekTarget) ? seekTarget : videoModel.getTime(); + if (time === currentTime) return; + + if (internalSeek === true) { + // Internal seek = seek video model only (disable 'seeking' listener) + // buffer(s) are already appended at requested time + videoModel.removeEventListener('seeking', onPlaybackSeeking); + logger.info('Requesting internal seek to time: ' + time); + videoModel.setCurrentTime(time, stickToBuffered); + } else { + seekTarget = time; + eventBus.trigger(_coreEventsEvents2['default'].PLAYBACK_SEEK_ASKED); + logger.info('Requesting seek to time: ' + time); + videoModel.setCurrentTime(time, stickToBuffered); } } @@ -34817,14 +35353,6 @@ function PlaybackController() { return streamController; } - function setLiveStartTime(value) { - liveStartTime = value; - } - - function getLiveStartTime() { - return liveStartTime; - } - /** * Computes the desirable delay for the live edge to avoid a risk of getting 404 when playing at the bleeding edge * @param {number} fragmentDuration - seconds? @@ -34833,22 +35361,14 @@ function PlaybackController() { * @returns {number} object * @memberof PlaybackController# */ - function computeLiveDelay(fragmentDuration, dvrWindowSize) { - var minBufferTime = arguments.length <= 2 || arguments[2] === undefined ? NaN : arguments[2]; - + function computeAndSetLiveDelay(fragmentDuration, dvrWindowSize, minBufferTime) { var delay = undefined, ret = undefined, - r = undefined, startTime = undefined; var END_OF_PLAYLIST_PADDING = 10; var MIN_BUFFER_TIME_FACTOR = 4; var FRAGMENT_DURATION_FACTOR = 4; - - var uriParameters = uriFragmentModel.getURIFragmentData(); - - if (uriParameters) { - r = parseInt(uriParameters.r, 10); - } + var adjustedFragmentDuration = !isNaN(fragmentDuration) && isFinite(fragmentDuration) ? fragmentDuration : NaN; var suggestedPresentationDelay = adapter.getSuggestedPresentationDelay(); @@ -34856,14 +35376,12 @@ function PlaybackController() { delay = 0; } else if (mediaPlayerModel.getLiveDelay()) { delay = mediaPlayerModel.getLiveDelay(); // If set by user, this value takes precedence - } else if (settings.get().streaming.liveDelayFragmentCount !== null && !isNaN(settings.get().streaming.liveDelayFragmentCount) && !isNaN(fragmentDuration)) { - delay = fragmentDuration * settings.get().streaming.liveDelayFragmentCount; - } else if (r) { - delay = r; + } else if (settings.get().streaming.liveDelayFragmentCount !== null && !isNaN(settings.get().streaming.liveDelayFragmentCount) && !isNaN(adjustedFragmentDuration)) { + delay = adjustedFragmentDuration * settings.get().streaming.liveDelayFragmentCount; } else if (settings.get().streaming.useSuggestedPresentationDelay === true && suggestedPresentationDelay !== null && !isNaN(suggestedPresentationDelay) && suggestedPresentationDelay > 0) { delay = suggestedPresentationDelay; - } else if (!isNaN(fragmentDuration)) { - delay = fragmentDuration * FRAGMENT_DURATION_FACTOR; + } else if (!isNaN(adjustedFragmentDuration)) { + delay = adjustedFragmentDuration * FRAGMENT_DURATION_FACTOR; } else { delay = !isNaN(minBufferTime) ? minBufferTime * MIN_BUFFER_TIME_FACTOR : streamInfo.manifestInfo.minBufferTime * MIN_BUFFER_TIME_FACTOR; } @@ -34891,6 +35409,16 @@ function PlaybackController() { return liveDelay; } + function setLiveDelay(value) { + var useMaxValue = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; + + if (useMaxValue && value < liveDelay) { + return; + } + + liveDelay = value; + } + function getCurrentLiveLatency() { if (!isDynamic || isNaN(availabilityStartTime)) { return NaN; @@ -34905,18 +35433,17 @@ function PlaybackController() { } function reset() { - liveStartTime = NaN; playOnceInitialized = false; - earliestTime = {}; + streamSwitch = false; + streamSeekTime = NaN; liveDelay = 0; availabilityStartTime = 0; - bufferedRange = {}; + seekTarget = NaN; if (videoModel) { + eventBus.off(_coreEventsEvents2['default'].STREAM_INITIALIZED, onStreamInitialized, this); eventBus.off(_coreEventsEvents2['default'].DATA_UPDATE_COMPLETED, onDataUpdateCompleted, this); eventBus.off(_coreEventsEvents2['default'].BUFFER_LEVEL_STATE_CHANGED, onBufferLevelStateChanged, this); - eventBus.off(_coreEventsEvents2['default'].BYTES_APPENDED_END_FRAGMENT, onBytesAppended, this); eventBus.off(_coreEventsEvents2['default'].LOADING_PROGRESS, onFragmentLoadProgress, this); - eventBus.off(_coreEventsEvents2['default'].PERIOD_SWITCH_STARTED, onPeriodSwitchStarted, this); eventBus.off(_coreEventsEvents2['default'].PLAYBACK_PROGRESS, onPlaybackProgression, this); eventBus.off(_coreEventsEvents2['default'].PLAYBACK_TIME_UPDATED, onPlaybackProgression, this); eventBus.off(_coreEventsEvents2['default'].PLAYBACK_ENDED, onPlaybackEnded, this); @@ -34959,63 +35486,23 @@ function PlaybackController() { } } - function getStartTimeFromUriParameters() { + function getStartTimeFromUriParameters(rangeStart, isDynamic) { var fragData = uriFragmentModel.getURIFragmentData(); - var uriParameters = undefined; - if (fragData) { - uriParameters = {}; - var r = parseInt(fragData.r, 10); - if (r >= 0 && streamInfo && r < streamInfo.manifestInfo.DVRWindowSize && fragData.t === null) { - fragData.t = Math.max(Math.floor(Date.now() / 1000) - streamInfo.manifestInfo.DVRWindowSize, streamInfo.manifestInfo.availableFrom.getTime() / 1000 + streamInfo.start) + r; - } - uriParameters.fragS = parseFloat(fragData.s); - uriParameters.fragT = parseFloat(fragData.t); + if (!fragData || !fragData.t) { + return NaN; } - return uriParameters; - } - /** - * @param {boolean} ignoreStartOffset - ignore URL fragment start offset if true - * @param {number} liveEdge - liveEdge value - * @returns {number} object - * @memberof PlaybackController# - */ - function getStreamStartTime(ignoreStartOffset, liveEdge) { - var presentationStartTime = undefined; - var startTimeOffset = NaN; + var startTime = NaN; - if (!ignoreStartOffset) { - var uriParameters = getStartTimeFromUriParameters(); - if (uriParameters) { - startTimeOffset = !isNaN(uriParameters.fragS) ? uriParameters.fragS : uriParameters.fragT; - } else { - startTimeOffset = 0; - } - } else { - startTimeOffset = streamInfo ? streamInfo.start : startTimeOffset; - } + // Consider only start time of MediaRange + // TODO: consider end time of MediaRange to stop playback at provided end time + fragData.t = fragData.t.split(',')[0]; - if (isDynamic) { - if (!isNaN(startTimeOffset) && streamInfo) { - presentationStartTime = startTimeOffset - streamInfo.manifestInfo.availableFrom.getTime() / 1000; + // "t=