UNPKG

@l5i/dashjs

Version:

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

208 lines (171 loc) 8.46 kB
/** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ import FactoryMaker from '../../core/FactoryMaker'; import {getTimeBasedSegment} from './SegmentsUtils'; function TimelineSegmentsGetter(config, isDynamic) { config = config || {}; const timelineConverter = config.timelineConverter; let instance; function checkConfig() { if (!timelineConverter || !timelineConverter.hasOwnProperty('calcMediaTimeFromPresentationTime') || !timelineConverter.hasOwnProperty('calcSegmentAvailabilityRange')) { throw new Error('Missing config parameter(s)'); } } function getSegmentsFromTimeline(representation, requestedTime, index, availabilityUpperLimit) { checkConfig(); if (!representation) { throw new Error('no representation'); } if (requestedTime === undefined) { requestedTime = null; } const base = representation.adaptation.period.mpd.manifest.Period_asArray[representation.adaptation.period.index]. AdaptationSet_asArray[representation.adaptation.index].Representation_asArray[representation.index].SegmentTemplate || representation.adaptation.period.mpd.manifest.Period_asArray[representation.adaptation.period.index]. AdaptationSet_asArray[representation.adaptation.index].Representation_asArray[representation.index].SegmentList; const timeline = base.SegmentTimeline; const list = base.SegmentURL_asArray; const isAvailableSegmentNumberCalculated = representation.availableSegmentsNumber > 0; let maxSegmentsAhead; if (availabilityUpperLimit) { maxSegmentsAhead = availabilityUpperLimit; } else { maxSegmentsAhead = (index > -1 || requestedTime !== null) ? 10 : Infinity; } let time = 0; let scaledTime = 0; let availabilityIdx = -1; const segments = []; let requiredMediaTime = null; let fragments, frag, i, len, j, repeat, repeatEndTime, nextFrag, hasEnoughSegments, startIdx, fTimescale; let createSegment = function (s, i) { let media = base.media; let mediaRange = s.mediaRange; if (list) { media = list[i].media || ''; mediaRange = list[i].mediaRange; } return getTimeBasedSegment( timelineConverter, isDynamic, representation, time, s.d, fTimescale, media, mediaRange, availabilityIdx, s.tManifest); }; fTimescale = representation.timescale; fragments = timeline.S_asArray; startIdx = index; if (requestedTime !== null) { requiredMediaTime = timelineConverter.calcMediaTimeFromPresentationTime(requestedTime, representation); } for (i = 0, len = fragments.length; i < len; i++) { frag = fragments[i]; repeat = 0; if (frag.hasOwnProperty('r')) { repeat = frag.r; } // For a repeated S element, t belongs only to the first segment if (frag.hasOwnProperty('t')) { time = frag.t; scaledTime = time / fTimescale; } // This is a special case: "A negative value of the @r attribute of the S element indicates that the duration indicated in @d attribute repeats until the start of the next S element, the end of the Period or until the // next MPD update." if (repeat < 0) { nextFrag = fragments[i + 1]; if (nextFrag && nextFrag.hasOwnProperty('t')) { repeatEndTime = nextFrag.t / fTimescale; } else { const availabilityEnd = representation.segmentAvailabilityRange ? representation.segmentAvailabilityRange.end : (timelineConverter.calcSegmentAvailabilityRange(representation, isDynamic).end); repeatEndTime = timelineConverter.calcMediaTimeFromPresentationTime(availabilityEnd, representation); representation.segmentDuration = frag.d / fTimescale; } repeat = Math.ceil((repeatEndTime - scaledTime) / (frag.d / fTimescale)) - 1; } // if we have enough segments in the list, but we have not calculated the total number of the segments yet we // should continue the loop and calc the number. Once it is calculated, we can break the loop. if (hasEnoughSegments) { if (isAvailableSegmentNumberCalculated) break; availabilityIdx += repeat + 1; continue; } for (j = 0; j <= repeat; j++) { availabilityIdx++; if (segments.length > maxSegmentsAhead) { hasEnoughSegments = true; if (isAvailableSegmentNumberCalculated) break; continue; } if (requiredMediaTime !== null) { // In some cases when requiredMediaTime = actual end time of the last segment // it is possible that this time a bit exceeds the declared end time of the last segment. // in this case we still need to include the last segment in the segment list. to do this we // use a correction factor = 1.5. This number is used because the largest possible deviation is // is 50% of segment duration. if (scaledTime >= (requiredMediaTime - (frag.d / fTimescale) * 1.5)) { segments.push(createSegment(frag, availabilityIdx)); } } else if (availabilityIdx >= startIdx) { segments.push(createSegment(frag, availabilityIdx)); } time += frag.d; scaledTime = time / fTimescale; } } if (!isAvailableSegmentNumberCalculated) { representation.availableSegmentsNumber = availabilityIdx + 1; } return segments; } instance = { getSegments: getSegmentsFromTimeline }; return instance; } TimelineSegmentsGetter.__dashjs_factory_name = 'TimelineSegmentsGetter'; const factory = FactoryMaker.getClassFactory(TimelineSegmentsGetter); export default factory;