diff --git a/src/dash/DashHandler.js b/src/dash/DashHandler.js index e461921c03..4475e50817 100644 --- a/src/dash/DashHandler.js +++ b/src/dash/DashHandler.js @@ -86,7 +86,7 @@ function DashHandler(config) { return currentTime; } - function getCurrentIndex () { + function getCurrentIndex() { return index; } @@ -762,8 +762,8 @@ function DashHandler(config) { frag = segments[i]; ft = frag.presentationStartTime; fd = frag.duration; - epsilon = (timeThreshold === undefined || timeThreshold === null) ? fd / 2 : timeThreshold; - + epsilon = (timeThreshold === undefined || timeThreshold === null) ? 0 : timeThreshold; + //Changing epsilon from fd/2 to 0 but leaving ability to send timeThreshold in to override. May break misaligned demuxed segments. if ((time + epsilon) >= ft && (time - epsilon) < (ft + fd)) { idx = frag.availabilityIdx; @@ -868,9 +868,11 @@ function DashHandler(config) { return null; } - requestedTime = time; + if (requestedTime !== time) { // When playing at live edge with 0 delay we may loop back with same time and index until it is available. Reduces verbosness of logs. + requestedTime = time; + log('Getting the request for ' + type + ' time : ' + time); + } - log('Getting the request for ' + type + ' time : ' + time); index = getIndexForSegments(time, representation, timeThreshold); //Index may be -1 if getSegments needs to update. So after getSegments is called and updated then try to get index again. getSegments(representation); @@ -878,7 +880,9 @@ function DashHandler(config) { index = getIndexForSegments(time, representation, timeThreshold); } - log('Index for ' + type + ' time ' + time + ' is ' + index); + if (requestedTime !== time) { + log('Index for ' + type + ' time ' + time + ' is ' + index ); + } finished = !ignoreIsFinished ? isMediaFinished(representation) : false; if (finished) { @@ -912,8 +916,7 @@ function DashHandler(config) { function getNextSegmentRequest(representation) { var request, segment, - finished, - idx; + finished; if (!representation || index === -1) { return null; @@ -921,21 +924,30 @@ function DashHandler(config) { requestedTime = null; index++; - idx = index; + log('Getting the next request at index: ' + index); finished = isMediaFinished(representation); if (finished) { request = new FragmentRequest(); request.action = FragmentRequest.ACTION_COMPLETE; - request.index = idx; + request.index = index; request.mediaType = type; request.mediaInfo = streamProcessor.getMediaInfo(); log('Signal complete.'); } else { getSegments(representation); - segment = getSegmentByIndex(idx, representation); + segment = getSegmentByIndex(index, representation); request = getRequestForSegment(segment); + if (!segment && isDynamic) { + /* + Sometimes when playing dynamic streams with 0 fragment delay at live edge we ask for + an index before it is available so we decrement index back and send null request + which triggers the validate loop to rerun and the next time the segment should be + available. + */ + index--; + } } return request; diff --git a/src/streaming/ManifestUpdater.js b/src/streaming/ManifestUpdater.js index 65d218e871..20e4cd7df9 100644 --- a/src/streaming/ManifestUpdater.js +++ b/src/streaming/ManifestUpdater.js @@ -114,7 +114,7 @@ function ManifestUpdater() { var date = new Date(); manifestModel.setValue(manifest); - log('Manifest has been refreshed at ' + date + '[' + date.getTime() + '] '); + log('Manifest has been refreshed at ' + date + '[' + date.getTime() / 1000 + '] '); delay = manifestExt.getRefreshDelay(manifest); timeSinceLastUpdate = (new Date().getTime() - manifest.loadedTime.getTime()) / 1000; diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index c28265bf6a..d92d2284d9 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -71,6 +71,7 @@ import MediaPlayerFactory from '../streaming/MediaPlayerFactory.js'; /** * @Module MediaPlayer + * @return {{initialize: initialize, on: module.on, off: module.off, extend: extend, attachView: module.attachView, attachSource: module.attachSource, isReady: module.isReady, play: module.play, isPaused: isPaused, pause: pause, isSeeking: isSeeking, seek: module.seek, setMute: setMute, isMuted: isMuted, setVolume: setVolume, getVolume: getVolume, time: module.time, duration: module.duration, timeAsUTC: module.timeAsUTC, durationAsUTC: module.durationAsUTC, getDVRWindowSize: module.getDVRWindowSize, getDVRSeekOffset: module.getDVRSeekOffset, convertToTimeCode: module.convertToTimeCode, formatUTC: module.formatUTC, getVersion: module.getVersion, getDebug: module.getDebug, getVideoModel: module.getVideoModel, getVideoContainer: module.getVideoContainer, setLiveDelayFragmentCount: module.setLiveDelayFragmentCount, useSuggestedPresentationDelay: module.useSuggestedPresentationDelay, enableLastBitrateCaching: module.enableLastBitrateCaching, enableLastMediaSettingsCaching: module.enableLastMediaSettingsCaching, setMaxAllowedBitrateFor: module.setMaxAllowedBitrateFor, getMaxAllowedBitrateFor: module.getMaxAllowedBitrateFor, setMaxAllowedRepresentationRatioFor: module.setMaxAllowedRepresentationRatioFor, getMaxAllowedRepresentationRatioFor: module.getMaxAllowedRepresentationRatioFor, setAutoPlay: module.setAutoPlay, getAutoPlay: module.getAutoPlay, setScheduleWhilePaused: module.setScheduleWhilePaused, getScheduleWhilePaused: module.getScheduleWhilePaused, getMetricsExt: module.getMetricsExt, getMetricsFor: module.getMetricsFor, getQualityFor: module.getQualityFor, setQualityFor: module.setQualityFor, getLimitBitrateByPortal: module.getLimitBitrateByPortal, setLimitBitrateByPortal: module.setLimitBitrateByPortal, setTextTrack: module.setTextTrack, getBitrateInfoListFor: module.getBitrateInfoListFor, setInitialBitrateFor: module.setInitialBitrateFor, getInitialBitrateFor: module.getInitialBitrateFor, setInitialRepresentationRatioFor: module.setInitialRepresentationRatioFor, getInitialRepresentationRatioFor: module.getInitialRepresentationRatioFor, getStreamsFromManifest: module.getStreamsFromManifest, getTracksFor: module.getTracksFor, getTracksForTypeFromManifest: module.getTracksForTypeFromManifest, getCurrentTrackFor: module.getCurrentTrackFor, setInitialMediaSettingsFor: module.setInitialMediaSettingsFor, getInitialMediaSettingsFor: module.getInitialMediaSettingsFor, setCurrentTrack: module.setCurrentTrack, getTrackSwitchModeFor: module.getTrackSwitchModeFor, setTrackSwitchModeFor: module.setTrackSwitchModeFor, setSelectionModeForInitialTrack: module.setSelectionModeForInitialTrack, getSelectionModeForInitialTrack: module.getSelectionModeForInitialTrack, getAutoSwitchQuality: module.getAutoSwitchQuality, setAutoSwitchQuality: module.setAutoSwitchQuality, getAutoSwitchQualityFor: module.getAutoSwitchQualityFor, setAutoSwitchQualityFor: module.setAutoSwitchQualityFor, setBandwidthSafetyFactor: module.setBandwidthSafetyFactor, getBandwidthSafetyFactor: module.getBandwidthSafetyFactor, setAbandonLoadTimeout: module.setAbandonLoadTimeout, retrieveManifest: module.retrieveManifest, addUTCTimingSource: module.addUTCTimingSource, removeUTCTimingSource: module.removeUTCTimingSource, clearDefaultUTCTimingSources: module.clearDefaultUTCTimingSources, restoreDefaultUTCTimingSources: module.restoreDefaultUTCTimingSources, setBufferToKeep: module.setBufferToKeep, setBufferPruningInterval: module.setBufferPruningInterval, setStableBufferTime: module.setStableBufferTime, setBufferTimeAtTopQuality: module.setBufferTimeAtTopQuality, setFragmentLoaderRetryAttempts: module.setFragmentLoaderRetryAttempts, setFragmentLoaderRetryInterval: module.setFragmentLoaderRetryInterval, setBufferTimeAtTopQualityLongForm: module.setBufferTimeAtTopQualityLongForm, setLongFormContentDurationThreshold: module.setLongFormContentDurationThreshold, setRichBufferThreshold: module.setRichBufferThreshold, getProtectionController: module.getProtectionController, attachProtectionController: module.attachProtectionController, setProtectionData: module.setProtectionData, enableManifestDateHeaderTimeSource: module.enableManifestDateHeaderTimeSource, displayCaptionsOnTop: module.displayCaptionsOnTop, attachVideoContainer: module.attachVideoContainer, attachTTMLRenderingDiv: module.attachTTMLRenderingDiv, reset: module.reset}|*} */ function MediaPlayer() { @@ -153,7 +154,7 @@ function MediaPlayer() { attachSource(source); } - log('[dash.js ' + VERSION + '] ' + 'new MediaPlayer instance has been created'); + log('[dash.js ' + VERSION + '] ' + 'MediaPlayer has been initialized'); } /** @@ -452,7 +453,8 @@ function MediaPlayer() { } /** - * TODO Need Docs + * @memberof module:MediaPlayer + * @instance */ function extend(parentNameString, childInstance, override) { FactoryMaker.extend(parentNameString, childInstance, override, context); diff --git a/src/streaming/controllers/ScheduleController.js b/src/streaming/controllers/ScheduleController.js index 3ee6ff652b..e73f1914e7 100644 --- a/src/streaming/controllers/ScheduleController.js +++ b/src/streaming/controllers/ScheduleController.js @@ -58,7 +58,6 @@ function ScheduleController(config) { let mediaPlayerModel = config.mediaPlayerModel; let instance, - fragmentsToLoad, type, ready, fragmentModel, @@ -83,7 +82,6 @@ function ScheduleController(config) { function setup() { - fragmentsToLoad = 0; initialPlayback = true; isStopped = false; playListMetrics = null; @@ -143,7 +141,7 @@ function ScheduleController(config) { } } - function doStart() { + function start() { if (!ready) return; isStopped = false; if (initialPlayback) { @@ -162,10 +160,10 @@ function ScheduleController(config) { addPlaylistMetrics(PlayList.INITIAL_PLAY_START_REASON); } - doStart(); + start(); } - function doStop() { + function stop() { if (isStopped) return; isStopped = true; log('Schedule controller stopping for ' + type); @@ -202,36 +200,30 @@ function ScheduleController(config) { function validate() { if (isStopped || (playbackController.isPaused() && (playbackController.getPlayedRanges().length > 0) && !scheduleWhilePaused)) return; - getRequiredFragmentCount(onGetRequiredFragmentCount); + getRequiredFragmentCount(); //log("validate", type); } - function getRequiredFragmentCount(callback) { - var rules = scheduleRulesCollection.getRules(ScheduleRulesCollection.FRAGMENTS_TO_SCHEDULE_RULES); - - rulesController.applyRules(rules, streamProcessor, callback, fragmentsToLoad, function (currentValue, newValue) { + function getRequiredFragmentCount() { + let rules = scheduleRulesCollection.getRules(ScheduleRulesCollection.FRAGMENTS_TO_SCHEDULE_RULES); + rulesController.applyRules(rules, streamProcessor, onGetRequiredFragmentCount, 0, function (currentValue, newValue) { currentValue = currentValue === SwitchRequest.NO_CHANGE ? 0 : currentValue; return Math.max(currentValue, newValue); }); } function onGetRequiredFragmentCount(result) { - fragmentsToLoad = result.value; - if (fragmentsToLoad > 0 && !isFragmentLoading && (manifestExt.getIsTextTrack(type) || !bufferController.getIsAppendingInProgress())) { + if (result.value === 1 && !isFragmentLoading && (manifestExt.getIsTextTrack(type) || !bufferController.getIsAppendingInProgress())) { isFragmentLoading = true; - abrController.getPlaybackQuality(streamProcessor, getNextFragment(onGetNextFragment)); + abrController.getPlaybackQuality(streamProcessor, getNextFragment()); } else { - validateTimeout = setTimeout(function () { - //log("timeout going back to validate") - validate(); - }, 1000); //TODO should this be something based on fragment duration? + startValidateTimer(1000); } } - function getNextFragment(callback) { - var rules = scheduleRulesCollection.getRules(ScheduleRulesCollection.NEXT_FRAGMENT_RULES); - - rulesController.applyRules(rules, streamProcessor, callback, null, function (currentValue, newValue) { + function getNextFragment() { + let rules = scheduleRulesCollection.getRules(ScheduleRulesCollection.NEXT_FRAGMENT_RULES); + rulesController.applyRules(rules, streamProcessor, onGetNextFragment, null, function (currentValue, newValue) { return newValue; }); } @@ -239,9 +231,19 @@ function ScheduleController(config) { function onGetNextFragment(result) { if (result.value) { fragmentModel.executeRequest(result.value); + } else { + isFragmentLoading = false; + startValidateTimer(1000); } } + function startValidateTimer(value) { + validateTimeout = setTimeout(function () { + //log("timeout going back to validate") + validate(); + }, value); + } + function onQualityChanged(e) { if (type !== e.mediaType || streamProcessor.getStreamInfo().id !== e.streamInfo.id) return; @@ -285,7 +287,7 @@ function ScheduleController(config) { isFragmentLoading = false; } if (!e.error) return; - doStop(); + stop(); } function onBytesAppended(e) { @@ -297,7 +299,7 @@ function ScheduleController(config) { function onDataUpdateStarted(e) { if (e.sender.getStreamProcessor() !== streamProcessor) return; - doStop(); + stop(); } function onInitRequested(e) { @@ -313,7 +315,7 @@ function ScheduleController(config) { fragmentModel.removeExecutedRequestsBeforeTime(e.to); if (e.hasEnoughSpaceToAppend && !bufferController.getIsBufferingCompleted()) { - doStart(); + start(); } } @@ -326,7 +328,7 @@ function ScheduleController(config) { function onQuotaExceeded(e) { if (e.sender.getStreamProcessor() !== streamProcessor) return; - doStop(); + stop(); } function addPlaylistMetrics(stopReason) { @@ -354,7 +356,7 @@ function ScheduleController(config) { } function onPlaybackStarted() { - doStart(); + start(); } function onPlaybackSeeking(e) { @@ -454,10 +456,9 @@ function ScheduleController(config) { eventBus.off(Events.TIMED_TEXT_REQUESTED, onTimedTextRequested, this); } - doStop(); + stop(); fragmentController.detachModel(fragmentModel); isFragmentLoading = false; - fragmentsToLoad = 0; timeToloadDelay = 0; seekTarget = NaN; playbackController = null; @@ -472,8 +473,8 @@ function ScheduleController(config) { setTimeToLoadDelay: setTimeToLoadDelay, getTimeToLoadDelay: getTimeToLoadDelay, replaceCanceledRequests: replaceCanceledRequests, - start: doStart, - stop: doStop, + start: start, + stop: stop, reset: reset }; diff --git a/src/streaming/models/FragmentModel.js b/src/streaming/models/FragmentModel.js index 3ac8ca2a04..ffac2769f9 100644 --- a/src/streaming/models/FragmentModel.js +++ b/src/streaming/models/FragmentModel.js @@ -185,7 +185,7 @@ function FragmentModel(config) { if (!request) return; - //Adds the ability to delay single fragment loading time to control buffer. Needed for Advanced ABR rules. + //Adds the ability to delay single fragment loading time to control buffer. if (now < request.delayLoadingTime ) { delayLoadingTimeout = setTimeout(function () { executeRequest(request); @@ -201,8 +201,8 @@ function FragmentModel(config) { eventBus.trigger(Events.STREAM_COMPLETED, {request: request, fragmentModel: this}); break; case FragmentRequest.ACTION_DOWNLOAD: - loadingRequests.push(request); addSchedulingInfoMetrics(request, FRAGMENT_MODEL_LOADING); + loadingRequests.push(request); loadCurrentFragment(request); break; default: diff --git a/src/streaming/rules/abr/AbandonRequestsRule.js b/src/streaming/rules/abr/AbandonRequestsRule.js index 4734821369..a890bcfe7c 100644 --- a/src/streaming/rules/abr/AbandonRequestsRule.js +++ b/src/streaming/rules/abr/AbandonRequestsRule.js @@ -83,7 +83,7 @@ function AbandonRequestsRule(/*config*/) { fragmentInfo.segmentDuration = req.duration; fragmentInfo.bytesTotal = req.bytesTotal; fragmentInfo.id = req.index; - //log("XXX FRAG ID : " ,fragmentInfo.id, " *****************"); + //log("FRAG ID : " ,fragmentInfo.id, " *****************"); } //update info base on subsequent progress events until completed. fragmentInfo.bytesLoaded = req.bytesLoaded; @@ -94,7 +94,7 @@ function AbandonRequestsRule(/*config*/) { fragmentInfo.measuredBandwidthInKbps = Math.round(fragmentInfo.bytesLoaded * 8 / fragmentInfo.elapsedTime); fragmentInfo.estimatedTimeOfDownload = (fragmentInfo.bytesTotal * 8 * 0.001 / fragmentInfo.measuredBandwidthInKbps).toFixed(2); - //log("XXX","id: ",fragmentInfo.id, "kbps: ", fragmentInfo.measuredBandwidthInKbps, "etd: ",fragmentInfo.estimatedTimeOfDownload, "et: ", fragmentInfo.elapsedTime/1000); + //log("id: ",fragmentInfo.id, "kbps: ", fragmentInfo.measuredBandwidthInKbps, "etd: ",fragmentInfo.estimatedTimeOfDownload, "et: ", fragmentInfo.elapsedTime/1000); if (fragmentInfo.estimatedTimeOfDownload < (fragmentInfo.segmentDuration * ABANDON_MULTIPLIER) || representationInfo.quality === 0) { callback(switchRequest); diff --git a/src/streaming/rules/scheduling/NextFragmentRequestRule.js b/src/streaming/rules/scheduling/NextFragmentRequestRule.js index 88a1429143..dae6aeb2af 100644 --- a/src/streaming/rules/scheduling/NextFragmentRequestRule.js +++ b/src/streaming/rules/scheduling/NextFragmentRequestRule.js @@ -31,19 +31,18 @@ import SwitchRequest from '../SwitchRequest.js'; import FactoryMaker from '../../../core/FactoryMaker.js'; -import FragmentRequest from '../../vo/FragmentRequest.js'; function NextFragmentRequestRule(config) { let instance; let context = this.context; - let adapter = config.adapter; let sourceBufferExt = config.sourceBufferExt; let virtualBuffer = config.virtualBuffer; let textSourceBuffer = config.textSourceBuffer; function execute(rulesContext, callback) { + var mediaType = rulesContext.getMediaInfo().type; var mediaInfo = rulesContext.getMediaInfo(); var streamId = rulesContext.getStreamInfo().id; @@ -81,13 +80,7 @@ function NextFragmentRequestRule(config) { } request = adapter.getFragmentRequestForTime(streamProcessor, representationInfo, time, {keepIdx: keepIdx}); - - while (request && streamProcessor.getFragmentModel().isFragmentLoaded(request)) { - if (request.action === FragmentRequest.ACTION_COMPLETE) { - request = null; - break; - } - + if (request && streamProcessor.getFragmentModel().isFragmentLoaded(request)) { request = adapter.getNextFragmentRequest(streamProcessor, representationInfo); }