/*
* 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 android.net.Uri;
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;
}
/**
* A {@link SegmentBase} that defines a single segment.
*/
public static class SingleSegmentBase extends SegmentBase {
/**
* The uri of the segment.
*/
public final Uri 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,
Uri uri, long indexStart, long indexLength) {
super(initialization, timescale, presentationTimeOffset);
this.uri = uri;
this.indexStart = indexStart;
this.indexLength = indexLength;
}
public RangedUri getIndex() {
return new RangedUri(uri, null, indexStart, indexLength);
}
}
/**
* A {@link SegmentBase} that consists of multiple segments.
*/
public abstract static class MultiSegmentBase extends SegmentBase {
/* package */ final long periodDurationMs;
/* 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 periodDurationMs The duration of the enclosing period in milliseconds.
* @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,
long periodDurationMs, int startNumber, long duration,
List<SegmentTimelineElement> segmentTimeline) {
super(initialization, timescale, presentationTimeOffset);
this.periodDurationMs = periodDurationMs;
this.startNumber = startNumber;
this.duration = duration;
this.segmentTimeline = segmentTimeline;
}
public final int getSegmentNum(long timeUs) {
// TODO: Optimize this
int index = startNumber;
while (index + 1 <= getLastSegmentNum()) {
if (getSegmentTimeUs(index + 1) <= timeUs) {
index++;
} else {
return index;
}
}
return index;
}
public final long getSegmentDurationUs(int sequenceNumber) {
if (segmentTimeline != null) {
return (segmentTimeline.get(sequenceNumber - startNumber).duration * 1000000) / timescale;
} else {
return sequenceNumber == getLastSegmentNum()
? (periodDurationMs * 1000) - getSegmentTimeUs(sequenceNumber)
: ((duration * 1000000L) / timescale);
}
}
public final long getSegmentTimeUs(int sequenceNumber) {
long unscaledSegmentTime;
if (segmentTimeline != null) {
unscaledSegmentTime = segmentTimeline.get(sequenceNumber - startNumber).startTime
- presentationTimeOffset;
} else {
unscaledSegmentTime = (sequenceNumber - startNumber) * duration;
}
return (unscaledSegmentTime * 1000000) / timescale;
}
public abstract RangedUri getSegmentUrl(Representation representation, int index);
public int getFirstSegmentNum() {
return startNumber;
}
public abstract int getLastSegmentNum();
}
/**
* 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 periodDurationMs The duration of the enclosing period in milliseconds.
* @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,
long periodDurationMs, int startNumber, long duration,
List<SegmentTimelineElement> segmentTimeline, List<RangedUri> mediaSegments) {
super(initialization, timescale, presentationTimeOffset, periodDurationMs, startNumber,
duration, segmentTimeline);
this.mediaSegments = mediaSegments;
}
@Override
public RangedUri getSegmentUrl(Representation representation, int sequenceNumber) {
return mediaSegments.get(sequenceNumber - startNumber);
}
@Override
public int getLastSegmentNum() {
return startNumber + mediaSegments.size() - 1;
}
}
/**
* 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 Uri 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 periodDurationMs The duration of the enclosing period in milliseconds.
* @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,
long periodDurationMs, int startNumber, long duration,
List<SegmentTimelineElement> segmentTimeline, UrlTemplate initializationTemplate,
UrlTemplate mediaTemplate, Uri baseUrl) {
super(initialization, timescale, presentationTimeOffset, periodDurationMs, 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() {
if (segmentTimeline != null) {
return segmentTimeline.size() + startNumber - 1;
} else {
long durationMs = (duration * 1000) / timescale;
return startNumber + (int) (periodDurationMs / durationMs);
}
}
}
/**
* 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;
}
}
}