Commit d3d63101 by olly Committed by Oliver Woodman

[Refactor - Step #5] Introduce TrackSelector

Notes:

- The way this works is that every time the player needs to
  select some tracks it invokes the TrackSelector. When a
  track selection is actually activated (i.e. "hits the
  screen") it gets passed back to the TrackSelector, which
  allows it to expose the current tracks through an API that
  it may choose to define. Since playlist support doesn't exist
  yet, it's currently the case that the pass-back always occurs
  immediately.
- A TrackSelector can invalidate its previous selections if its
  selection criteria changes. This will force the player to invoke
  it again to make a new selection. If the new selection is the
  same as the previous one for a renderer then the player handles
  this efficiently (i.e. turns it into a no-op).
- DefaultTrackSelector supports disabling/enabling of renderers.
  Separately, it supports overrides to select specific formats.
  Since formats may change (playlists/periods), overrides are
  specific to not only the renderer but also the set of formats
  that are available to it. If the formats available to a renderer
  change then the override will no longer apply. If the same set
  of formats become available at some point later, it will apply
  once more. This will nicely handle cases like ad-insertion where
  the ads have different formats, but all segments of main content
  use the same set of formats.
- In general, in  multi-period or playlist cases, the preferred
  way of selecting formats will be via constraints (e.g. "don't play
  HD", "prefer higher quality audio") rather than explicit format
  selections. The ability to set various constraints on
  DefaultTrackSelector is future work.

Note about the demo app:

- I've removed the verbose log toggle. I doubt anyone has
  ever used it! I've also removed the background audio option.
  Without using a service it can't be considered a reference
  implementation, so it's probably best to leave developers to
  figure this one out. Finally, listening to AudioCapabilities
  has also gone. This will be replaced by having the player
  detect and handle the capabilities change internally in a
  future CL. This will work by allowing a renderer to invalidate
  the track selections when its capabilities change, much like
  how a selector is able to invalidate the track selections in
  this CL.
- It's now possible to enable ABR with an arbitrary subset of
  tracks.
- Unsupported tracks are shown grayed out in the UI. I'm not
  showing tracks that aren't associated to any renderer, but we
  could optionally add that later.
- Every time the tracks change, there's logcat output showing
  all of the tracks and which ones are enabled. Unassociated
  tracks are displayed in this output.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=117122202
parent 9c98c4bb
...@@ -15,13 +15,17 @@ ...@@ -15,13 +15,17 @@
*/ */
package com.google.android.exoplayer.demo; package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
import com.google.android.exoplayer.ExoPlayer; import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.TimeRange; import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.audio.AudioTrack; import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.demo.player.DemoPlayer; import com.google.android.exoplayer.demo.player.DemoPlayer;
import com.google.android.exoplayer.util.VerboseLogUtil;
import android.media.MediaCodec.CryptoException; import android.media.MediaCodec.CryptoException;
import android.os.SystemClock; import android.os.SystemClock;
...@@ -46,13 +50,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener ...@@ -46,13 +50,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
} }
private long sessionStartTimeMs; private long sessionStartTimeMs;
private long[] loadStartTimeMs;
private long[] availableRangeValuesUs; private long[] availableRangeValuesUs;
public EventLogger() {
loadStartTimeMs = new long[DemoPlayer.RENDERER_COUNT];
}
public void startSession() { public void startSession() {
sessionStartTimeMs = SystemClock.elapsedRealtime(); sessionStartTimeMs = SystemClock.elapsedRealtime();
Log.d(TAG, "start [0]"); Log.d(TAG, "start [0]");
...@@ -82,6 +81,54 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener ...@@ -82,6 +81,54 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
+ ", " + pixelWidthHeightRatio + "]"); + ", " + pixelWidthHeightRatio + "]");
} }
@Override
public void onTracksChanged(TrackInfo trackInfo) {
Log.d(TAG, "Tracks [");
// Log tracks associated to renderers.
for (int rendererIndex = 0; rendererIndex < trackInfo.rendererCount; rendererIndex++) {
TrackGroupArray trackGroups = trackInfo.getTrackGroups(rendererIndex);
TrackSelection trackSelection = trackInfo.getTrackSelection(rendererIndex);
if (trackGroups.length > 0) {
Log.d(TAG, " Renderer:" + rendererIndex + " [");
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex);
String adaptiveSupport = getAdaptiveSupportString(
trackGroup.length, trackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false));
Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " [");
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
String status = getTrackStatusString(trackSelection, groupIndex, trackIndex);
String formatSupport = getFormatSupportString(
trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
+ getFormatString(trackGroup.getFormat(trackIndex))
+ ", supported=" + formatSupport);
}
Log.d(TAG, " ]");
}
Log.d(TAG, " ]");
}
}
// Log tracks not associated with a renderer.
TrackGroupArray trackGroups = trackInfo.getUnassociatedTrackGroups();
if (trackGroups.length > 0) {
Log.d(TAG, " Renderer:None [");
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
Log.d(TAG, " Group:" + groupIndex + " [");
TrackGroup trackGroup = trackGroups.get(groupIndex);
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
String status = getTrackStatusString(false);
String formatSupport = getFormatSupportString(TrackRenderer.FORMAT_UNSUPPORTED_TYPE);
Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
+ getFormatString(trackGroup.getFormat(trackIndex))
+ ", supported=" + formatSupport);
}
Log.d(TAG, " ]");
}
Log.d(TAG, " ]");
}
Log.d(TAG, "]");
}
// DemoPlayer.InfoListener // DemoPlayer.InfoListener
@Override @Override
...@@ -98,21 +145,13 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener ...@@ -98,21 +145,13 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override @Override
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format, public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
long mediaStartTimeMs, long mediaEndTimeMs) { long mediaStartTimeMs, long mediaEndTimeMs) {
loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime(); // Do nothing.
if (VerboseLogUtil.isTagEnabled(TAG)) {
Log.v(TAG, "loadStart [" + getSessionTimeString() + ", " + sourceId + ", " + type
+ ", " + mediaStartTimeMs + ", " + mediaEndTimeMs + "]");
}
} }
@Override @Override
public void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format, public void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs) { long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs) {
if (VerboseLogUtil.isTagEnabled(TAG)) { // Do nothing.
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId];
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " + downloadTime
+ "]");
}
} }
@Override @Override
...@@ -187,7 +226,15 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener ...@@ -187,7 +226,15 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e); Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
} }
private String getStateString(int state) { private String getSessionTimeString() {
return getTimeString(SystemClock.elapsedRealtime() - sessionStartTimeMs);
}
private static String getTimeString(long timeMs) {
return TIME_FORMAT.format((timeMs) / 1000f);
}
private static String getStateString(int state) {
switch (state) { switch (state) {
case ExoPlayer.STATE_BUFFERING: case ExoPlayer.STATE_BUFFERING:
return "B"; return "B";
...@@ -204,12 +251,76 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener ...@@ -204,12 +251,76 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
} }
} }
private String getSessionTimeString() { private static String getFormatSupportString(int formatSupport) {
return getTimeString(SystemClock.elapsedRealtime() - sessionStartTimeMs); switch (formatSupport) {
case TrackRenderer.FORMAT_HANDLED:
return "YES";
case TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES:
return "NO_EXCEEDS_CAPABILITIES";
case TrackRenderer.FORMAT_UNSUPPORTED_SUBTYPE:
return "NO_UNSUPPORTED_TYPE";
case TrackRenderer.FORMAT_UNSUPPORTED_TYPE:
return "NO";
default:
return "?";
}
} }
private String getTimeString(long timeMs) { private static String getAdaptiveSupportString(int trackCount, int adaptiveSupport) {
return TIME_FORMAT.format((timeMs) / 1000f); if (trackCount < 2) {
return "N/A";
}
switch (adaptiveSupport) {
case TrackRenderer.ADAPTIVE_SEAMLESS:
return "YES";
case TrackRenderer.ADAPTIVE_NOT_SEAMLESS:
return "YES_NOT_SEAMLESS";
case TrackRenderer.ADAPTIVE_NOT_SUPPORTED:
return "NO";
default:
return "?";
}
}
private static String getFormatString(Format format) {
StringBuilder builder = new StringBuilder();
builder.append("id=").append(format.id).append(", mimeType=").append(format.sampleMimeType);
if (format.bitrate != Format.NO_VALUE) {
builder.append(", bitrate=").append(format.bitrate);
}
if (format.width != -1 && format.height != -1) {
builder.append(", res=").append(format.width).append("x").append(format.height);
}
if (format.frameRate != -1) {
builder.append(", fps=").append(format.frameRate);
}
if (format.channelCount != -1) {
builder.append(", channels=").append(format.channelCount);
}
if (format.sampleRate != -1) {
builder.append(", sample_rate=").append(format.sampleRate);
}
if (format.language != null) {
builder.append(", language=").append(format.language);
}
return builder.toString();
}
private static String getTrackStatusString(TrackSelection selection, int groupIndex,
int trackIndex) {
boolean groupEnabled = selection != null && selection.group == groupIndex;
if (groupEnabled) {
for (int i = 0; i < selection.length; i++) {
if (selection.getTrack(i) == trackIndex) {
return getTrackStatusString(true);
}
}
}
return getTrackStatusString(false);
}
private static String getTrackStatusString(boolean enabled) {
return enabled ? "[X]" : "[ ]";
} }
} }
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package com.google.android.exoplayer.demo.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.CodecCounters; import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.DefaultTrackSelector;
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer; import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
...@@ -51,7 +53,6 @@ import android.os.Handler; ...@@ -51,7 +53,6 @@ import android.os.Handler;
import android.view.Surface; import android.view.Surface;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
...@@ -61,11 +62,12 @@ import java.util.concurrent.CopyOnWriteArrayList; ...@@ -61,11 +62,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
* with one of a number of {@link SourceBuilder} classes to suit different use cases (e.g. DASH, * with one of a number of {@link SourceBuilder} classes to suit different use cases (e.g. DASH,
* SmoothStreaming and so on). * SmoothStreaming and so on).
*/ */
public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener, public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.EventListener,
HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener, ChunkSampleSource.EventListener, HlsSampleSource.EventListener,
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener, DefaultBandwidthMeter.EventListener, MediaCodecVideoTrackRenderer.EventListener,
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer, MediaCodecAudioTrackRenderer.EventListener, StreamingDrmSessionManager.EventListener,
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider { DashChunkSource.EventListener, TextRenderer, MetadataRenderer<Map<String, Object>>,
DebugTextViewHelper.Provider {
/** /**
* Builds a source to play. * Builds a source to play.
...@@ -86,6 +88,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -86,6 +88,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
public interface Listener { public interface Listener {
void onStateChanged(boolean playWhenReady, int playbackState); void onStateChanged(boolean playWhenReady, int playbackState);
void onError(Exception e); void onError(Exception e);
void onTracksChanged(TrackInfo trackInfo);
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio); float pixelWidthHeightRatio);
} }
...@@ -146,8 +149,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -146,8 +149,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
public static final int STATE_BUFFERING = ExoPlayer.STATE_BUFFERING; public static final int STATE_BUFFERING = ExoPlayer.STATE_BUFFERING;
public static final int STATE_READY = ExoPlayer.STATE_READY; public static final int STATE_READY = ExoPlayer.STATE_READY;
public static final int STATE_ENDED = ExoPlayer.STATE_ENDED; public static final int STATE_ENDED = ExoPlayer.STATE_ENDED;
public static final int TRACK_DISABLED = ExoPlayer.TRACK_DISABLED;
public static final int TRACK_DEFAULT = ExoPlayer.TRACK_DEFAULT;
public static final int RENDERER_COUNT = 4; public static final int RENDERER_COUNT = 4;
public static final int TYPE_VIDEO = 0; public static final int TYPE_VIDEO = 0;
...@@ -156,6 +157,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -156,6 +157,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
public static final int TYPE_METADATA = 3; public static final int TYPE_METADATA = 3;
private final ExoPlayer player; private final ExoPlayer player;
private final DefaultTrackSelector trackSelector;
private final SourceBuilder sourceBuilder; private final SourceBuilder sourceBuilder;
private final BandwidthMeter bandwidthMeter; private final BandwidthMeter bandwidthMeter;
private final MediaCodecVideoTrackRenderer videoRenderer; private final MediaCodecVideoTrackRenderer videoRenderer;
...@@ -165,9 +167,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -165,9 +167,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
private Surface surface; private Surface surface;
private Format videoFormat; private Format videoFormat;
private int videoTrackToRestore; private TrackInfo trackInfo;
private boolean backgrounded;
private CaptionListener captionListener; private CaptionListener captionListener;
private Id3MetadataListener id3MetadataListener; private Id3MetadataListener id3MetadataListener;
...@@ -193,12 +193,14 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -193,12 +193,14 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
id3Renderer}; id3Renderer};
// Build the player and associated objects. // Build the player and associated objects.
player = ExoPlayer.Factory.newInstance(renderers, 1000, 5000); trackSelector = new DefaultTrackSelector(mainHandler, this);
player = ExoPlayer.Factory.newInstance(renderers, trackSelector, 1000, 5000);
player.addListener(this); player.addListener(this);
playerControl = new PlayerControl(player); playerControl = new PlayerControl(player);
}
// Set initial state, with the text renderer initially disabled. public DefaultTrackSelector getTrackSelector() {
player.setSelectedTrack(TYPE_TEXT, TRACK_DISABLED); return trackSelector;
} }
public PlayerControl getPlayerControl() { public PlayerControl getPlayerControl() {
...@@ -243,41 +245,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -243,41 +245,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
pushSurface(true); pushSurface(true);
} }
public int getTrackCount(int type) { public TrackInfo getTrackInfo() {
return player.getTrackCount(type); return trackInfo;
}
public Format getTrackFormat(int type, int index) {
return player.getTrackFormat(type, index);
}
public int getSelectedTrack(int type) {
return player.getSelectedTrack(type);
}
public void setSelectedTrack(int type, int index) {
player.setSelectedTrack(type, index);
if (type == TYPE_TEXT && index < 0 && captionListener != null) {
captionListener.onCues(Collections.<Cue>emptyList());
}
}
public boolean getBackgrounded() {
return backgrounded;
}
public void setBackgrounded(boolean backgrounded) {
if (this.backgrounded == backgrounded) {
return;
}
this.backgrounded = backgrounded;
if (backgrounded) {
videoTrackToRestore = getSelectedTrack(TYPE_VIDEO);
setSelectedTrack(TYPE_VIDEO, TRACK_DISABLED);
blockingClearSurface();
} else {
setSelectedTrack(TYPE_VIDEO, videoTrackToRestore);
}
} }
public void prepare() { public void prepare() {
...@@ -352,6 +321,14 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -352,6 +321,14 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
@Override @Override
public void onTracksChanged(TrackInfo trackInfo) {
this.trackInfo = trackInfo;
for (Listener listener : listeners) {
listener.onTracksChanged(trackInfo);
}
}
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) { float pixelWidthHeightRatio) {
for (Listener listener : listeners) { for (Listener listener : listeners) {
...@@ -451,14 +428,14 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -451,14 +428,14 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
@Override @Override
public void onCues(List<Cue> cues) { public void onCues(List<Cue> cues) {
if (captionListener != null && getSelectedTrack(TYPE_TEXT) != TRACK_DISABLED) { if (captionListener != null && trackInfo.getTrackSelection(TYPE_TEXT) != null) {
captionListener.onCues(cues); captionListener.onCues(cues);
} }
} }
@Override @Override
public void onMetadata(Map<String, Object> metadata) { public void onMetadata(Map<String, Object> metadata) {
if (id3MetadataListener != null && getSelectedTrack(TYPE_METADATA) != TRACK_DISABLED) { if (id3MetadataListener != null && trackInfo.getTrackSelection(TYPE_METADATA) != null) {
id3MetadataListener.onId3Metadata(metadata); id3MetadataListener.onId3Metadata(metadata);
} }
} }
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="?android:attr/listDivider"/>
...@@ -94,13 +94,6 @@ ...@@ -94,13 +94,6 @@
android:visibility="gone" android:visibility="gone"
android:onClick="showTextPopup"/> android:onClick="showTextPopup"/>
<Button android:id="@+id/verbose_log_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/logging"
style="@style/DemoButton"
android:onClick="showVerboseLogPopup"/>
<Button android:id="@+id/retry_button" <Button android:id="@+id/retry_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
...@@ -19,23 +19,19 @@ ...@@ -19,23 +19,19 @@
<!-- The user visible name of the application. [CHAR LIMIT=20] --> <!-- The user visible name of the application. [CHAR LIMIT=20] -->
<string name="application_name">ExoPlayer Demo</string> <string name="application_name">ExoPlayer Demo</string>
<string name="enable_background_audio">Play in background</string>
<string name="video">Video</string> <string name="video">Video</string>
<string name="audio">Audio</string> <string name="audio">Audio</string>
<string name="text">Text</string> <string name="text">Text</string>
<string name="logging">Logging</string> <string name="retry">Retry</string>
<string name="logging_normal">Normal</string>
<string name="logging_verbose">Verbose</string> <string name="selection_disabled">Disabled</string>
<string name="retry">Retry</string> <string name="selection_default">Default</string>
<string name="off">[off]</string> <string name="selection_default_none">Default (none)</string>
<string name="error_drm_not_supported">Protected content not supported on API levels below 18</string> <string name="error_drm_not_supported">Protected content not supported on API levels below 18</string>
......
...@@ -73,12 +73,13 @@ import android.os.Looper; ...@@ -73,12 +73,13 @@ import android.os.Looper;
* <a name="State"></a> * <a name="State"></a>
* <h3>Player state</h3> * <h3>Player state</h3>
* *
* <p>The components of an {@link ExoPlayer}'s state can be divided into two distinct groups. State * <p>The components of an {@link ExoPlayer}'s state can be divided into two distinct groups. The
* accessed by {@link #getSelectedTrack(int)} and {@link #getPlayWhenReady()} is only ever * state accessed by calling {@link #getPlayWhenReady()} is only ever changed by invoking
* changed by invoking the player's methods, and are never changed as a result of operations that * {@link #setPlayWhenReady(boolean)}, and is never changed as a result of operations that have been
* have been performed asynchronously by the playback thread. In contrast, the playback state * performed asynchronously by the playback thread. In contrast, the playback state accessed by
* accessed by {@link #getPlaybackState()} is only ever changed as a result of operations * calling {@link #getPlaybackState()} is only ever changed as a result of operations completing on
* completing on the playback thread, as illustrated below.</p> * the playback thread, as illustrated below.</p>
*
* <p align="center"><img src="../../../../../images/exoplayer_state.png" * <p align="center"><img src="../../../../../images/exoplayer_state.png"
* alt="ExoPlayer state" * alt="ExoPlayer state"
* border="0"/></p> * border="0"/></p>
...@@ -118,15 +119,16 @@ public interface ExoPlayer { ...@@ -118,15 +119,16 @@ public interface ExoPlayer {
* Must be invoked from a thread that has an associated {@link Looper}. * Must be invoked from a thread that has an associated {@link Looper}.
* *
* @param renderers The {@link TrackRenderer}s that will be used by the instance. * @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param minBufferMs A minimum duration of data that must be buffered for playback to start * @param minBufferMs A minimum duration of data that must be buffered for playback to start
* or resume following a user action such as a seek. * or resume following a user action such as a seek.
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume * @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
* after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and * after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking). * not due to a user action such as starting playback or seeking).
*/ */
public static ExoPlayer newInstance(TrackRenderer[] renderers, int minBufferMs, public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector,
int minRebufferMs) { int minBufferMs, int minRebufferMs) {
return new ExoPlayerImpl(renderers, minBufferMs, minRebufferMs); return new ExoPlayerImpl(renderers, trackSelector, minBufferMs, minRebufferMs);
} }
/** /**
...@@ -135,9 +137,11 @@ public interface ExoPlayer { ...@@ -135,9 +137,11 @@ public interface ExoPlayer {
* Must be invoked from a thread that has an associated {@link Looper}. * Must be invoked from a thread that has an associated {@link Looper}.
* *
* @param renderers The {@link TrackRenderer}s that will be used by the instance. * @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/ */
public static ExoPlayer newInstance(TrackRenderer... renderers) { public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector) {
return new ExoPlayerImpl(renderers, DEFAULT_MIN_BUFFER_MS, DEFAULT_MIN_REBUFFER_MS); return new ExoPlayerImpl(renderers, trackSelector, DEFAULT_MIN_BUFFER_MS,
DEFAULT_MIN_REBUFFER_MS);
} }
} }
...@@ -220,17 +224,6 @@ public interface ExoPlayer { ...@@ -220,17 +224,6 @@ public interface ExoPlayer {
static final int STATE_ENDED = 5; static final int STATE_ENDED = 5;
/** /**
* A value that can be passed as the second argument to {@link #setSelectedTrack(int, int)} to
* disable the renderer.
*/
static final int TRACK_DISABLED = -1;
/**
* A value that can be passed as the second argument to {@link #setSelectedTrack(int, int)} to
* select the default track.
*/
static final int TRACK_DEFAULT = 0;
/**
* Represents an unknown time or duration. * Represents an unknown time or duration.
*/ */
static final long UNKNOWN_TIME = -1; static final long UNKNOWN_TIME = -1;
...@@ -272,41 +265,6 @@ public interface ExoPlayer { ...@@ -272,41 +265,6 @@ public interface ExoPlayer {
void prepare(SampleSource sampleSource); void prepare(SampleSource sampleSource);
/** /**
* Returns the number of tracks exposed by the specified renderer.
*
* @param rendererIndex The index of the renderer.
* @return The number of tracks.
*/
int getTrackCount(int rendererIndex);
/**
* Returns the format of a track.
*
* @param rendererIndex The index of the renderer.
* @param trackIndex The index of the track.
* @return The format of the track.
*/
Format getTrackFormat(int rendererIndex, int trackIndex);
/**
* Selects a track for the specified renderer.
*
* @param rendererIndex The index of the renderer.
* @param trackIndex The index of the track. A negative value or a value greater than or equal to
* the renderer's track count will disable the renderer.
*/
void setSelectedTrack(int rendererIndex, int trackIndex);
/**
* Returns the index of the currently selected track for the specified renderer.
*
* @param rendererIndex The index of the renderer.
* @return The selected track. A negative value or a value greater than or equal to the renderer's
* track count indicates that the renderer is disabled.
*/
int getSelectedTrack(int rendererIndex);
/**
* Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}.
* If the player is already in this state, then this method can be used to pause and resume * If the player is already in this state, then this method can be used to pause and resume
* playback. * playback.
......
...@@ -23,7 +23,6 @@ import android.os.Looper; ...@@ -23,7 +23,6 @@ import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.util.Log; import android.util.Log;
import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
/** /**
...@@ -36,8 +35,6 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -36,8 +35,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final Handler eventHandler; private final Handler eventHandler;
private final ExoPlayerImplInternal internalPlayer; private final ExoPlayerImplInternal internalPlayer;
private final CopyOnWriteArraySet<Listener> listeners; private final CopyOnWriteArraySet<Listener> listeners;
private final Format[][] trackFormats;
private final int[] selectedTrackIndices;
private boolean playWhenReady; private boolean playWhenReady;
private int playbackState; private int playbackState;
...@@ -46,7 +43,8 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -46,7 +43,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
/** /**
* Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}. * Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}.
* *
* @param renderers The {@link TrackRenderer}s belonging to this instance. * @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param minBufferMs A minimum duration of data that must be buffered for playback to start * @param minBufferMs A minimum duration of data that must be buffered for playback to start
* or resume following a user action such as a seek. * or resume following a user action such as a seek.
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume * @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
...@@ -54,23 +52,22 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -54,23 +52,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
* not due to a user action such as starting playback or seeking). * not due to a user action such as starting playback or seeking).
*/ */
@SuppressLint("HandlerLeak") @SuppressLint("HandlerLeak")
public ExoPlayerImpl(TrackRenderer[] renderers, int minBufferMs, int minRebufferMs) { public ExoPlayerImpl(TrackRenderer[] renderers, TrackSelector trackSelector, int minBufferMs,
int minRebufferMs) {
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION); Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
Assertions.checkNotNull(renderers); Assertions.checkNotNull(renderers);
Assertions.checkState(renderers.length > 0); Assertions.checkState(renderers.length > 0);
this.playWhenReady = false; this.playWhenReady = false;
this.playbackState = STATE_IDLE; this.playbackState = STATE_IDLE;
this.listeners = new CopyOnWriteArraySet<>(); this.listeners = new CopyOnWriteArraySet<>();
this.trackFormats = new Format[renderers.length][];
this.selectedTrackIndices = new int[renderers.length];
eventHandler = new Handler() { eventHandler = new Handler() {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
ExoPlayerImpl.this.handleEvent(msg); ExoPlayerImpl.this.handleEvent(msg);
} }
}; };
internalPlayer = new ExoPlayerImplInternal(renderers, minBufferMs, minRebufferMs, internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, minBufferMs, minRebufferMs,
playWhenReady, selectedTrackIndices, eventHandler); playWhenReady, eventHandler);
} }
@Override @Override
...@@ -95,34 +92,10 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -95,34 +92,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override @Override
public void prepare(SampleSource source) { public void prepare(SampleSource source) {
Arrays.fill(trackFormats, null);
internalPlayer.prepare(source); internalPlayer.prepare(source);
} }
@Override @Override
public int getTrackCount(int rendererIndex) {
return trackFormats[rendererIndex] != null ? trackFormats[rendererIndex].length : 0;
}
@Override
public Format getTrackFormat(int rendererIndex, int trackIndex) {
return trackFormats[rendererIndex][trackIndex];
}
@Override
public void setSelectedTrack(int rendererIndex, int trackIndex) {
if (selectedTrackIndices[rendererIndex] != trackIndex) {
selectedTrackIndices[rendererIndex] = trackIndex;
internalPlayer.setRendererSelectedTrack(rendererIndex, trackIndex);
}
}
@Override
public int getSelectedTrack(int rendererIndex) {
return selectedTrackIndices[rendererIndex];
}
@Override
public void setPlayWhenReady(boolean playWhenReady) { public void setPlayWhenReady(boolean playWhenReady) {
if (this.playWhenReady != playWhenReady) { if (this.playWhenReady != playWhenReady) {
this.playWhenReady = playWhenReady; this.playWhenReady = playWhenReady;
...@@ -196,14 +169,6 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -196,14 +169,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
// Not private so it can be called from an inner class without going through a thunk method. // Not private so it can be called from an inner class without going through a thunk method.
/* package */ void handleEvent(Message msg) { /* package */ void handleEvent(Message msg) {
switch (msg.what) { switch (msg.what) {
case ExoPlayerImplInternal.MSG_PREPARED: {
System.arraycopy(msg.obj, 0, trackFormats, 0, trackFormats.length);
playbackState = msg.arg1;
for (Listener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
break;
}
case ExoPlayerImplInternal.MSG_STATE_CHANGED: { case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
playbackState = msg.arg1; playbackState = msg.arg1;
for (Listener listener : listeners) { for (Listener listener : listeners) {
......
...@@ -306,9 +306,8 @@ public final class Format { ...@@ -306,9 +306,8 @@ public final class Format {
@Override @Override
public String toString() { public String toString() {
return "Format(" + id + ", " + containerMimeType + ", " + sampleMimeType + ", " + bitrate + ", " return "Format(" + id + ", " + containerMimeType + ", " + sampleMimeType + ", " + bitrate + ", "
+ maxInputSize + ", " + language + ", [" + width + ", " + height + ", " + frameRate + ", " + ", " + language + ", [" + width + ", " + height + ", " + frameRate + "]"
+ rotationDegrees + ", " + pixelWidthHeightRatio + "]" + ", [" + channelCount + ", " + ", [" + channelCount + ", " + sampleRate + "])";
+ sampleRate + "])";
} }
@Override @Override
......
...@@ -23,7 +23,7 @@ import java.util.Arrays; ...@@ -23,7 +23,7 @@ import java.util.Arrays;
public final class TrackGroupArray { public final class TrackGroupArray {
/** /**
* The number of groups in the list. Greater than or equal to zero. * The number of groups in the array. Greater than or equal to zero.
*/ */
public final int length; public final int length;
......
...@@ -69,6 +69,21 @@ public final class TrackSelection { ...@@ -69,6 +69,21 @@ public final class TrackSelection {
return tracks.clone(); return tracks.clone();
} }
/**
* Gets whether a given track index is included in the selection.
*
* @param trackIndex The track index.
* @return True if the index is included in the selection. False otherwise.
*/
public boolean containsTrack(int trackIndex) {
for (int i = 0; i < length; i++) {
if (tracks[i] == trackIndex) {
return true;
}
}
return false;
}
@Override @Override
public int hashCode() { public int hashCode() {
if (hashCode == 0) { if (hashCode == 0) {
......
/*
* 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;
import java.util.Arrays;
/**
* An array of {@link TrackSelection}s generated by a {@link TrackSelector}.
*/
public final class TrackSelectionArray {
/**
* The number of selections in the array. Greater than or equal to zero.
*/
public final int length;
private final TrackSelection[] trackSelections;
// Lazily initialized hashcode.
private int hashCode;
/**
* @param trackSelections The selections. Must not be null or contain null elements, but may be
* empty.
*/
public TrackSelectionArray(TrackSelection... trackSelections) {
this.trackSelections = trackSelections;
this.length = trackSelections.length;
}
/**
* Gets the selection at a given index.
*
* @param index The index of the selection.
* @return The selection.
*/
public TrackSelection get(int index) {
return trackSelections[index];
}
@Override
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 31 * result + Arrays.hashCode(trackSelections);
hashCode = result;
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TrackSelectionArray other = (TrackSelectionArray) obj;
return Arrays.equals(trackSelections, other.trackSelections);
}
}
/*
* 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;
import android.util.Pair;
/**
* Selects tracks to be consumed by available {@link TrackRenderer}s.
*/
public abstract class TrackSelector {
/**
* Notified when previous selections by a {@link TrackSelector} are no longer valid.
*/
/* package */ interface InvalidationListener {
/**
* Invoked by a {@link TrackSelector} when previous selections are no longer valid.
*/
void onTrackSelectionsInvalidated();
}
private InvalidationListener listener;
/* package */ void init(InvalidationListener listener) {
this.listener = listener;
}
/**
* Invalidates all previously generated track selections.
*/
protected final void invalidate() {
if (listener != null) {
listener.onTrackSelectionsInvalidated();
}
}
/**
* Generates a {@link TrackSelection} for each renderer.
* <P>
* The selections are returned in a {@link TrackSelectionArray}, together with an opaque object
* that the selector wishes to receive in an invocation of {@link #onSelectionActivated(Object)}
* should the selection be activated.
*
* @param renderers The renderers.
* @param trackGroups The available track groups.
* @return A {@link TrackSelectionArray} containing a {@link TrackSelection} for each renderer,
* together with an opaque object that will be passed to {@link #onSelectionActivated(Object)}
* if the selection is activated.
* @throws ExoPlaybackException If an error occurs selecting tracks.
*/
protected abstract Pair<TrackSelectionArray, Object> selectTracks(TrackRenderer[] renderers,
TrackGroupArray trackGroups) throws ExoPlaybackException;
/**
* Invoked when a selection previously generated by
* {@link #selectTracks(TrackRenderer[], TrackGroupArray)} is activated.
*
* @param selectionInfo The opaque object associated with the selection.
*/
protected abstract void onSelectionActivated(Object selectionInfo);
}
/*
* 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.util;
/**
* Utility class for managing a set of tags for which verbose logging should be enabled.
*/
public final class VerboseLogUtil {
private static volatile String[] enabledTags;
private static volatile boolean enableAllTags;
private VerboseLogUtil() {}
/**
* Sets the tags for which verbose logging should be enabled.
*
* @param tags The set of tags.
*/
public static void setEnabledTags(String... tags) {
enabledTags = tags;
enableAllTags = false;
}
/**
* Specifies whether or not all logging should be enabled.
*
* @param enable True if all logging should be enabled; false if only tags enabled by
* setEnabledTags should have logging enabled.
*/
public static void setEnableAllTags(boolean enable) {
enableAllTags = enable;
}
/**
* Checks whether verbose logging should be output for a given tag.
*
* @param tag The tag.
* @return Whether verbose logging should be output for the tag.
*/
public static boolean isTagEnabled(String tag) {
if (enableAllTags) {
return true;
}
// Take a local copy of the array to ensure thread safety.
String[] tags = enabledTags;
if (tags == null || tags.length == 0) {
return false;
}
for (int i = 0; i < tags.length; i++) {
if (tags[i].equals(tag)) {
return true;
}
}
return false;
}
/**
* Checks whether all logging is enabled;
*
* @return True if all logging is enabled; false otherwise.
*/
public static boolean areAllTagsEnabled() {
return enableAllTags;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment