diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java new file mode 100644 index 0000000000..336e4c6057 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java @@ -0,0 +1,73 @@ +/* + * 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; + +import com.google.android.exoplayer.dash.mpd.RangedUri; + +/** + * Indexes the segments within a media stream. + * + * TODO: Generalize to cover all chunk streaming modes (e.g. SmoothStreaming) if possible. + */ +public interface DashSegmentIndex { + + /** + * Returns the segment number of the segment containing a given media time. + * + * @param timeUs The time in microseconds. + * @return The segment number of the corresponding segment. + */ + int getSegmentNum(long timeUs); + + /** + * Returns the start time of a segment. + * + * @param segmentNum The segment number. + * @return The corresponding start time in microseconds. + */ + long getTimeUs(int segmentNum); + + /** + * Returns the duration of a segment. + * + * @param segmentNum The segment number. + * @return The duration of the segment, in microseconds. + */ + long getDurationUs(int segmentNum); + + /** + * Returns a {@link RangedUri} defining the location of a segment. + * + * @param segmentNum The segment number. + * @return The {@link RangedUri} defining the location of the data. + */ + RangedUri getSegmentUrl(int segmentNum); + + /** + * Returns the segment number of the first segment. + * + * @return The segment number of the first segment. + */ + int getFirstSegmentNum(); + + /** + * Returns the segment number of the last segment. + * + * @return The segment number of the last segment. + */ + int getLastSegmentNum(); + +} diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java new file mode 100644 index 0000000000..3d0bb0913a --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java @@ -0,0 +1,77 @@ +/* + * 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; + +import com.google.android.exoplayer.dash.mpd.RangedUri; +import com.google.android.exoplayer.parser.SegmentIndex; +import com.google.android.exoplayer.util.Util; + +import android.net.Uri; + +/** + * An implementation of {@link DashSegmentIndex} that wraps a {@link SegmentIndex} parsed from a + * media stream. + */ +public class DashWrappingSegmentIndex implements DashSegmentIndex { + + private final SegmentIndex segmentIndex; + private final Uri uri; + private final long indexAnchor; + + /** + * @param segmentIndex The {@link SegmentIndex} to wrap. + * @param uri The {@link Uri} where the data is located. + * @param indexAnchor The index anchor point. This value is added to the byte offsets specified + * in the wrapped {@link SegmentIndex}. + */ + public DashWrappingSegmentIndex(SegmentIndex segmentIndex, Uri uri, long indexAnchor) { + this.segmentIndex = segmentIndex; + this.uri = uri; + this.indexAnchor = indexAnchor; + } + + @Override + public int getFirstSegmentNum() { + return 0; + } + + @Override + public int getLastSegmentNum() { + return segmentIndex.length - 1; + } + + @Override + public long getTimeUs(int segmentNum) { + return segmentIndex.timesUs[segmentNum]; + } + + @Override + public long getDurationUs(int segmentNum) { + return segmentIndex.durationsUs[segmentNum]; + } + + @Override + public RangedUri getSegmentUrl(int segmentNum) { + return new RangedUri(uri, null, indexAnchor + segmentIndex.offsets[segmentNum], + segmentIndex.sizes[segmentNum]); + } + + @Override + public int getSegmentNum(long timeUs) { + return Util.binarySearchFloor(segmentIndex.timesUs, timeUs, true, true); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java new file mode 100644 index 0000000000..89a9dd49be --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java @@ -0,0 +1,313 @@ +/* + * 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 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 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 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 segmentTimeline, List 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 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; + } + + } + +}