Refactored ExoPlayer to use String-based format ids.

This commit is contained in:
Oliver Woodman 2014-07-04 01:04:10 +01:00
parent 563b434de2
commit 4fd4c89518
10 changed files with 73 additions and 52 deletions

View File

@ -91,7 +91,7 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
} }
@Override @Override
public void onLoadStarted(int sourceId, int formatId, int trigger, boolean isInitialization, public void onLoadStarted(int sourceId, String formatId, int trigger, boolean isInitialization,
int mediaStartTimeMs, int mediaEndTimeMs, long totalBytes) { int mediaStartTimeMs, int mediaEndTimeMs, long totalBytes) {
loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime(); loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime();
if (VerboseLogUtil.isTagEnabled(TAG)) { if (VerboseLogUtil.isTagEnabled(TAG)) {
@ -110,13 +110,13 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
} }
@Override @Override
public void onVideoFormatEnabled(int formatId, int trigger, int mediaTimeMs) { public void onVideoFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", " + Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", " +
Integer.toString(trigger) + "]"); Integer.toString(trigger) + "]");
} }
@Override @Override
public void onAudioFormatEnabled(int formatId, int trigger, int mediaTimeMs) { public void onAudioFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", " + Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", " +
Integer.toString(trigger) + "]"); Integer.toString(trigger) + "]");
} }

View File

