From 4fd4c8951865e246b128c987174f7c8ed2297a6d Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 4 Jul 2014 01:04:10 +0100 Subject: [PATCH] Refactored ExoPlayer to use String-based format ids. --- .../exoplayer/demo/full/EventLogger.java | 6 ++-- .../demo/full/player/DemoPlayer.java | 11 ++++--- .../exoplayer/chunk/ChunkSampleSource.java | 10 +++--- .../android/exoplayer/chunk/Format.java | 21 ++++++++++-- .../exoplayer/chunk/FormatEvaluator.java | 2 +- .../exoplayer/dash/DashMp4ChunkSource.java | 14 ++++---- .../exoplayer/dash/DashWebmChunkSource.java | 14 ++++---- .../MediaPresentationDescriptionParser.java | 12 +------ .../exoplayer/dash/mpd/Representation.java | 2 +- .../SmoothStreamingChunkSource.java | 33 +++++++++++++------ 10 files changed, 73 insertions(+), 52 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java index 7db4240d42..f8306d10d1 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java @@ -91,7 +91,7 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener } @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) { loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime(); if (VerboseLogUtil.isTagEnabled(TAG)) { @@ -110,13 +110,13 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener } @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 + ", " + Integer.toString(trigger) + "]"); } @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 + ", " + Integer.toString(trigger) + "]"); } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java index 79934c712b..acf69656ed 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java @@ -118,11 +118,11 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi * A listener for debugging information. */ public interface InfoListener { - void onVideoFormatEnabled(int formatId, int trigger, int mediaTimeMs); - void onAudioFormatEnabled(int formatId, int trigger, int mediaTimeMs); + void onVideoFormatEnabled(String formatId, int trigger, int mediaTimeMs); + void onAudioFormatEnabled(String formatId, int trigger, int mediaTimeMs); void onDroppedFrames(int count, long elapsed); 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); void onLoadCompleted(int sourceId); } @@ -398,7 +398,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi } @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) { return; } @@ -469,7 +470,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi } @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) { if (infoListener != null) { infoListener.onLoadStarted(sourceId, formatId, trigger, isInitialization, mediaStartTimeMs, diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index 5800acca26..d9f3923495 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -59,7 +59,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener { * load is for initialization data. * @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); /** @@ -126,7 +126,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener { * {@link ChunkSource}. * @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; } - } 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, mediaChunk.startTimeUs); MediaFormat format = mediaChunk.getMediaFormat(); @@ -653,7 +653,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener { 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 long totalBytes) { 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) { if (eventHandler != null && eventListener != null) { eventHandler.post(new Runnable() { diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/Format.java b/library/src/main/java/com/google/android/exoplayer/chunk/Format.java index d7d301404d..4aabe601dc 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/Format.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/Format.java @@ -20,7 +20,7 @@ import java.util.Comparator; /** * A format definition for streams. */ -public final class Format { +public class Format { /** * Sorts {@link Format} objects in order of decreasing bandwidth. @@ -37,7 +37,7 @@ public final class Format { /** * An identifier for the format. */ - public final int id; + public final String id; /** * The mime type of the format. @@ -70,6 +70,8 @@ public final class Format { public final int bandwidth; /** + * @deprecated Format identifiers are now strings. + * * @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. @@ -78,8 +80,23 @@ public final class Format { * @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. */ + @Deprecated public Format(int id, String mimeType, int width, int height, int numChannels, 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.mimeType = mimeType; this.width = width; diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java index 7998c5ebde..1b8b9d9082 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java @@ -146,7 +146,7 @@ public interface FormatEvaluator { public void evaluate(List queue, long playbackPositionUs, Format[] formats, Evaluation evaluation) { 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.format = newFormat; diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java index f1b1abcf4b..fb2bbd19f2 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java @@ -35,10 +35,10 @@ import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.NonBlockingInputStream; import android.util.Log; -import android.util.SparseArray; import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; import java.util.List; /** @@ -64,8 +64,8 @@ public class DashMp4ChunkSource implements ChunkSource { private final int numSegmentsPerChunk; private final Format[] formats; - private final SparseArray representations; - private final SparseArray extractors; + private final HashMap representations; + private final HashMap extractors; private boolean lastChunkWasInitialization; @@ -92,8 +92,8 @@ public class DashMp4ChunkSource implements ChunkSource { this.evaluator = evaluator; this.numSegmentsPerChunk = numSegmentsPerChunk; this.formats = new Format[representations.length]; - this.extractors = new SparseArray(); - this.representations = new SparseArray(); + this.extractors = new HashMap(); + this.representations = new HashMap(); this.trackInfo = new TrackInfo(representations[0].format.mimeType, representations[0].periodDuration * 1000); this.evaluation = new Evaluation(); @@ -103,7 +103,7 @@ public class DashMp4ChunkSource implements ChunkSource { formats[i] = representations[i].format; maxWidth = Math.max(formats[i].width, maxWidth); 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.maxWidth = maxWidth; @@ -152,7 +152,7 @@ public class DashMp4ChunkSource implements ChunkSource { out.chunk = null; return; } 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 // of the queue. Leave unchanged. return; diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java index f3e00f34c0..95db7488d9 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java @@ -35,10 +35,10 @@ import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.NonBlockingInputStream; import android.util.Log; -import android.util.SparseArray; import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; import java.util.List; /** @@ -57,8 +57,8 @@ public class DashWebmChunkSource implements ChunkSource { private final int numSegmentsPerChunk; private final Format[] formats; - private final SparseArray representations; - private final SparseArray extractors; + private final HashMap representations; + private final HashMap extractors; private boolean lastChunkWasInitialization; @@ -73,8 +73,8 @@ public class DashWebmChunkSource implements ChunkSource { this.evaluator = evaluator; this.numSegmentsPerChunk = numSegmentsPerChunk; this.formats = new Format[representations.length]; - this.extractors = new SparseArray(); - this.representations = new SparseArray(); + this.extractors = new HashMap(); + this.representations = new HashMap(); this.trackInfo = new TrackInfo( representations[0].format.mimeType, representations[0].periodDuration * 1000); this.evaluation = new Evaluation(); @@ -84,7 +84,7 @@ public class DashWebmChunkSource implements ChunkSource { formats[i] = representations[i].format; maxWidth = Math.max(formats[i].width, maxWidth); 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.maxWidth = maxWidth; @@ -133,7 +133,7 @@ public class DashWebmChunkSource implements ChunkSource { out.chunk = null; return; } 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 // of the queue. Leave unchanged. return; diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java index 9b0df77761..d14fd05f09 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java @@ -21,7 +21,6 @@ import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.util.MimeTypes; import android.net.Uri; -import android.util.Log; import org.xml.sax.helpers.DefaultHandler; import org.xmlpull.v1.XmlPullParser; @@ -45,8 +44,6 @@ import java.util.regex.Pattern; */ public class MediaPresentationDescriptionParser extends DefaultHandler { - private static final String TAG = "MediaPresentationDescriptionParser"; - // Note: Does not support the date part of ISO 8601 private static final Pattern DURATION = 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, long periodDuration, String parentMimeType, List segmentTimelineList) throws XmlPullParserException, IOException { - int 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(); - } + String id = xpp.getAttributeValue(null, "id"); int bandwidth = parseInt(xpp, "bandwidth") / 8; int audioSamplingRate = parseInt(xpp, "audioSamplingRate"); int width = parseInt(xpp, "width"); diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java index e5b11e94ca..7b8b996888 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java @@ -81,7 +81,7 @@ public class Representation { /** * 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. */ diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index 581d001de4..e996366ae2 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -63,7 +63,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { private final int maxHeight; private final SparseArray extractors; - private final Format[] formats; + private final SmoothStreamingFormat[] formats; /** * @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; - formats = new Format[trackCount]; + formats = new SmoothStreamingFormat[trackCount]; extractors = new SparseArray(); int maxWidth = 0; int maxHeight = 0; for (int i = 0; i < trackCount; i++) { int trackIndex = trackIndices != null ? trackIndices[i] : i; TrackElement trackElement = streamElement.tracks[trackIndex]; - formats[i] = new Format(trackIndex, trackElement.mimeType, trackElement.maxWidth, - trackElement.maxHeight, trackElement.numChannels, trackElement.sampleRate, - trackElement.bitrate / 8); + formats[i] = new SmoothStreamingFormat(String.valueOf(trackIndex), trackElement.mimeType, + trackElement.maxWidth, trackElement.maxHeight, trackElement.numChannels, + trackElement.sampleRate, trackElement.bitrate / 8, trackIndex); maxWidth = Math.max(maxWidth, trackElement.maxWidth); maxHeight = Math.max(maxHeight, trackElement.maxHeight); @@ -155,14 +155,14 @@ public class SmoothStreamingChunkSource implements ChunkSource { long playbackPositionUs, ChunkOperationHolder out) { evaluation.queueSize = queue.size(); formatEvaluator.evaluate(queue, playbackPositionUs, formats, evaluation); - Format selectedFormat = evaluation.format; + SmoothStreamingFormat selectedFormat = (SmoothStreamingFormat) evaluation.format; out.queueSize = evaluation.queueSize; if (selectedFormat == null) { out.chunk = null; return; } 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 // of the queue. Do nothing. return; @@ -181,11 +181,12 @@ public class SmoothStreamingChunkSource implements ChunkSource { } 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); Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null, - extractors.get(selectedFormat.id), dataSource, nextChunkIndex, isLastChunk, - streamElement.getStartTimeUs(nextChunkIndex), + extractors.get(Integer.parseInt(selectedFormat.id)), dataSource, nextChunkIndex, + isLastChunk, streamElement.getStartTimeUs(nextChunkIndex), isLastChunk ? -1 : streamElement.getStartTimeUs(nextChunkIndex + 1), 0); out.chunk = mediaChunk; } @@ -254,4 +255,16 @@ public class SmoothStreamingChunkSource implements ChunkSource { 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; + } + + } + }