@l5i/dashjs
Version:
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
225 lines (185 loc) • 9.7 kB
JavaScript
/**
* The copyright in this software is being made available under the BSD License,
* included below. This software may be subject to other third party and contributor
* rights, including patent rights, and no such rights are granted under this license.
*
* Copyright (c) 2013, Dash Industry Forum.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* * Neither the name of Dash Industry Forum nor the names of its
* contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import EventBus from '../../core/EventBus';
import Events from '../../core/events/Events';
import FactoryMaker from '../../core/FactoryMaker';
function TimelineConverter() {
let context = this.context;
let eventBus = EventBus(context).getInstance();
let instance,
clientServerTimeShift,
isClientServerTimeSyncCompleted,
expectedLiveEdge;
function initialize() {
resetInitialSettings();
eventBus.on(Events.TIME_SYNCHRONIZATION_COMPLETED, onTimeSyncComplete, this);
}
function isTimeSyncCompleted() {
return isClientServerTimeSyncCompleted;
}
function setTimeSyncCompleted(value) {
isClientServerTimeSyncCompleted = value;
}
function getClientTimeOffset() {
return clientServerTimeShift;
}
function setClientTimeOffset(value) {
clientServerTimeShift = value;
}
function getExpectedLiveEdge() {
return expectedLiveEdge;
}
function setExpectedLiveEdge(value) {
expectedLiveEdge = value;
}
function calcAvailabilityTimeFromPresentationTime(presentationTime, mpd, isDynamic, calculateEnd) {
let availabilityTime = NaN;
if (calculateEnd) {
//@timeShiftBufferDepth specifies the duration of the time shifting buffer that is guaranteed
// to be available for a Media Presentation with type 'dynamic'.
// When not present, the value is infinite.
if (isDynamic && (mpd.timeShiftBufferDepth != Number.POSITIVE_INFINITY)) {
availabilityTime = new Date(mpd.availabilityStartTime.getTime() + ((presentationTime + mpd.timeShiftBufferDepth) * 1000));
} else {
availabilityTime = mpd.availabilityEndTime;
}
} else {
if (isDynamic) {
availabilityTime = new Date(mpd.availabilityStartTime.getTime() + (presentationTime - clientServerTimeShift) * 1000);
} else {
// in static mpd, all segments are available at the same time
availabilityTime = mpd.availabilityStartTime;
}
}
return availabilityTime;
}
function calcAvailabilityStartTimeFromPresentationTime(presentationTime, mpd, isDynamic) {
return calcAvailabilityTimeFromPresentationTime.call(this, presentationTime, mpd, isDynamic);
}
function calcAvailabilityEndTimeFromPresentationTime(presentationTime, mpd, isDynamic) {
return calcAvailabilityTimeFromPresentationTime.call(this, presentationTime, mpd, isDynamic, true);
}
function calcPresentationTimeFromWallTime(wallTime, period) {
return ((wallTime.getTime() - period.mpd.availabilityStartTime.getTime() + clientServerTimeShift * 1000) / 1000);
}
function calcPresentationTimeFromMediaTime(mediaTime, representation) {
const periodStart = representation.adaptation.period.start;
const presentationOffset = representation.presentationTimeOffset;
return mediaTime + (periodStart - presentationOffset);
}
function calcMediaTimeFromPresentationTime(presentationTime, representation) {
const periodStart = representation.adaptation.period.start;
const presentationOffset = representation.presentationTimeOffset;
return presentationTime - periodStart + presentationOffset;
}
function calcWallTimeForSegment(segment, isDynamic) {
let suggestedPresentationDelay,
displayStartTime,
wallTime;
if (isDynamic) {
suggestedPresentationDelay = segment.representation.adaptation.period.mpd.suggestedPresentationDelay;
displayStartTime = segment.presentationStartTime + suggestedPresentationDelay;
wallTime = new Date(segment.availabilityStartTime.getTime() + (displayStartTime * 1000));
}
return wallTime;
}
function calcSegmentAvailabilityRange(voRepresentation, isDynamic) {
// Static Range Finder
const voPeriod = voRepresentation.adaptation.period;
const range = { start: voPeriod.start, end: voPeriod.start + voPeriod.duration };
if (!isDynamic) return range;
if (!isClientServerTimeSyncCompleted && voRepresentation.segmentAvailabilityRange) {
return voRepresentation.segmentAvailabilityRange;
}
// Dynamic Range Finder
const d = voRepresentation.segmentDuration || (voRepresentation.segments && voRepresentation.segments.length ? voRepresentation.segments[voRepresentation.segments.length - 1].duration : 0);
const now = calcPresentationTimeFromWallTime(new Date(), voPeriod);
const periodEnd = voPeriod.start + voPeriod.duration;
range.start = Math.max((now - voPeriod.mpd.timeShiftBufferDepth), voPeriod.start);
const endOffset = voRepresentation.availabilityTimeOffset !== undefined &&
voRepresentation.availabilityTimeOffset < d ? d - voRepresentation.availabilityTimeOffset : d;
range.end = now >= periodEnd && now - endOffset < periodEnd ? periodEnd : now - endOffset;
return range;
}
function calcPeriodRelativeTimeFromMpdRelativeTime(representation, mpdRelativeTime) {
const periodStartTime = representation.adaptation.period.start;
return mpdRelativeTime - periodStartTime;
}
/*
* We need to figure out if we want to timesync for segmentTimeine where useCalculatedLiveEdge = true
* seems we figure out client offset based on logic in liveEdgeFinder getLiveEdge timelineConverter.setClientTimeOffset(liveEdge - representationInfo.DVRWindow.end);
* FYI StreamController's onManifestUpdated entry point to timeSync
* */
function onTimeSyncComplete(e) {
if (isClientServerTimeSyncCompleted) return;
if (e.offset !== undefined) {
setClientTimeOffset(e.offset / 1000);
isClientServerTimeSyncCompleted = true;
}
}
function calcMSETimeOffset(representation) {
// The MSEOffset is offset from AST for media. It is Period@start - presentationTimeOffset
const presentationOffset = representation.presentationTimeOffset;
const periodStart = representation.adaptation.period.start;
return (periodStart - presentationOffset);
}
function resetInitialSettings() {
clientServerTimeShift = 0;
isClientServerTimeSyncCompleted = false;
expectedLiveEdge = NaN;
}
function reset() {
eventBus.off(Events.TIME_SYNCHRONIZATION_COMPLETED, onTimeSyncComplete, this);
resetInitialSettings();
}
instance = {
initialize: initialize,
isTimeSyncCompleted: isTimeSyncCompleted,
setTimeSyncCompleted: setTimeSyncCompleted,
getClientTimeOffset: getClientTimeOffset,
setClientTimeOffset: setClientTimeOffset,
getExpectedLiveEdge: getExpectedLiveEdge,
setExpectedLiveEdge: setExpectedLiveEdge,
calcAvailabilityStartTimeFromPresentationTime: calcAvailabilityStartTimeFromPresentationTime,
calcAvailabilityEndTimeFromPresentationTime: calcAvailabilityEndTimeFromPresentationTime,
calcPresentationTimeFromWallTime: calcPresentationTimeFromWallTime,
calcPresentationTimeFromMediaTime: calcPresentationTimeFromMediaTime,
calcPeriodRelativeTimeFromMpdRelativeTime: calcPeriodRelativeTimeFromMpdRelativeTime,
calcMediaTimeFromPresentationTime: calcMediaTimeFromPresentationTime,
calcSegmentAvailabilityRange: calcSegmentAvailabilityRange,
calcWallTimeForSegment: calcWallTimeForSegment,
calcMSETimeOffset: calcMSETimeOffset,
reset: reset
};
return instance;
}
TimelineConverter.__dashjs_factory_name = 'TimelineConverter';
export default FactoryMaker.getSingletonFactory(TimelineConverter);