@ -118,11 +118,11 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
* A listener for debugging information. * A listener for debugging information.
*/ */
public interface InfoListener { public interface InfoListener {
void onVideoFormatEnabled(int formatId, int trigger, int mediaTimeMs); void onVideoFormatEnabled(String formatId, int trigger, int mediaTimeMs);
void onAudioFormatEnabled(int formatId, int trigger, int mediaTimeMs); void onAudioFormatEnabled(String formatId, int trigger, int mediaTimeMs);
void onDroppedFrames(int count, long elapsed); void onDroppedFrames(int count, long elapsed);
void onBandwidthSample(int elapsedMs, long bytes, long bandwidthEstimate); void onBandwidthSample(int elapsedMs, long bytes, long bandwidthEstimate);
void onLoadStarted(int sourceId, int formatId, int trigger, boolean isInitialization, void onLoadStarted(int sourceId, String formatId, int trigger, boolean isInitialization,
int mediaStartTimeMs, int mediaEndTimeMs, long totalBytes); int mediaStartTimeMs, int mediaEndTimeMs, long totalBytes);
void onLoadCompleted(int sourceId); void onLoadCompleted(int sourceId);
} }
@ -398,7 +398,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
@Override @Override
public void onDownstreamFormatChanged(int sourceId, int formatId, int trigger, int mediaTimeMs) { public void onDownstreamFormatChanged(int sourceId, String formatId, int trigger,
int mediaTimeMs) {
if (infoListener == null) { if (infoListener == null) {
return; return;
} }
@ -469,7 +470,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
@Override @Override
public void onLoadStarted(int sourceId, int formatId, int trigger, boolean isInitialization, public void onLoadStarted(int sourceId, String formatId, int trigger, boolean isInitialization,
int mediaStartTimeMs, int mediaEndTimeMs, long totalBytes) { int mediaStartTimeMs, int mediaEndTimeMs, long totalBytes) {
if (infoListener != null) { if (infoListener != null) {
infoListener.onLoadStarted(sourceId, formatId, trigger, isInitialization, mediaStartTimeMs, infoListener.onLoadStarted(sourceId, formatId, trigger, isInitialization, mediaStartTimeMs,

View File

@ -59,7 +59,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
* load is for initialization data. * load is for initialization data.
* @param totalBytes The length of the data being loaded in bytes. * @param totalBytes The length of the data being loaded in bytes.
*/ */
void onLoadStarted(int sourceId, int formatId, int trigger, boolean isInitialization, void onLoadStarted(int sourceId, String formatId, int trigger, boolean isInitialization,
int mediaStartTimeMs, int mediaEndTimeMs, long totalBytes); int mediaStartTimeMs, int mediaEndTimeMs, long totalBytes);
/** /**
@ -126,7 +126,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
* {@link ChunkSource}. * {@link ChunkSource}.
* @param mediaTimeMs The media time at which the change occurred. * @param mediaTimeMs The media time at which the change occurred.
*/ */
void onDownstreamFormatChanged(int sourceId, int formatId, int trigger, int mediaTimeMs); void onDownstreamFormatChanged(int sourceId, String formatId, int trigger, int mediaTimeMs);
} }
@ -295,7 +295,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
} }
return NOTHING_READ; return NOTHING_READ;
} }
} else if (downstreamFormat == null || downstreamFormat.id != mediaChunk.format.id) { } else if (downstreamFormat == null || !downstreamFormat.id.equals(mediaChunk.format.id)) {
notifyDownstreamFormatChanged(mediaChunk.format.id, mediaChunk.trigger, notifyDownstreamFormatChanged(mediaChunk.format.id, mediaChunk.trigger,
mediaChunk.startTimeUs); mediaChunk.startTimeUs);
MediaFormat format = mediaChunk.getMediaFormat(); MediaFormat format = mediaChunk.getMediaFormat();
@ -653,7 +653,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
return (int) (timeUs / 1000); return (int) (timeUs / 1000);
} }
private void notifyLoadStarted(final int formatId, final int trigger, private void notifyLoadStarted(final String formatId, final int trigger,
final boolean isInitialization, final long mediaStartTimeUs, final long mediaEndTimeUs, final boolean isInitialization, final long mediaStartTimeUs, final long mediaEndTimeUs,
final long totalBytes) { final long totalBytes) {
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
@ -724,7 +724,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
} }
} }
private void notifyDownstreamFormatChanged(final int formatId, final int trigger, private void notifyDownstreamFormatChanged(final String formatId, final int trigger,
final long mediaTimeUs) { final long mediaTimeUs) {
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() { eventHandler.post(new Runnable() {

View File

@ -20,7 +20,7 @@ import java.util.Comparator;
/** /**
* A format definition for streams. * A format definition for streams.
*/ */
public final class Format { public class Format {
/** /**
* Sorts {@link Format} objects in order of decreasing bandwidth. * Sorts {@link Format} objects in order of decreasing bandwidth.
@ -37,7 +37,7 @@ public final class Format {
/** /**
* An identifier for the format. * An identifier for the format.
*/ */
public final int id; public final String id;
/** /**
* The mime type of the format. * The mime type of the format.
@ -70,6 +70,8 @@ public final class Format {
public final int bandwidth; public final int bandwidth;
/** /**
* @deprecated Format identifiers are now strings.
*
* @param id The format identifier. * @param id The format identifier.
* @param mimeType The format mime type. * @param mimeType The format mime type.
* @param width The width of the video in pixels, or -1 for non-video formats. * @param width The width of the video in pixels, or -1 for non-video formats.
@ -78,8 +80,23 @@ public final class Format {
* @param audioSamplingRate The audio sampling rate in Hz, or -1 for non-audio formats. * @param audioSamplingRate The audio sampling rate in Hz, or -1 for non-audio formats.
* @param bandwidth The average bandwidth of the format in bytes per second. * @param bandwidth The average bandwidth of the format in bytes per second.
*/ */
@Deprecated
public Format(int id, String mimeType, int width, int height, int numChannels, public Format(int id, String mimeType, int width, int height, int numChannels,
int audioSamplingRate, int bandwidth) { int audioSamplingRate, int bandwidth) {
this(String.valueOf(id), mimeType, width, height, numChannels, audioSamplingRate, bandwidth);
}
/**
* @param id The format identifier.
* @param mimeType The format mime type.
* @param width The width of the video in pixels, or -1 for non-video formats.
* @param height The height of the video in pixels, or -1 for non-video formats.
* @param numChannels The number of audio channels, or -1 for non-audio formats.
* @param audioSamplingRate The audio sampling rate in Hz, or -1 for non-audio formats.
* @param bandwidth The average bandwidth of the format in bytes per second.
*/
public Format(String id, String mimeType, int width, int height, int numChannels,
int audioSamplingRate, int bandwidth) {
this.id = id; this.id = id;
this.mimeType = mimeType; this.mimeType = mimeType;
this.width = width; this.width = width;

View File

@ -146,7 +146,7 @@ public interface FormatEvaluator {
public void evaluate(List<? extends MediaChunk> queue, long playbackPositionUs, public void evaluate(List<? extends MediaChunk> queue, long playbackPositionUs,
Format[] formats, Evaluation evaluation) { Format[] formats, Evaluation evaluation) {
Format newFormat = formats[random.nextInt(formats.length)]; Format newFormat = formats[random.nextInt(formats.length)];
if (evaluation.format != null && evaluation.format.id != newFormat.id) { if (evaluation.format != null && !evaluation.format.id.equals(newFormat.id)) {
evaluation.trigger = TRIGGER_ADAPTIVE; evaluation.trigger = TRIGGER_ADAPTIVE;
} }
evaluation.format = newFormat; evaluation.format = newFormat;

View File

@ -35,10 +35,10 @@ import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import android.util.Log; import android.util.Log;
import android.util.SparseArray;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
/** /**
@ -64,8 +64,8 @@ public class DashMp4ChunkSource implements ChunkSource {
private final int numSegmentsPerChunk; private final int numSegmentsPerChunk;
private final Format[] formats; private final Format[] formats;
private final SparseArray<Representation> representations; private final HashMap<String, Representation> representations;
private final SparseArray<FragmentedMp4Extractor> extractors; private final HashMap<String, FragmentedMp4Extractor> extractors;
private boolean lastChunkWasInitialization; private boolean lastChunkWasInitialization;
@ -92,8 +92,8 @@ public class DashMp4ChunkSource implements ChunkSource {
this.evaluator = evaluator; this.evaluator = evaluator;
this.numSegmentsPerChunk = numSegmentsPerChunk; this.numSegmentsPerChunk = numSegmentsPerChunk;
this.formats = new Format[representations.length]; this.formats = new Format[representations.length];
this.extractors = new SparseArray<FragmentedMp4Extractor>(); this.extractors = new HashMap<String, FragmentedMp4Extractor>();
this.representations = new SparseArray<Representation>(); this.representations = new HashMap<String, Representation>();
this.trackInfo = new TrackInfo(representations[0].format.mimeType, this.trackInfo = new TrackInfo(representations[0].format.mimeType,
representations[0].periodDuration * 1000); representations[0].periodDuration * 1000);
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
@ -103,7 +103,7 @@ public class DashMp4ChunkSource implements ChunkSource {
formats[i] = representations[i].format; formats[i] = representations[i].format;
maxWidth = Math.max(formats[i].width, maxWidth); maxWidth = Math.max(formats[i].width, maxWidth);
maxHeight = Math.max(formats[i].height, maxHeight); maxHeight = Math.max(formats[i].height, maxHeight);
extractors.append(formats[i].id, new FragmentedMp4Extractor()); extractors.put(formats[i].id, new FragmentedMp4Extractor());
this.representations.put(formats[i].id, representations[i]); this.representations.put(formats[i].id, representations[i]);
} }
this.maxWidth = maxWidth; this.maxWidth = maxWidth;
@ -152,7 +152,7 @@ public class DashMp4ChunkSource implements ChunkSource {
out.chunk = null; out.chunk = null;
return; return;
} else if (out.queueSize == queue.size() && out.chunk != null } else if (out.queueSize == queue.size() && out.chunk != null
&& out.chunk.format.id == selectedFormat.id) { && out.chunk.format.id.equals(selectedFormat.id)) {
// We already have a chunk, and the evaluation hasn't changed either the format or the size // We already have a chunk, and the evaluation hasn't changed either the format or the size
// of the queue. Leave unchanged. // of the queue. Leave unchanged.
return; return;

View File

@ -35,10 +35,10 @@ import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import android.util.Log; import android.util.Log;
import android.util.SparseArray;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
/** /**
@ -57,8 +57,8 @@ public class DashWebmChunkSource implements ChunkSource {
private final int numSegmentsPerChunk; private final int numSegmentsPerChunk;
private final Format[] formats; private final Format[] formats;
private final SparseArray<Representation> representations; private final HashMap<String, Representation> representations;
private final SparseArray<WebmExtractor> extractors; private final HashMap<String, WebmExtractor> extractors;
private boolean lastChunkWasInitialization; private boolean lastChunkWasInitialization;
@ -73,8 +73,8 @@ public class DashWebmChunkSource implements ChunkSource {
this.evaluator = evaluator; this.evaluator = evaluator;
this.numSegmentsPerChunk = numSegmentsPerChunk; this.numSegmentsPerChunk = numSegmentsPerChunk;
this.formats = new Format[representations.length]; this.formats = new Format[representations.length];
this.extractors = new SparseArray<WebmExtractor>(); this.extractors = new HashMap<String, WebmExtractor>();
this.representations = new SparseArray<Representation>(); this.representations = new HashMap<String, Representation>();
this.trackInfo = new TrackInfo( this.trackInfo = new TrackInfo(
representations[0].format.mimeType, representations[0].periodDuration * 1000); representations[0].format.mimeType, representations[0].periodDuration * 1000);
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
@ -84,7 +84,7 @@ public class DashWebmChunkSource implements ChunkSource {
formats[i] = representations[i].format; formats[i] = representations[i].format;
maxWidth = Math.max(formats[i].width, maxWidth); maxWidth = Math.max(formats[i].width, maxWidth);
maxHeight = Math.max(formats[i].height, maxHeight); maxHeight = Math.max(formats[i].height, maxHeight);
extractors.append(formats[i].id, new WebmExtractor()); extractors.put(formats[i].id, new WebmExtractor());
this.representations.put(formats[i].id, representations[i]); this.representations.put(formats[i].id, representations[i]);
} }
this.maxWidth = maxWidth; this.maxWidth = maxWidth;
@ -133,7 +133,7 @@ public class DashWebmChunkSource implements ChunkSource {
out.chunk = null; out.chunk = null;
return; return;
} else if (out.queueSize == queue.size() && out.chunk != null } else if (out.queueSize == queue.size() && out.chunk != null
&& out.chunk.format.id == selectedFormat.id) { && out.chunk.format.id.equals(selectedFormat.id)) {
// We already have a chunk, and the evaluation hasn't changed either the format or the size // We already have a chunk, and the evaluation hasn't changed either the format or the size
// of the queue. Leave unchanged. // of the queue. Leave unchanged.
return; return;

View File

@ -21,7 +21,6 @@ import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import android.net.Uri; import android.net.Uri;
import android.util.Log;
import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.DefaultHandler;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
@ -45,8 +44,6 @@ import java.util.regex.Pattern;
*/ */
public class MediaPresentationDescriptionParser extends DefaultHandler { public class MediaPresentationDescriptionParser extends DefaultHandler {
private static final String TAG = "MediaPresentationDescriptionParser";
// Note: Does not support the date part of ISO 8601 // Note: Does not support the date part of ISO 8601
private static final Pattern DURATION = private static final Pattern DURATION =
Pattern.compile("^PT(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?$"); Pattern.compile("^PT(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?$");
@ -214,14 +211,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
private Representation parseRepresentation(XmlPullParser xpp, String contentId, long periodStart, private Representation parseRepresentation(XmlPullParser xpp, String contentId, long periodStart,
long periodDuration, String parentMimeType, List<Segment.Timeline> segmentTimelineList) long periodDuration, String parentMimeType, List<Segment.Timeline> segmentTimelineList)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
int id; String id = xpp.getAttributeValue(null, "id");
try {
id = parseInt(xpp, "id");
} catch (NumberFormatException nfe) {
Log.d(TAG, "Unable to parse id; " + nfe.getMessage());
// TODO: need a way to generate a unique and stable id; use hashCode for now
id = xpp.getAttributeValue(null, "id").hashCode();
}
int bandwidth = parseInt(xpp, "bandwidth") / 8; int bandwidth = parseInt(xpp, "bandwidth") / 8;
int audioSamplingRate = parseInt(xpp, "audioSamplingRate"); int audioSamplingRate = parseInt(xpp, "audioSamplingRate");
int width = parseInt(xpp, "width"); int width = parseInt(xpp, "width");

View File

@ -81,7 +81,7 @@ public class Representation {
/** /**
* Generates a cache key for the {@link Representation}, in the format * Generates a cache key for the {@link Representation}, in the format
* {@link #contentId}.{@link #format.id}.{@link #revisionId}. * {@code contentId + "." + format.id + "." + revisionId}.
* *
* @return A cache key. * @return A cache key.
*/ */

View File

@ -63,7 +63,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
private final int maxHeight; private final int maxHeight;
private final SparseArray<FragmentedMp4Extractor> extractors; private final SparseArray<FragmentedMp4Extractor> extractors;
private final Format[] formats; private final SmoothStreamingFormat[] formats;
/** /**
* @param baseUrl The base URL for the streams. * @param baseUrl The base URL for the streams.
@ -94,16 +94,16 @@ public class SmoothStreamingChunkSource implements ChunkSource {
} }
int trackCount = trackIndices != null ? trackIndices.length : streamElement.tracks.length; int trackCount = trackIndices != null ? trackIndices.length : streamElement.tracks.length;
formats = new Format[trackCount]; formats = new SmoothStreamingFormat[trackCount];
extractors = new SparseArray<FragmentedMp4Extractor>(); extractors = new SparseArray<FragmentedMp4Extractor>();
int maxWidth = 0; int maxWidth = 0;
int maxHeight = 0; int maxHeight = 0;
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
int trackIndex = trackIndices != null ? trackIndices[i] : i; int trackIndex = trackIndices != null ? trackIndices[i] : i;
TrackElement trackElement = streamElement.tracks[trackIndex]; TrackElement trackElement = streamElement.tracks[trackIndex];
formats[i] = new Format(trackIndex, trackElement.mimeType, trackElement.maxWidth, formats[i] = new SmoothStreamingFormat(String.valueOf(trackIndex), trackElement.mimeType,
trackElement.maxHeight, trackElement.numChannels, trackElement.sampleRate, trackElement.maxWidth, trackElement.maxHeight, trackElement.numChannels,
trackElement.bitrate / 8); trackElement.sampleRate, trackElement.bitrate / 8, trackIndex);
maxWidth = Math.max(maxWidth, trackElement.maxWidth); maxWidth = Math.max(maxWidth, trackElement.maxWidth);
maxHeight = Math.max(maxHeight, trackElement.maxHeight); maxHeight = Math.max(maxHeight, trackElement.maxHeight);
@ -155,14 +155,14 @@ public class SmoothStreamingChunkSource implements ChunkSource {
long playbackPositionUs, ChunkOperationHolder out) { long playbackPositionUs, ChunkOperationHolder out) {
evaluation.queueSize = queue.size(); evaluation.queueSize = queue.size();
formatEvaluator.evaluate(queue, playbackPositionUs, formats, evaluation); formatEvaluator.evaluate(queue, playbackPositionUs, formats, evaluation);
Format selectedFormat = evaluation.format; SmoothStreamingFormat selectedFormat = (SmoothStreamingFormat) evaluation.format;
out.queueSize = evaluation.queueSize; out.queueSize = evaluation.queueSize;
if (selectedFormat == null) { if (selectedFormat == null) {
out.chunk = null; out.chunk = null;
return; return;
} else if (out.queueSize == queue.size() && out.chunk != null } else if (out.queueSize == queue.size() && out.chunk != null
&& out.chunk.format.id == evaluation.format.id) { && out.chunk.format.id.equals(evaluation.format.id)) {
// We already have a chunk, and the evaluation hasn't changed either the format or the size // We already have a chunk, and the evaluation hasn't changed either the format or the size
// of the queue. Do nothing. // of the queue. Do nothing.
return; return;
@ -181,11 +181,12 @@ public class SmoothStreamingChunkSource implements ChunkSource {
} }
boolean isLastChunk = nextChunkIndex == streamElement.chunkCount - 1; boolean isLastChunk = nextChunkIndex == streamElement.chunkCount - 1;
String requestUrl = streamElement.buildRequestUrl(selectedFormat.id, nextChunkIndex); String requestUrl = streamElement.buildRequestUrl(selectedFormat.trackIndex,
nextChunkIndex);
Uri uri = Uri.parse(baseUrl + '/' + requestUrl); Uri uri = Uri.parse(baseUrl + '/' + requestUrl);
Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null, Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null,
extractors.get(selectedFormat.id), dataSource, nextChunkIndex, isLastChunk, extractors.get(Integer.parseInt(selectedFormat.id)), dataSource, nextChunkIndex,
streamElement.getStartTimeUs(nextChunkIndex), isLastChunk, streamElement.getStartTimeUs(nextChunkIndex),
isLastChunk ? -1 : streamElement.getStartTimeUs(nextChunkIndex + 1), 0); isLastChunk ? -1 : streamElement.getStartTimeUs(nextChunkIndex + 1), 0);
out.chunk = mediaChunk; out.chunk = mediaChunk;
} }
@ -254,4 +255,16 @@ public class SmoothStreamingChunkSource implements ChunkSource {
data[secondPosition] = temp; data[secondPosition] = temp;
} }
private static final class SmoothStreamingFormat extends Format {
public final int trackIndex;
public SmoothStreamingFormat(String id, String mimeType, int width, int height,
int numChannels, int audioSamplingRate, int bandwidth, int trackIndex) {
super(id, mimeType, width, height, numChannels, audioSamplingRate, bandwidth);
this.trackIndex = trackIndex;
}
}
} }