/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer.dash.mpd; import com.google.android.exoplayer.C; import com.google.android.exoplayer.dash.DashSegmentIndex; import com.google.android.exoplayer.util.Util; import java.util.List; /** * An approximate representation of a SegmentBase manifest element. */ public abstract class SegmentBase { /* package */ final RangedUri initialization; /* package */ final long timescale; /* package */ final long presentationTimeOffset; /** * @param initialization A {@link RangedUri} corresponding to initialization data, if such data * exists. * @param timescale The timescale in units per second. * @param presentationTimeOffset The presentation time offset. The value in seconds is the * division of this value and {@code timescale}. */ public SegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset) { this.initialization = initialization; this.timescale = timescale; this.presentationTimeOffset = presentationTimeOffset; } /** * Gets the {@link RangedUri} defining the location of initialization data for a given * representation. May be null if no initialization data exists. * * @param representation The {@link Representation} for which initialization data is required. * @return A {@link RangedUri} defining the location of the initialization data, or null. */ public RangedUri getInitialization(Representation representation) { return initialization; } /** * Gets the presentation time offset, in microseconds. * * @return The presentation time offset, in microseconds. */ public long getPresentationTimeOffsetUs() { return Util.scaleLargeTimestamp(presentationTimeOffset, C.MICROS_PER_SECOND, timescale); } /** * A {@link SegmentBase} that defines a single segment. */ public static class SingleSegmentBase extends SegmentBase { /** * The uri of the segment. */ public final String uri; /* package */ final long indexStart; /* package */ final long indexLength; /** * @param initialization A {@link RangedUri} corresponding to initialization data, if such data * exists. * @param timescale The timescale in units per second. * @param presentationTimeOffset The presentation time offset. The value in seconds is the * division of this value and {@code timescale}. * @param uri The uri of the segment. * @param indexStart The byte offset of the index data in the segment. * @param indexLength The length of the index data in bytes. */ public SingleSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset, String uri, long indexStart, long indexLength) { super(initialization, timescale, presentationTimeOffset); this.uri = uri; this.indexStart = indexStart; this.indexLength = indexLength; } /** * @param uri The uri of the segment. */ public SingleSegmentBase(String uri) { this(null, 1, 0, uri, 0, -1); } public RangedUri getIndex() { return indexLength <= 0 ? null : new RangedUri(uri, null, indexStart, indexLength); } } /** * A {@link SegmentBase} that consists of multiple segments. */ public abstract static class MultiSegmentBase extends SegmentBase { /* package */ final int startNumber; /* package */ final long duration; /* package */ final List<SegmentTimelineElement> segmentTimeline; /** * @param initialization A {@link RangedUri} corresponding to initialization data, if such data * exists. * @param timescale The timescale in units per second. * @param presentationTimeOffset The presentation time offset. The value in seconds is the * division of this value and {@code timescale}. * @param startNumber The sequence number of the first segment. * @param duration The duration of each segment in the case of fixed duration segments. The * value in seconds is the division of this value and {@code timescale}. If * {@code segmentTimeline} is non-null then this parameter is ignored. * @param segmentTimeline A segment timeline corresponding to the segments. If null, then * segments are assumed to be of fixed duration as specified by the {@code duration} * parameter. */ public MultiSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset, int startNumber, long duration, List<SegmentTimelineElement> segmentTimeline) { super(initialization, timescale, presentationTimeOffset); this.startNumber = startNumber; this.duration = duration; this.segmentTimeline = segmentTimeline; } /** * @see DashSegmentIndex#getSegmentNum(long, long) */ public int getSegmentNum(long timeUs, long periodDurationUs) { final int firstSegmentNum = getFirstSegmentNum(); int lowIndex = firstSegmentNum; int highIndex = getLastSegmentNum(periodDurationUs); if (segmentTimeline == null) { // All segments are of equal duration (with the possible exception of the last one). long durationUs = (duration * C.MICROS_PER_SECOND) / timescale; int segmentNum = startNumber + (int) (timeUs / durationUs); // Ensure we stay within bounds. return segmentNum < lowIndex ? lowIndex : highIndex != DashSegmentIndex.INDEX_UNBOUNDED && segmentNum > highIndex ? highIndex : segmentNum; } else { // The high index cannot be unbounded. Identify the segment using binary search. while (lowIndex <= highIndex) { int midIndex = (lowIndex + highIndex) / 2; long midTimeUs = getSegmentTimeUs(midIndex); if (midTimeUs < timeUs) { lowIndex = midIndex + 1; } else if (midTimeUs > timeUs) { highIndex = midIndex - 1; } else { return midIndex; } } return lowIndex == firstSegmentNum ? lowIndex : highIndex; } } /** * @see DashSegmentIndex#getDurationUs(int, long) */ public final long getSegmentDurationUs(int sequenceNumber, long periodDurationUs) { if (segmentTimeline != null) { long duration = segmentTimeline.get(sequenceNumber - startNumber).duration; return (duration * C.MICROS_PER_SECOND) / timescale; } else { return sequenceNumber == getLastSegmentNum(periodDurationUs) ? (periodDurationUs - getSegmentTimeUs(sequenceNumber)) : ((duration * C.MICROS_PER_SECOND) / timescale); } } /** * @see DashSegmentIndex#getTimeUs(int) */ public final long getSegmentTimeUs(int sequenceNumber) { long unscaledSegmentTime; if (segmentTimeline != null) { unscaledSegmentTime = segmentTimeline.get(sequenceNumber - startNumber).startTime - presentationTimeOffset; } else { unscaledSegmentTime = (sequenceNumber - startNumber) * duration; } return Util.scaleLargeTimestamp(unscaledSegmentTime, C.MICROS_PER_SECOND, timescale); } /** * Returns a {@link RangedUri} defining the location of a segment for the given index in the * given representation. * * @see DashSegmentIndex#getSegmentUrl(int) */ public abstract RangedUri getSegmentUrl(Representation representation, int index); /** * @see DashSegmentIndex#getFirstSegmentNum() */ public int getFirstSegmentNum() { return startNumber; } /** * @see DashSegmentIndex#getLastSegmentNum(long) */ public abstract int getLastSegmentNum(long periodDurationUs); /** * @see DashSegmentIndex#isExplicit() */ public boolean isExplicit() { return segmentTimeline != null; } } /** * A {@link MultiSegmentBase} that uses a SegmentList to define its segments. */ public static class SegmentList extends MultiSegmentBase { /* package */ final List<RangedUri> mediaSegments; /** * @param initialization A {@link RangedUri} corresponding to initialization data, if such data * exists. * @param timescale The timescale in units per second. * @param presentationTimeOffset The presentation time offset. The value in seconds is the * division of this value and {@code timescale}. * @param startNumber The sequence number of the first segment. * @param duration The duration of each segment in the case of fixed duration segments. The * value in seconds is the division of this value and {@code timescale}. If * {@code segmentTimeline} is non-null then this parameter is ignored. * @param segmentTimeline A segment timeline corresponding to the segments. If null, then * segments are assumed to be of fixed duration as specified by the {@code duration} * parameter. * @param mediaSegments A list of {@link RangedUri}s indicating the locations of the segments. */ public SegmentList(RangedUri initialization, long timescale, long presentationTimeOffset, int startNumber, long duration, List<SegmentTimelineElement> segmentTimeline, List<RangedUri> mediaSegments) { super(initialization, timescale, presentationTimeOffset, startNumber, duration, segmentTimeline); this.mediaSegments = mediaSegments; } @Override public RangedUri getSegmentUrl(Representation representation, int sequenceNumber) { return mediaSegments.get(sequenceNumber - startNumber); } @Override public int getLastSegmentNum(long periodDurationUs) { return startNumber + mediaSegments.size() - 1; } @Override public boolean isExplicit() { return true; } } /** * A {@link MultiSegmentBase} that uses a SegmentTemplate to define its segments. */ public static class SegmentTemplate extends MultiSegmentBase { /* package */ final UrlTemplate initializationTemplate; /* package */ final UrlTemplate mediaTemplate; private final String baseUrl; /** * @param initialization A {@link RangedUri} corresponding to initialization data, if such data * exists. The value of this parameter is ignored if {@code initializationTemplate} is * non-null. * @param timescale The timescale in units per second. * @param presentationTimeOffset The presentation time offset. The value in seconds is the * division of this value and {@code timescale}. * @param startNumber The sequence number of the first segment. * @param duration The duration of each segment in the case of fixed duration segments. The * value in seconds is the division of this value and {@code timescale}. If * {@code segmentTimeline} is non-null then this parameter is ignored. * @param segmentTimeline A segment timeline corresponding to the segments. If null, then * segments are assumed to be of fixed duration as specified by the {@code duration} * parameter. * @param initializationTemplate A template defining the location of initialization data, if * such data exists. If non-null then the {@code initialization} parameter is ignored. If * null then {@code initialization} will be used. * @param mediaTemplate A template defining the location of each media segment. * @param baseUrl A url to use as the base for relative urls generated by the templates. */ public SegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset, int startNumber, long duration, List<SegmentTimelineElement> segmentTimeline, UrlTemplate initializationTemplate, UrlTemplate mediaTemplate, String baseUrl) { super(initialization, timescale, presentationTimeOffset, startNumber, duration, segmentTimeline); this.initializationTemplate = initializationTemplate; this.mediaTemplate = mediaTemplate; this.baseUrl = baseUrl; } @Override public RangedUri getInitialization(Representation representation) { if (initializationTemplate != null) { String urlString = initializationTemplate.buildUri(representation.format.id, 0, representation.format.bitrate, 0); return new RangedUri(baseUrl, urlString, 0, -1); } else { return super.getInitialization(representation); } } @Override public RangedUri getSegmentUrl(Representation representation, int sequenceNumber) { long time = 0; if (segmentTimeline != null) { time = segmentTimeline.get(sequenceNumber - startNumber).startTime; } else { time = (sequenceNumber - startNumber) * duration; } String uriString = mediaTemplate.buildUri(representation.format.id, sequenceNumber, representation.format.bitrate, time); return new RangedUri(baseUrl, uriString, 0, -1); } @Override public int getLastSegmentNum(long periodDurationUs) { if (segmentTimeline != null) { return segmentTimeline.size() + startNumber - 1; } else if (periodDurationUs == C.UNKNOWN_TIME_US) { return DashSegmentIndex.INDEX_UNBOUNDED; } else { long durationUs = (duration * C.MICROS_PER_SECOND) / timescale; return startNumber + (int) Util.ceilDivide(periodDurationUs, durationUs) - 1; } } } /** * Represents a timeline segment from the MPD's SegmentTimeline list. */ public static class SegmentTimelineElement { /* package */ long startTime; /* package */ long duration; /** * @param startTime The start time of the element. The value in seconds is the division of this * value and the {@code timescale} of the enclosing element. * @param duration The duration of the element. The value in seconds is the division of this * value and the {@code timescale} of the enclosing element. */ public SegmentTimelineElement(long startTime, long duration) { this.startTime = startTime; this.duration = duration; } } }