mirror of
https://github.com/samsonjs/media.git
synced 2026-04-05 11:15:46 +00:00
[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
This commit is contained in:
parent
9c98c4bb10
commit
d3d63101d1
18 changed files with 1356 additions and 596 deletions
|
|
@ -15,13 +15,17 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.demo;
|
||||
|
||||
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||
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.demo.player.DemoPlayer;
|
||||
import com.google.android.exoplayer.util.VerboseLogUtil;
|
||||
|
||||
import android.media.MediaCodec.CryptoException;
|
||||
import android.os.SystemClock;
|
||||
|
|
@ -46,13 +50,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||
}
|
||||
|
||||
private long sessionStartTimeMs;
|
||||
private long[] loadStartTimeMs;
|
||||
private long[] availableRangeValuesUs;
|
||||
|
||||
public EventLogger() {
|
||||
loadStartTimeMs = new long[DemoPlayer.RENDERER_COUNT];
|
||||
}
|
||||
|
||||
public void startSession() {
|
||||
sessionStartTimeMs = SystemClock.elapsedRealtime();
|
||||
Log.d(TAG, "start [0]");
|
||||
|
|
@ -82,6 +81,54 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||
+ ", " + 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
|
||||
|
||||
@Override
|
||||
|
|
@ -98,21 +145,13 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||
@Override
|
||||
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
|
||||
long mediaStartTimeMs, long mediaEndTimeMs) {
|
||||
loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime();
|
||||
if (VerboseLogUtil.isTagEnabled(TAG)) {
|
||||
Log.v(TAG, "loadStart [" + getSessionTimeString() + ", " + sourceId + ", " + type
|
||||
+ ", " + mediaStartTimeMs + ", " + mediaEndTimeMs + "]");
|
||||
}
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
|
||||
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs) {
|
||||
if (VerboseLogUtil.isTagEnabled(TAG)) {
|
||||
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId];
|
||||
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " + downloadTime
|
||||
+ "]");
|
||||
}
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -187,7 +226,15 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||
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) {
|
||||
case ExoPlayer.STATE_BUFFERING:
|
||||
return "B";
|
||||
|
|
@ -204,12 +251,76 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||
}
|
||||
}
|
||||
|
||||
private String getSessionTimeString() {
|
||||
return getTimeString(SystemClock.elapsedRealtime() - sessionStartTimeMs);
|
||||
private static String getFormatSupportString(int formatSupport) {
|
||||
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) {
|
||||
return TIME_FORMAT.format((timeMs) / 1000f);
|
||||
private static String getAdaptiveSupportString(int trackCount, int adaptiveSupport) {
|
||||
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,19 +16,19 @@
|
|||
package com.google.android.exoplayer.demo;
|
||||
|
||||
import com.google.android.exoplayer.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
|
||||
import com.google.android.exoplayer.ExoPlaybackException;
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
|
||||
import com.google.android.exoplayer.demo.player.DashSourceBuilder;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
|
||||
import com.google.android.exoplayer.demo.player.ExtractorSourceBuilder;
|
||||
import com.google.android.exoplayer.demo.player.HlsSourceBuilder;
|
||||
import com.google.android.exoplayer.demo.player.SmoothStreamingSourceBuilder;
|
||||
import com.google.android.exoplayer.demo.ui.TrackSelectionHelper;
|
||||
import com.google.android.exoplayer.drm.UnsupportedDrmException;
|
||||
import com.google.android.exoplayer.metadata.GeobMetadata;
|
||||
import com.google.android.exoplayer.metadata.PrivMetadata;
|
||||
|
|
@ -37,9 +37,7 @@ import com.google.android.exoplayer.text.CaptionStyleCompat;
|
|||
import com.google.android.exoplayer.text.Cue;
|
||||
import com.google.android.exoplayer.text.SubtitleLayout;
|
||||
import com.google.android.exoplayer.util.DebugTextViewHelper;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
import com.google.android.exoplayer.util.VerboseLogUtil;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.annotation.TargetApi;
|
||||
|
|
@ -52,8 +50,6 @@ import android.os.Bundle;
|
|||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
|
@ -64,8 +60,6 @@ import android.view.View.OnTouchListener;
|
|||
import android.view.accessibility.CaptioningManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.PopupMenu.OnMenuItemClickListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
|
@ -73,15 +67,13 @@ import java.net.CookieHandler;
|
|||
import java.net.CookieManager;
|
||||
import java.net.CookiePolicy;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An activity that plays media using {@link DemoPlayer}.
|
||||
*/
|
||||
public class PlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
|
||||
DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener,
|
||||
AudioCapabilitiesReceiver.Listener {
|
||||
DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener {
|
||||
|
||||
// For use within demo app code.
|
||||
public static final String CONTENT_ID_EXTRA = "content_id";
|
||||
|
|
@ -92,8 +84,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
private static final String CONTENT_EXT_EXTRA = "type";
|
||||
|
||||
private static final String TAG = "PlayerActivity";
|
||||
private static final int MENU_GROUP_TRACKS = 1;
|
||||
private static final int ID_OFFSET = 2;
|
||||
|
||||
private static final CookieManager defaultCookieManager;
|
||||
static {
|
||||
|
|
@ -116,19 +106,18 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
private Button retryButton;
|
||||
|
||||
private DemoPlayer player;
|
||||
private DefaultTrackSelector trackSelector;
|
||||
private TrackSelectionHelper trackSelectionHelper;
|
||||
private DebugTextViewHelper debugViewHelper;
|
||||
private boolean playerNeedsPrepare;
|
||||
|
||||
private long playerPosition;
|
||||
private boolean enableBackgroundAudio;
|
||||
|
||||
private Uri contentUri;
|
||||
private int contentType;
|
||||
private String contentId;
|
||||
private String provider;
|
||||
|
||||
private AudioCapabilitiesReceiver audioCapabilitiesReceiver;
|
||||
|
||||
// Activity lifecycle
|
||||
|
||||
@Override
|
||||
|
|
@ -182,9 +171,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
if (currentHandler != defaultCookieManager) {
|
||||
CookieHandler.setDefault(defaultCookieManager);
|
||||
}
|
||||
|
||||
audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, this);
|
||||
audioCapabilitiesReceiver.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -204,31 +190,16 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
|
||||
provider = intent.getStringExtra(PROVIDER_EXTRA);
|
||||
configureSubtitleView();
|
||||
if (player == null) {
|
||||
if (!maybeRequestPermission()) {
|
||||
preparePlayer(true);
|
||||
}
|
||||
} else {
|
||||
player.setBackgrounded(false);
|
||||
if (!maybeRequestPermission()) {
|
||||
preparePlayer(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (!enableBackgroundAudio) {
|
||||
releasePlayer();
|
||||
} else {
|
||||
player.setBackgrounded(true);
|
||||
}
|
||||
shutterView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
audioCapabilitiesReceiver.unregister();
|
||||
releasePlayer();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
// OnClickListener methods
|
||||
|
|
@ -240,20 +211,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
}
|
||||
}
|
||||
|
||||
// AudioCapabilitiesReceiver.Listener methods
|
||||
|
||||
@Override
|
||||
public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
boolean backgrounded = player.getBackgrounded();
|
||||
boolean playWhenReady = player.getPlayWhenReady();
|
||||
releasePlayer();
|
||||
preparePlayer(playWhenReady);
|
||||
player.setBackgrounded(backgrounded);
|
||||
}
|
||||
|
||||
// Permission request listener method
|
||||
|
||||
@Override
|
||||
|
|
@ -321,6 +278,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
player.setCaptionListener(this);
|
||||
player.setMetadataListener(this);
|
||||
player.seekTo(playerPosition);
|
||||
trackSelector = player.getTrackSelector();
|
||||
trackSelectionHelper = new TrackSelectionHelper(trackSelector);
|
||||
playerNeedsPrepare = true;
|
||||
mediaController.setMediaPlayer(player.getPlayerControl());
|
||||
mediaController.setEnabled(true);
|
||||
|
|
@ -355,6 +314,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
|
||||
// DemoPlayer.Listener implementation
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(TrackInfo trackSet) {
|
||||
updateButtonVisibilities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(boolean playWhenReady, int playbackState) {
|
||||
if (playbackState == ExoPlayer.STATE_ENDED) {
|
||||
|
|
@ -440,142 +404,23 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
}
|
||||
|
||||
private boolean haveTracks(int type) {
|
||||
return player != null && player.getTrackCount(type) > 0;
|
||||
TrackInfo trackInfo = player == null ? null : player.getTrackInfo();
|
||||
return trackInfo != null && trackInfo.getTrackGroups(type).length != 0;
|
||||
}
|
||||
|
||||
public void showVideoPopup(View v) {
|
||||
PopupMenu popup = new PopupMenu(this, v);
|
||||
configurePopupWithTracks(popup, null, DemoPlayer.TYPE_VIDEO);
|
||||
popup.show();
|
||||
public void showVideoPopup(@SuppressWarnings("unused") View v) {
|
||||
trackSelectionHelper.showSelectionDialog(this, R.string.video, player.getTrackInfo(),
|
||||
DemoPlayer.TYPE_VIDEO);
|
||||
}
|
||||
|
||||
public void showAudioPopup(View v) {
|
||||
PopupMenu popup = new PopupMenu(this, v);
|
||||
Menu menu = popup.getMenu();
|
||||
menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.enable_background_audio);
|
||||
final MenuItem backgroundAudioItem = menu.findItem(0);
|
||||
backgroundAudioItem.setCheckable(true);
|
||||
backgroundAudioItem.setChecked(enableBackgroundAudio);
|
||||
OnMenuItemClickListener clickListener = new OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item == backgroundAudioItem) {
|
||||
enableBackgroundAudio = !item.isChecked();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
configurePopupWithTracks(popup, clickListener, DemoPlayer.TYPE_AUDIO);
|
||||
popup.show();
|
||||
public void showAudioPopup(@SuppressWarnings("unused") View v) {
|
||||
trackSelectionHelper.showSelectionDialog(this, R.string.audio, player.getTrackInfo(),
|
||||
DemoPlayer.TYPE_AUDIO);
|
||||
}
|
||||
|
||||
public void showTextPopup(View v) {
|
||||
PopupMenu popup = new PopupMenu(this, v);
|
||||
configurePopupWithTracks(popup, null, DemoPlayer.TYPE_TEXT);
|
||||
popup.show();
|
||||
}
|
||||
|
||||
public void showVerboseLogPopup(View v) {
|
||||
PopupMenu popup = new PopupMenu(this, v);
|
||||
Menu menu = popup.getMenu();
|
||||
menu.add(Menu.NONE, 0, Menu.NONE, R.string.logging_normal);
|
||||
menu.add(Menu.NONE, 1, Menu.NONE, R.string.logging_verbose);
|
||||
menu.setGroupCheckable(Menu.NONE, true, true);
|
||||
menu.findItem((VerboseLogUtil.areAllTagsEnabled()) ? 1 : 0).setChecked(true);
|
||||
popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == 0) {
|
||||
VerboseLogUtil.setEnableAllTags(false);
|
||||
} else {
|
||||
VerboseLogUtil.setEnableAllTags(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
popup.show();
|
||||
}
|
||||
|
||||
private void configurePopupWithTracks(PopupMenu popup,
|
||||
final OnMenuItemClickListener customActionClickListener,
|
||||
final int trackType) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
int trackCount = player.getTrackCount(trackType);
|
||||
if (trackCount == 0) {
|
||||
return;
|
||||
}
|
||||
popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
return (customActionClickListener != null
|
||||
&& customActionClickListener.onMenuItemClick(item))
|
||||
|| onTrackItemClick(item, trackType);
|
||||
}
|
||||
});
|
||||
Menu menu = popup.getMenu();
|
||||
// ID_OFFSET ensures we avoid clashing with Menu.NONE (which equals 0).
|
||||
menu.add(MENU_GROUP_TRACKS, DemoPlayer.TRACK_DISABLED + ID_OFFSET, Menu.NONE, R.string.off);
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
menu.add(MENU_GROUP_TRACKS, i + ID_OFFSET, Menu.NONE,
|
||||
buildTrackName(player.getTrackFormat(trackType, i)));
|
||||
}
|
||||
menu.setGroupCheckable(MENU_GROUP_TRACKS, true, true);
|
||||
menu.findItem(player.getSelectedTrack(trackType) + ID_OFFSET).setChecked(true);
|
||||
}
|
||||
|
||||
private static String buildTrackName(Format format) {
|
||||
String trackName;
|
||||
if (MimeTypes.isVideo(format.sampleMimeType)) {
|
||||
trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format),
|
||||
buildBitrateString(format)), buildTrackIdString(format));
|
||||
} else if (MimeTypes.isAudio(format.sampleMimeType)) {
|
||||
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
|
||||
buildAudioPropertyString(format)), buildBitrateString(format)),
|
||||
buildTrackIdString(format));
|
||||
} else {
|
||||
trackName = joinWithSeparator(joinWithSeparator(buildLanguageString(format),
|
||||
buildBitrateString(format)), buildTrackIdString(format));
|
||||
}
|
||||
return trackName.length() == 0 ? "unknown" : trackName;
|
||||
}
|
||||
|
||||
private static String buildResolutionString(Format format) {
|
||||
return format.width == Format.NO_VALUE || format.height == Format.NO_VALUE
|
||||
? "" : format.width + "x" + format.height;
|
||||
}
|
||||
|
||||
private static String buildAudioPropertyString(Format format) {
|
||||
return format.channelCount == Format.NO_VALUE || format.sampleRate == Format.NO_VALUE
|
||||
? "" : format.channelCount + "ch, " + format.sampleRate + "Hz";
|
||||
}
|
||||
|
||||
private static String buildLanguageString(Format format) {
|
||||
return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? ""
|
||||
: format.language;
|
||||
}
|
||||
|
||||
private static String buildBitrateString(Format format) {
|
||||
return format.bitrate == Format.NO_VALUE ? ""
|
||||
: String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f);
|
||||
}
|
||||
|
||||
private static String joinWithSeparator(String first, String second) {
|
||||
return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second);
|
||||
}
|
||||
|
||||
private static String buildTrackIdString(Format format) {
|
||||
return format.id == null ? "" : " (" + format.id + ")";
|
||||
}
|
||||
|
||||
private boolean onTrackItemClick(MenuItem item, int type) {
|
||||
if (player == null || item.getGroupId() != MENU_GROUP_TRACKS) {
|
||||
return false;
|
||||
}
|
||||
player.setSelectedTrack(type, item.getItemId() - ID_OFFSET);
|
||||
return true;
|
||||
public void showTextPopup(@SuppressWarnings("unused") View v) {
|
||||
trackSelectionHelper.showSelectionDialog(this, R.string.text, player.getTrackInfo(),
|
||||
DemoPlayer.TYPE_TEXT);
|
||||
}
|
||||
|
||||
private void toggleControlsVisibility() {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
package com.google.android.exoplayer.demo.player;
|
||||
|
||||
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.ExoPlayer;
|
||||
import com.google.android.exoplayer.Format;
|
||||
|
|
@ -51,7 +53,6 @@ import android.os.Handler;
|
|||
import android.view.Surface;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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,
|
||||
* SmoothStreaming and so on).
|
||||
*/
|
||||
public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener,
|
||||
HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
|
||||
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
|
||||
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer,
|
||||
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider {
|
||||
public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.EventListener,
|
||||
ChunkSampleSource.EventListener, HlsSampleSource.EventListener,
|
||||
DefaultBandwidthMeter.EventListener, MediaCodecVideoTrackRenderer.EventListener,
|
||||
MediaCodecAudioTrackRenderer.EventListener, StreamingDrmSessionManager.EventListener,
|
||||
DashChunkSource.EventListener, TextRenderer, MetadataRenderer<Map<String, Object>>,
|
||||
DebugTextViewHelper.Provider {
|
||||
|
||||
/**
|
||||
* Builds a source to play.
|
||||
|
|
@ -86,6 +88,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||
public interface Listener {
|
||||
void onStateChanged(boolean playWhenReady, int playbackState);
|
||||
void onError(Exception e);
|
||||
void onTracksChanged(TrackInfo trackInfo);
|
||||
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio);
|
||||
}
|
||||
|
|
@ -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_READY = ExoPlayer.STATE_READY;
|
||||
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 TYPE_VIDEO = 0;
|
||||
|
|
@ -156,6 +157,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||
public static final int TYPE_METADATA = 3;
|
||||
|
||||
private final ExoPlayer player;
|
||||
private final DefaultTrackSelector trackSelector;
|
||||
private final SourceBuilder sourceBuilder;
|
||||
private final BandwidthMeter bandwidthMeter;
|
||||
private final MediaCodecVideoTrackRenderer videoRenderer;
|
||||
|
|
@ -165,9 +167,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||
|
||||
private Surface surface;
|
||||
private Format videoFormat;
|
||||
private int videoTrackToRestore;
|
||||
|
||||
private boolean backgrounded;
|
||||
private TrackInfo trackInfo;
|
||||
|
||||
private CaptionListener captionListener;
|
||||
private Id3MetadataListener id3MetadataListener;
|
||||
|
|
@ -193,12 +193,14 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||
id3Renderer};
|
||||
|
||||
// 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);
|
||||
playerControl = new PlayerControl(player);
|
||||
}
|
||||
|
||||
// Set initial state, with the text renderer initially disabled.
|
||||
player.setSelectedTrack(TYPE_TEXT, TRACK_DISABLED);
|
||||
public DefaultTrackSelector getTrackSelector() {
|
||||
return trackSelector;
|
||||
}
|
||||
|
||||
public PlayerControl getPlayerControl() {
|
||||
|
|
@ -243,41 +245,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||
pushSurface(true);
|
||||
}
|
||||
|
||||
public int getTrackCount(int type) {
|
||||
return player.getTrackCount(type);
|
||||
}
|
||||
|
||||
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 TrackInfo getTrackInfo() {
|
||||
return trackInfo;
|
||||
}
|
||||
|
||||
public void prepare() {
|
||||
|
|
@ -351,6 +320,14 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||
}
|
||||
}
|
||||
|
||||
@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,
|
||||
float pixelWidthHeightRatio) {
|
||||
|
|
@ -451,14 +428,14 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* 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.demo.ui;
|
||||
|
||||
import com.google.android.exoplayer.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
|
||||
import com.google.android.exoplayer.Format;
|
||||
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.demo.R;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckedTextView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Helper class for displaying track selection dialogs.
|
||||
*/
|
||||
public class TrackSelectionHelper implements View.OnClickListener, DialogInterface.OnClickListener {
|
||||
|
||||
private final DefaultTrackSelector selector;
|
||||
|
||||
private CheckedTextView disableView;
|
||||
private CheckedTextView defaultView;
|
||||
private CheckedTextView[][] trackViews;
|
||||
|
||||
private TrackInfo trackInfo;
|
||||
private int rendererIndex;
|
||||
private TrackGroupArray trackGroups;
|
||||
private boolean[] trackGroupsAdaptive;
|
||||
|
||||
private boolean isDisabled;
|
||||
private TrackSelection override;
|
||||
|
||||
/**
|
||||
* @param selector The track selector.
|
||||
*/
|
||||
public TrackSelectionHelper(DefaultTrackSelector selector) {
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the selection dialog for a given renderer.
|
||||
*
|
||||
* @param activity The parent activity.
|
||||
* @param titleId The dialog's title.
|
||||
* @param trackInfo The current track information.
|
||||
* @param rendererIndex The index of the renderer.
|
||||
*/
|
||||
public void showSelectionDialog(Activity activity, int titleId, TrackInfo trackInfo,
|
||||
int rendererIndex) {
|
||||
this.trackInfo = trackInfo;
|
||||
this.rendererIndex = rendererIndex;
|
||||
|
||||
trackGroups = trackInfo.getTrackGroups(rendererIndex);
|
||||
trackGroupsAdaptive = new boolean[trackGroups.length];
|
||||
for (int i = 0; i < trackGroups.length; i++) {
|
||||
trackGroupsAdaptive[i] = trackInfo.getAdaptiveSupport(rendererIndex, i, false)
|
||||
!= TrackRenderer.ADAPTIVE_NOT_SUPPORTED;
|
||||
}
|
||||
isDisabled = selector.getRendererDisabled(rendererIndex);
|
||||
if (selector.hasSelectionOverride(rendererIndex, trackGroups)) {
|
||||
override = trackInfo.getTrackSelection(rendererIndex);
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(titleId)
|
||||
.setView(buildView(LayoutInflater.from(builder.getContext())))
|
||||
.setPositiveButton(android.R.string.ok, this)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private View buildView(LayoutInflater inflater) {
|
||||
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.track_selection_dialog, null);
|
||||
|
||||
// View for disabling the renderer.
|
||||
disableView = (CheckedTextView) inflater.inflate(
|
||||
android.R.layout.simple_list_item_single_choice, root, false);
|
||||
disableView.setText(R.string.selection_disabled);
|
||||
disableView.setOnClickListener(this);
|
||||
root.addView(disableView);
|
||||
|
||||
// View for clearing the override to allow the selector to use its default selection logic.
|
||||
defaultView = (CheckedTextView) inflater.inflate(
|
||||
android.R.layout.simple_list_item_single_choice, root, false);
|
||||
defaultView.setText(R.string.selection_default);
|
||||
defaultView.setOnClickListener(this);
|
||||
root.addView(inflater.inflate(R.layout.list_divider, root, false));
|
||||
root.addView(defaultView);
|
||||
|
||||
// Per-track views.
|
||||
boolean haveSupportedTracks = false;
|
||||
trackViews = new CheckedTextView[trackGroups.length][];
|
||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||
root.addView(inflater.inflate(R.layout.list_divider, root, false));
|
||||
TrackGroup group = trackGroups.get(groupIndex);
|
||||
trackViews[groupIndex] = new CheckedTextView[group.length];
|
||||
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
||||
int trackViewLayoutId = group.length < 2 || !trackGroupsAdaptive[groupIndex]
|
||||
? android.R.layout.simple_list_item_single_choice
|
||||
: android.R.layout.simple_list_item_multiple_choice;
|
||||
CheckedTextView trackView = (CheckedTextView) inflater.inflate(
|
||||
trackViewLayoutId, root, false);
|
||||
trackView.setText(buildTrackName(group.getFormat(trackIndex)));
|
||||
if (trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)
|
||||
== TrackRenderer.FORMAT_HANDLED) {
|
||||
haveSupportedTracks = true;
|
||||
trackView.setTag(Pair.create(groupIndex, trackIndex));
|
||||
trackView.setOnClickListener(this);
|
||||
} else {
|
||||
trackView.setEnabled(false);
|
||||
}
|
||||
trackViews[groupIndex][trackIndex] = trackView;
|
||||
root.addView(trackView);
|
||||
}
|
||||
}
|
||||
|
||||
if (!haveSupportedTracks) {
|
||||
// Indicate that the default selection will be nothing.
|
||||
defaultView.setText(R.string.selection_default_none);
|
||||
}
|
||||
|
||||
updateViews();
|
||||
return root;
|
||||
}
|
||||
|
||||
private void updateViews() {
|
||||
disableView.setChecked(isDisabled);
|
||||
defaultView.setChecked(!isDisabled && override == null);
|
||||
for (int i = 0; i < trackViews.length; i++) {
|
||||
for (int j = 0; j < trackViews[i].length; j++) {
|
||||
trackViews[i][j].setChecked(
|
||||
override != null && override.group == i && override.containsTrack(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DialogInterface.OnClickListener
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (isDisabled) {
|
||||
selector.setRendererDisabled(rendererIndex, true);
|
||||
return;
|
||||
}
|
||||
selector.setRendererDisabled(rendererIndex, false);
|
||||
if (override != null) {
|
||||
selector.setSelectionOverride(rendererIndex, trackGroups, override);
|
||||
} else {
|
||||
selector.clearSelectionOverrides(rendererIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// View.OnClickListener
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view == disableView) {
|
||||
isDisabled = true;
|
||||
override = null;
|
||||
} else if (view == defaultView) {
|
||||
isDisabled = false;
|
||||
override = null;
|
||||
} else {
|
||||
isDisabled = false;
|
||||
@SuppressWarnings("unchecked")
|
||||
Pair<Integer, Integer> tag = (Pair<Integer, Integer>) view.getTag();
|
||||
int groupIndex = tag.first;
|
||||
int trackIndex = tag.second;
|
||||
if (!trackGroupsAdaptive[groupIndex] || override == null) {
|
||||
override = new TrackSelection(groupIndex, trackIndex);
|
||||
} else {
|
||||
// The group being modified is adaptive and we already have a non-null override.
|
||||
boolean isEnabled = ((CheckedTextView) view).isChecked();
|
||||
if (isEnabled) {
|
||||
// Remove the track from the override.
|
||||
if (override.length == 1) {
|
||||
// The last track is being removed, so the override becomes empty.
|
||||
override = null;
|
||||
} else {
|
||||
int[] tracks = new int[override.length - 1];
|
||||
int trackCount = 0;
|
||||
for (int i = 0; i < override.length; i++) {
|
||||
if (override.getTrack(i) != trackIndex) {
|
||||
tracks[trackCount++] = override.getTrack(i);
|
||||
}
|
||||
}
|
||||
override = new TrackSelection(groupIndex, tracks);
|
||||
}
|
||||
} else {
|
||||
// Add the track to the override.
|
||||
int[] tracks = Arrays.copyOf(override.getTracks(), override.length + 1);
|
||||
tracks[tracks.length - 1] = trackIndex;
|
||||
override = new TrackSelection(groupIndex, tracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update the views with the new state.
|
||||
updateViews();
|
||||
}
|
||||
|
||||
// Track name construction.
|
||||
|
||||
private static String buildTrackName(Format format) {
|
||||
String trackName;
|
||||
if (MimeTypes.isVideo(format.sampleMimeType)) {
|
||||
trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format),
|
||||
buildBitrateString(format)), buildTrackIdString(format));
|
||||
} else if (MimeTypes.isAudio(format.sampleMimeType)) {
|
||||
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
|
||||
buildAudioPropertyString(format)), buildBitrateString(format)),
|
||||
buildTrackIdString(format));
|
||||
} else {
|
||||
trackName = joinWithSeparator(joinWithSeparator(buildLanguageString(format),
|
||||
buildBitrateString(format)), buildTrackIdString(format));
|
||||
}
|
||||
return trackName.length() == 0 ? "unknown" : trackName;
|
||||
}
|
||||
|
||||
private static String buildResolutionString(Format format) {
|
||||
return format.width == Format.NO_VALUE || format.height == Format.NO_VALUE
|
||||
? "" : format.width + "x" + format.height;
|
||||
}
|
||||
|
||||
private static String buildAudioPropertyString(Format format) {
|
||||
return format.channelCount == Format.NO_VALUE || format.sampleRate == Format.NO_VALUE
|
||||
? "" : format.channelCount + "ch, " + format.sampleRate + "Hz";
|
||||
}
|
||||
|
||||
private static String buildLanguageString(Format format) {
|
||||
return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? ""
|
||||
: format.language;
|
||||
}
|
||||
|
||||
private static String buildBitrateString(Format format) {
|
||||
return format.bitrate == Format.NO_VALUE ? ""
|
||||
: String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f);
|
||||
}
|
||||
|
||||
private static String joinWithSeparator(String first, String second) {
|
||||
return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second);
|
||||
}
|
||||
|
||||
private static String buildTrackIdString(Format format) {
|
||||
return format.id == null ? "" : " (" + format.id + ")";
|
||||
}
|
||||
|
||||
}
|
||||
19
demo/src/main/res/layout/list_divider.xml
Normal file
19
demo/src/main/res/layout/list_divider.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?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 @@
|
|||
android:visibility="gone"
|
||||
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"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
19
demo/src/main/res/layout/track_selection_dialog.xml
Normal file
19
demo/src/main/res/layout/track_selection_dialog.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?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 @@
|
|||
<!-- The user visible name of the application. [CHAR LIMIT=20] -->
|
||||
<string name="application_name">ExoPlayer Demo</string>
|
||||
|
||||
<string name="enable_background_audio">Play in background</string>
|
||||
|
||||
<string name="video">Video</string>
|
||||
|
||||
<string name="audio">Audio</string>
|
||||
|
||||
<string name="text">Text</string>
|
||||
|
||||
<string name="logging">Logging</string>
|
||||
|
||||
<string name="logging_normal">Normal</string>
|
||||
|
||||
<string name="logging_verbose">Verbose</string>
|
||||
|
||||
<string name="retry">Retry</string>
|
||||
|
||||
<string name="off">[off]</string>
|
||||
<string name="selection_disabled">Disabled</string>
|
||||
|
||||
<string name="selection_default">Default</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>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,558 @@
|
|||
/*
|
||||
* 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 com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link TrackSelector} suitable for a wide range of use cases.
|
||||
*/
|
||||
public class DefaultTrackSelector extends TrackSelector {
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be notified of {@link DefaultTrackSelector} events.
|
||||
*/
|
||||
public interface EventListener {
|
||||
|
||||
/**
|
||||
* Invoked when the track information has changed.
|
||||
*
|
||||
* @param trackInfo Contains the new track and track selection information.
|
||||
*/
|
||||
void onTracksChanged(TrackInfo trackInfo);
|
||||
|
||||
}
|
||||
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
private final SparseArray<Map<TrackGroupArray, TrackSelection>> trackSelectionOverrides;
|
||||
private final SparseBooleanArray rendererDisabledFlags;
|
||||
|
||||
private TrackInfo activeTrackInfo;
|
||||
|
||||
/**
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
*/
|
||||
public DefaultTrackSelector(Handler eventHandler, EventListener eventListener) {
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
trackSelectionOverrides = new SparseArray<>();
|
||||
rendererDisabledFlags = new SparseBooleanArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about the current tracks and track selection for each renderer.
|
||||
*
|
||||
* @return Contains the current tracks and track selection information.
|
||||
*/
|
||||
public TrackInfo getTrackInfo() {
|
||||
return activeTrackInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the renderer at the specified index is disabled.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @param disabled True if the renderer is disabled. False otherwise.
|
||||
*/
|
||||
public void setRendererDisabled(int rendererIndex, boolean disabled) {
|
||||
if (rendererDisabledFlags.get(rendererIndex) == disabled) {
|
||||
// The disabled flag is unchanged.
|
||||
return;
|
||||
}
|
||||
rendererDisabledFlags.put(rendererIndex, disabled);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the renderer is disabled.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @return True if the renderer is disabled. False otherwise.
|
||||
*/
|
||||
public boolean getRendererDisabled(int rendererIndex) {
|
||||
return rendererDisabledFlags.get(rendererIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the track selection for the renderer at a specified index.
|
||||
* <p>
|
||||
* When the {@link TrackGroupArray} available to the renderer at the specified index matches the
|
||||
* one provided, the override is applied. When the {@link TrackGroupArray} does not match, the
|
||||
* override has no effect. The override replaces any previous override for the renderer and the
|
||||
* provided {@link TrackGroupArray}.
|
||||
* <p>
|
||||
* Passing a {@code null} override will explicitly disable the renderer. To remove overrides use
|
||||
* {@link #clearSelectionOverride(int, TrackGroupArray)}, {@link #clearSelectionOverrides(int)}
|
||||
* or {@link #clearSelectionOverrides()}.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @param groups The {@link TrackGroupArray} for which the override should be applied.
|
||||
* @param override The overriding {@link TrackSelection}.
|
||||
*/
|
||||
// TODO - Don't allow overrides that select unsupported tracks, unless some flag has been
|
||||
// explicitly set by the user to indicate that they want this.
|
||||
public void setSelectionOverride(int rendererIndex, TrackGroupArray groups,
|
||||
TrackSelection override) {
|
||||
Map<TrackGroupArray, TrackSelection> overrides = trackSelectionOverrides.get(rendererIndex);
|
||||
if (overrides == null) {
|
||||
overrides = new HashMap<>();
|
||||
trackSelectionOverrides.put(rendererIndex, overrides);
|
||||
}
|
||||
if (overrides.containsKey(groups) && Util.areEqual(overrides.get(groups), override)) {
|
||||
// The override is unchanged.
|
||||
return;
|
||||
}
|
||||
overrides.put(groups, override);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there is an override for the specified renderer and {@link TrackGroupArray}.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @param groups The {@link TrackGroupArray}.
|
||||
* @return True if there is an override. False otherwise.
|
||||
*/
|
||||
public boolean hasSelectionOverride(int rendererIndex, TrackGroupArray groups) {
|
||||
Map<TrackGroupArray, TrackSelection> overrides = trackSelectionOverrides.get(rendererIndex);
|
||||
return overrides != null && overrides.containsKey(groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a track selection override for the specified renderer and {@link TrackGroupArray}.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @param groups The {@link TrackGroupArray} for which the override should be cleared.
|
||||
*/
|
||||
public void clearSelectionOverride(int rendererIndex, TrackGroupArray groups) {
|
||||
Map<TrackGroupArray, TrackSelection> overrides = trackSelectionOverrides.get(rendererIndex);
|
||||
if (overrides == null || !overrides.containsKey(groups)) {
|
||||
// Nothing to clear.
|
||||
return;
|
||||
}
|
||||
overrides.remove(groups);
|
||||
if (overrides.isEmpty()) {
|
||||
trackSelectionOverrides.remove(rendererIndex);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all track selection override for the specified renderer.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
*/
|
||||
public void clearSelectionOverrides(int rendererIndex) {
|
||||
Map<TrackGroupArray, TrackSelection> overrides = trackSelectionOverrides.get(rendererIndex);
|
||||
if (overrides == null || overrides.isEmpty()) {
|
||||
// Nothing to clear.
|
||||
return;
|
||||
}
|
||||
trackSelectionOverrides.remove(rendererIndex);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all track selection overrides.
|
||||
*/
|
||||
public void clearSelectionOverrides() {
|
||||
if (trackSelectionOverrides.size() == 0) {
|
||||
// Nothing to clear.
|
||||
return;
|
||||
}
|
||||
trackSelectionOverrides.clear();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// TrackSelector implementation.
|
||||
|
||||
@Override
|
||||
protected void onSelectionActivated(Object selectionInfo) {
|
||||
activeTrackInfo = (TrackInfo) selectionInfo;
|
||||
notifyTrackInfoChanged(activeTrackInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pair<TrackSelectionArray, Object> selectTracks(TrackRenderer[] renderers,
|
||||
TrackGroupArray trackGroups) throws ExoPlaybackException {
|
||||
// Structures into which data will be written during the selection. The extra item at the end
|
||||
// of each array is to store data associated with track groups that cannot be associated with
|
||||
// any renderer.
|
||||
int[] rendererTrackGroupCounts = new int[renderers.length + 1];
|
||||
TrackGroup[][] rendererTrackGroups = new TrackGroup[renderers.length + 1][];
|
||||
int[][][] rendererFormatSupports = new int[renderers.length + 1][][];
|
||||
for (int i = 0; i < rendererTrackGroups.length; i++) {
|
||||
rendererTrackGroups[i] = new TrackGroup[trackGroups.length];
|
||||
rendererFormatSupports[i] = new int[trackGroups.length][];
|
||||
}
|
||||
|
||||
// Determine the extent to which each renderer supports mixed mimeType adaptation.
|
||||
int[] mixedMimeTypeAdaptationSupport = getMixedMimeTypeAdaptationSupport(renderers);
|
||||
|
||||
// Associate each track group to a preferred renderer, and evaluate the support that the
|
||||
// renderer provides for each track in the group.
|
||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||
TrackGroup group = trackGroups.get(groupIndex);
|
||||
// Associate the group to a preferred renderer.
|
||||
int rendererIndex = findRenderer(renderers, group);
|
||||
// Evaluate the support that the renderer provides for each track in the group.
|
||||
int[] rendererFormatSupport = rendererIndex == renderers.length ? new int[group.length]
|
||||
: getFormatSupport(renderers[rendererIndex], group);
|
||||
// Stash the results.
|
||||
int rendererTrackGroupCount = rendererTrackGroupCounts[rendererIndex];
|
||||
rendererTrackGroups[rendererIndex][rendererTrackGroupCount] = group;
|
||||
rendererFormatSupports[rendererIndex][rendererTrackGroupCount] = rendererFormatSupport;
|
||||
rendererTrackGroupCounts[rendererIndex]++;
|
||||
}
|
||||
|
||||
// Create a track group array for each renderer, and trim each rendererFormatSupports entry.
|
||||
TrackGroupArray[] rendererTrackGroupArrays = new TrackGroupArray[renderers.length];
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
int rendererTrackGroupCount = rendererTrackGroupCounts[i];
|
||||
rendererTrackGroupArrays[i] = new TrackGroupArray(
|
||||
Arrays.copyOf(rendererTrackGroups[i], rendererTrackGroupCount));
|
||||
rendererFormatSupports[i] = Arrays.copyOf(rendererFormatSupports[i], rendererTrackGroupCount);
|
||||
}
|
||||
|
||||
// Create a track group array for track groups not associated with a renderer.
|
||||
int unassociatedTrackGroupCount = rendererTrackGroupCounts[renderers.length];
|
||||
TrackGroupArray unassociatedTrackGroupArray = new TrackGroupArray(
|
||||
Arrays.copyOf(rendererTrackGroups[renderers.length], unassociatedTrackGroupCount));
|
||||
|
||||
// Make a track selection for each renderer.
|
||||
TrackSelection[] rendererTrackSelections = new TrackSelection[renderers.length];
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
rendererTrackSelections[i] = rendererDisabledFlags.get(i) ? null
|
||||
: selectTracksForRenderer(rendererTrackGroupArrays[i], rendererFormatSupports[i],
|
||||
trackSelectionOverrides.get(i));
|
||||
}
|
||||
|
||||
// The track selections above index into the track group arrays associated to each renderer,
|
||||
// and not to the original track groups passed to this method. Build the corresponding track
|
||||
// selections into the original track groups to pass back as the final selection.
|
||||
TrackSelection[] trackSelections = new TrackSelection[renderers.length];
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
TrackSelection selection = rendererTrackSelections[i];
|
||||
if (selection != null) {
|
||||
TrackGroup group = rendererTrackGroupArrays[i].get(selection.group);
|
||||
int originalGroupIndex = findGroupInGroupArray(trackGroups, group);
|
||||
trackSelections[i] = new TrackSelection(originalGroupIndex, selection.getTracks());
|
||||
}
|
||||
}
|
||||
|
||||
// Package up the track information and selections.
|
||||
TrackSelectionArray trackSelectionArray = new TrackSelectionArray(trackSelections);
|
||||
TrackInfo trackInfo = new TrackInfo(rendererTrackGroupArrays, rendererTrackSelections,
|
||||
mixedMimeTypeAdaptationSupport, rendererFormatSupports, unassociatedTrackGroupArray);
|
||||
return Pair.<TrackSelectionArray, Object>create(trackSelectionArray, trackInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the renderer to which the provided {@link TrackGroup} should be associated.
|
||||
* <p>
|
||||
* A {@link TrackGroup} is associated to a renderer that reports
|
||||
* {@link TrackRenderer#FORMAT_HANDLED} support for one or more of the tracks in the group, or
|
||||
* {@link TrackRenderer#FORMAT_EXCEEDS_CAPABILITIES} if no such renderer exists, or
|
||||
* {@link TrackRenderer#FORMAT_UNSUPPORTED_SUBTYPE} if again no such renderer exists. In the case
|
||||
* that two or more renderers report the same level of support, the renderer with the lowest index
|
||||
* is associated.
|
||||
* <p>
|
||||
* If all renderers report {@link TrackRenderer#FORMAT_UNSUPPORTED_TYPE} for all of the tracks in
|
||||
* the group, then {@code renderers.length} is returned to indicate that no association was made.
|
||||
*
|
||||
* @param renderers The renderers from which to select.
|
||||
* @param group The {@link TrackGroup} whose associated renderer is to be found.
|
||||
* @return The index of the associated renderer, or {@code renderers.length} if no association
|
||||
* was made.
|
||||
* @throws ExoPlaybackException If an error occurs finding a renderer.
|
||||
*/
|
||||
private static int findRenderer(TrackRenderer[] renderers, TrackGroup group)
|
||||
throws ExoPlaybackException {
|
||||
int bestRendererIndex = renderers.length;
|
||||
int bestSupportLevel = TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
||||
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
|
||||
TrackRenderer renderer = renderers[rendererIndex];
|
||||
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
||||
int trackSupportLevel = renderer.supportsFormat(group.getFormat(trackIndex));
|
||||
if (trackSupportLevel > bestSupportLevel) {
|
||||
bestRendererIndex = rendererIndex;
|
||||
bestSupportLevel = trackSupportLevel;
|
||||
if (bestSupportLevel == TrackRenderer.FORMAT_HANDLED) {
|
||||
// We can't do better.
|
||||
return bestRendererIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestRendererIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link TrackRenderer#supportsFormat(Format)} for each track in the specified
|
||||
* {@link TrackGroup}, returning the results in an array.
|
||||
*
|
||||
* @param renderer The renderer to evaluate.
|
||||
* @param group The {@link TrackGroup} to evaluate.
|
||||
* @return An array containing the result of calling {@link TrackRenderer#supportsFormat(Format)}
|
||||
* on the renderer for each track in the group.
|
||||
* @throws ExoPlaybackException If an error occurs determining the format support.
|
||||
*/
|
||||
private static int[] getFormatSupport(TrackRenderer renderer, TrackGroup group)
|
||||
throws ExoPlaybackException {
|
||||
int[] formatSupport = new int[group.length];
|
||||
for (int i = 0; i < group.length; i++) {
|
||||
formatSupport[i] = renderer.supportsFormat(group.getFormat(i));
|
||||
}
|
||||
return formatSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link TrackRenderer#supportsMixedMimeTypeAdaptation()} for each renderer, returning
|
||||
* the results in an array.
|
||||
*
|
||||
* @param renderers The renderers to evaluate.
|
||||
* @return An array containing the result of calling
|
||||
* {@link TrackRenderer#supportsMixedMimeTypeAdaptation()} on each renderer.
|
||||
* @throws ExoPlaybackException If an error occurs determining the adaptation support.
|
||||
*/
|
||||
private static int[] getMixedMimeTypeAdaptationSupport(TrackRenderer[] renderers)
|
||||
throws ExoPlaybackException {
|
||||
int[] mixedMimeTypeAdaptationSupport = new int[renderers.length];
|
||||
for (int i = 0; i < mixedMimeTypeAdaptationSupport.length; i++) {
|
||||
mixedMimeTypeAdaptationSupport[i] = renderers[i].supportsMixedMimeTypeAdaptation();
|
||||
}
|
||||
return mixedMimeTypeAdaptationSupport;
|
||||
}
|
||||
|
||||
private static TrackSelection selectTracksForRenderer(TrackGroupArray trackGroups,
|
||||
int[][] formatSupport, Map<TrackGroupArray, TrackSelection> overrides) {
|
||||
if (overrides != null && overrides.containsKey(trackGroups)) {
|
||||
return overrides.get(trackGroups);
|
||||
}
|
||||
|
||||
// TODO[REFACTOR]: Implement real default selection logic here.
|
||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||
TrackGroup trackGroup = trackGroups.get(groupIndex);
|
||||
int[] trackFormatSupport = formatSupport[groupIndex];
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if ((trackFormatSupport[trackIndex] & TrackRenderer.FORMAT_SUPPORT_MASK)
|
||||
== TrackRenderer.FORMAT_HANDLED) {
|
||||
return new TrackSelection(groupIndex, trackIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the specified group in a group array, using referential equality.
|
||||
*
|
||||
* @param groupArray The group array to search.
|
||||
* @param group The group to search for.
|
||||
* @return The index of the group in the group array.
|
||||
* @throws IllegalStateException If the group was not found.
|
||||
*/
|
||||
private static int findGroupInGroupArray(TrackGroupArray groupArray, TrackGroup group) {
|
||||
for (int i = 0; i < groupArray.length; i++) {
|
||||
if (groupArray.get(i) == group) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
private void notifyTrackInfoChanged(final TrackInfo trackInfo) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onTracksChanged(trackInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides track information for each {@link TrackRenderer}.
|
||||
*/
|
||||
public static final class TrackInfo {
|
||||
|
||||
/**
|
||||
* The number of renderers.
|
||||
*/
|
||||
public final int rendererCount;
|
||||
|
||||
private final TrackGroupArray[] trackGroups;
|
||||
private final TrackSelection[] trackSelections;
|
||||
private final int[] mixedMimeTypeAdaptiveSupport;
|
||||
private final int[][][] formatSupport;
|
||||
private final TrackGroupArray unassociatedTrackGroups;
|
||||
|
||||
/**
|
||||
* @param trackGroups The {@link TrackGroupArray}s for each renderer.
|
||||
* @param trackSelections The current {@link TrackSelection}s for each renderer.
|
||||
* @param mixedMimeTypeAdaptiveSupport The result of
|
||||
* {@link TrackRenderer#supportsMixedMimeTypeAdaptation()} for each renderer.
|
||||
* @param formatSupport The result of {@link TrackRenderer#supportsFormat(Format)} for each
|
||||
* track, indexed by renderer index, group index and track index (in that order).
|
||||
* @param unassociatedTrackGroups Contains {@link TrackGroup}s not associated with any renderer.
|
||||
*/
|
||||
/* package */ TrackInfo(TrackGroupArray[] trackGroups, TrackSelection[] trackSelections,
|
||||
int[] mixedMimeTypeAdaptiveSupport, int[][][] formatSupport,
|
||||
TrackGroupArray unassociatedTrackGroups) {
|
||||
this.trackGroups = trackGroups;
|
||||
this.trackSelections = trackSelections;
|
||||
this.formatSupport = formatSupport;
|
||||
this.mixedMimeTypeAdaptiveSupport = mixedMimeTypeAdaptiveSupport;
|
||||
this.unassociatedTrackGroups = unassociatedTrackGroups;
|
||||
this.rendererCount = trackGroups.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the array of {@link TrackGroup}s that belong to the renderer at a specified index.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @return The corresponding {@link TrackGroup}s.
|
||||
*/
|
||||
public TrackGroupArray getTrackGroups(int rendererIndex) {
|
||||
return trackGroups[rendererIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current {@link TrackSelection} for the renderer at a specified index.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @return The corresponding {@link TrackSelection}, or null if the renderer is disabled.
|
||||
*/
|
||||
public TrackSelection getTrackSelection(int rendererIndex) {
|
||||
return trackSelections[rendererIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extent to which the format of an individual track is supported by the renderer.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @param groupIndex The index of the group to which the track belongs.
|
||||
* @param trackIndex The index of the track within the group.
|
||||
* @return One of {@link TrackRenderer#FORMAT_HANDLED},
|
||||
* {@link TrackRenderer#FORMAT_EXCEEDS_CAPABILITIES},
|
||||
* {@link TrackRenderer#FORMAT_UNSUPPORTED_SUBTYPE} and
|
||||
* {@link TrackRenderer#FORMAT_UNSUPPORTED_TYPE}.
|
||||
*/
|
||||
public int getTrackFormatSupport(int rendererIndex, int groupIndex, int trackIndex) {
|
||||
return formatSupport[rendererIndex][groupIndex][trackIndex]
|
||||
& TrackRenderer.FORMAT_SUPPORT_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extent to which the renderer supports adaptation between supported tracks in a
|
||||
* specified {@link TrackGroup}.
|
||||
* <p>
|
||||
* Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns
|
||||
* {@link TrackRenderer#FORMAT_HANDLED} are always considered.
|
||||
* Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns
|
||||
* {@link TrackRenderer#FORMAT_UNSUPPORTED_TYPE} or
|
||||
* {@link TrackRenderer#FORMAT_UNSUPPORTED_SUBTYPE} are never considered.
|
||||
* Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns
|
||||
* {@link TrackRenderer#FORMAT_EXCEEDS_CAPABILITIES} are considered only if
|
||||
* {@code includeCapabilitiesExceededTracks} is set to {@code true}.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @param groupIndex The index of the group.
|
||||
* @param includeCapabilitiesExceededTracks True if formats that exceed the capabilities of the
|
||||
* renderer should be included when determining support. False otherwise.
|
||||
* @return One of {@link TrackRenderer#ADAPTIVE_SEAMLESS},
|
||||
* {@link TrackRenderer#ADAPTIVE_NOT_SEAMLESS} and
|
||||
* {@link TrackRenderer#ADAPTIVE_NOT_SUPPORTED}.
|
||||
*/
|
||||
public int getAdaptiveSupport(int rendererIndex, int groupIndex,
|
||||
boolean includeCapabilitiesExceededTracks) {
|
||||
int trackCount = trackGroups[rendererIndex].get(groupIndex).length;
|
||||
// Iterate over the tracks in the group, recording the indices of those to consider.
|
||||
int[] trackIndices = new int[trackCount];
|
||||
int trackIndexCount = 0;
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
int fixedSupport = getTrackFormatSupport(rendererIndex, groupIndex, i);
|
||||
if (fixedSupport == TrackRenderer.FORMAT_HANDLED || (includeCapabilitiesExceededTracks
|
||||
&& fixedSupport == TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES)) {
|
||||
trackIndices[trackIndexCount++] = i;
|
||||
}
|
||||
}
|
||||
trackIndices = Arrays.copyOf(trackIndices, trackIndexCount);
|
||||
return getAdaptiveSupport(rendererIndex, groupIndex, trackIndices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extent to which the renderer supports adaptation between specified tracks within a
|
||||
* {@link TrackGroup}.
|
||||
*
|
||||
* @param rendererIndex The renderer index.
|
||||
* @param groupIndex The index of the group.
|
||||
* @return One of {@link TrackRenderer#ADAPTIVE_SEAMLESS},
|
||||
* {@link TrackRenderer#ADAPTIVE_NOT_SEAMLESS} and
|
||||
* {@link TrackRenderer#ADAPTIVE_NOT_SUPPORTED}.
|
||||
*/
|
||||
public int getAdaptiveSupport(int rendererIndex, int groupIndex, int[] trackIndices) {
|
||||
TrackGroup trackGroup = trackGroups[rendererIndex].get(groupIndex);
|
||||
if (!trackGroup.adaptive) {
|
||||
return TrackRenderer.ADAPTIVE_NOT_SUPPORTED;
|
||||
}
|
||||
int handledTrackCount = 0;
|
||||
int adaptiveSupport = TrackRenderer.ADAPTIVE_SEAMLESS;
|
||||
boolean multipleMimeTypes = false;
|
||||
String firstSampleMimeType = null;
|
||||
for (int i = 0; i < trackIndices.length; i++) {
|
||||
int trackIndex = trackIndices[i];
|
||||
String sampleMimeType = trackGroups[rendererIndex].get(groupIndex).getFormat(trackIndex)
|
||||
.sampleMimeType;
|
||||
if (handledTrackCount++ == 0) {
|
||||
firstSampleMimeType = sampleMimeType;
|
||||
} else {
|
||||
multipleMimeTypes |= !Util.areEqual(firstSampleMimeType, sampleMimeType);
|
||||
}
|
||||
adaptiveSupport = Math.min(adaptiveSupport,
|
||||
formatSupport[rendererIndex][groupIndex][i] & TrackRenderer.ADAPTIVE_SUPPORT_MASK);
|
||||
}
|
||||
return multipleMimeTypes
|
||||
? Math.min(adaptiveSupport, mixedMimeTypeAdaptiveSupport[rendererIndex])
|
||||
: adaptiveSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link TrackGroup}s not associated with any {@link TrackRenderer}.
|
||||
*
|
||||
* @return The {@link TrackGroup}s not associated with any {@link TrackRenderer}.
|
||||
*/
|
||||
public TrackGroupArray getUnassociatedTrackGroups() {
|
||||
return unassociatedTrackGroups;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -73,12 +73,13 @@ import android.os.Looper;
|
|||
* <a name="State"></a>
|
||||
* <h3>Player state</h3>
|
||||
*
|
||||
* <p>The components of an {@link ExoPlayer}'s state can be divided into two distinct groups. State
|
||||
* accessed by {@link #getSelectedTrack(int)} and {@link #getPlayWhenReady()} is only ever
|
||||
* changed by invoking the player's methods, and are never changed as a result of operations that
|
||||
* have been performed asynchronously by the playback thread. In contrast, the playback state
|
||||
* accessed by {@link #getPlaybackState()} is only ever changed as a result of operations
|
||||
* completing on the playback thread, as illustrated below.</p>
|
||||
* <p>The components of an {@link ExoPlayer}'s state can be divided into two distinct groups. The
|
||||
* state accessed by calling {@link #getPlayWhenReady()} is only ever changed by invoking
|
||||
* {@link #setPlayWhenReady(boolean)}, and is never changed as a result of operations that have been
|
||||
* performed asynchronously by the playback thread. In contrast, the playback state accessed by
|
||||
* calling {@link #getPlaybackState()} is only ever changed as a result of operations completing on
|
||||
* the playback thread, as illustrated below.</p>
|
||||
*
|
||||
* <p align="center"><img src="../../../../../images/exoplayer_state.png"
|
||||
* alt="ExoPlayer state"
|
||||
* border="0"/></p>
|
||||
|
|
@ -118,15 +119,16 @@ public interface ExoPlayer {
|
|||
* 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 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
|
||||
* 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
|
||||
* 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).
|
||||
*/
|
||||
public static ExoPlayer newInstance(TrackRenderer[] renderers, int minBufferMs,
|
||||
int minRebufferMs) {
|
||||
return new ExoPlayerImpl(renderers, minBufferMs, minRebufferMs);
|
||||
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector,
|
||||
int minBufferMs, int minRebufferMs) {
|
||||
return new ExoPlayerImpl(renderers, trackSelector, minBufferMs, minRebufferMs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -135,9 +137,11 @@ public interface ExoPlayer {
|
|||
* 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 trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||
*/
|
||||
public static ExoPlayer newInstance(TrackRenderer... renderers) {
|
||||
return new ExoPlayerImpl(renderers, DEFAULT_MIN_BUFFER_MS, DEFAULT_MIN_REBUFFER_MS);
|
||||
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector) {
|
||||
return new ExoPlayerImpl(renderers, trackSelector, DEFAULT_MIN_BUFFER_MS,
|
||||
DEFAULT_MIN_REBUFFER_MS);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -219,17 +223,6 @@ public interface ExoPlayer {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
|
@ -271,41 +264,6 @@ public interface ExoPlayer {
|
|||
*/
|
||||
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}.
|
||||
* If the player is already in this state, then this method can be used to pause and resume
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import android.os.Looper;
|
|||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
|
|
@ -36,8 +35,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
private final Handler eventHandler;
|
||||
private final ExoPlayerImplInternal internalPlayer;
|
||||
private final CopyOnWriteArraySet<Listener> listeners;
|
||||
private final Format[][] trackFormats;
|
||||
private final int[] selectedTrackIndices;
|
||||
|
||||
private boolean playWhenReady;
|
||||
private int playbackState;
|
||||
|
|
@ -46,7 +43,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
/**
|
||||
* 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
|
||||
* 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
|
||||
|
|
@ -54,23 +52,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
* not due to a user action such as starting playback or seeking).
|
||||
*/
|
||||
@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);
|
||||
Assertions.checkNotNull(renderers);
|
||||
Assertions.checkState(renderers.length > 0);
|
||||
this.playWhenReady = false;
|
||||
this.playbackState = STATE_IDLE;
|
||||
this.listeners = new CopyOnWriteArraySet<>();
|
||||
this.trackFormats = new Format[renderers.length][];
|
||||
this.selectedTrackIndices = new int[renderers.length];
|
||||
eventHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
ExoPlayerImpl.this.handleEvent(msg);
|
||||
}
|
||||
};
|
||||
internalPlayer = new ExoPlayerImplInternal(renderers, minBufferMs, minRebufferMs,
|
||||
playWhenReady, selectedTrackIndices, eventHandler);
|
||||
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, minBufferMs, minRebufferMs,
|
||||
playWhenReady, eventHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -95,33 +92,9 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
|
||||
@Override
|
||||
public void prepare(SampleSource source) {
|
||||
Arrays.fill(trackFormats, null);
|
||||
internalPlayer.prepare(source);
|
||||
}
|
||||
|
||||
@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) {
|
||||
if (this.playWhenReady != playWhenReady) {
|
||||
|
|
@ -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.
|
||||
/* package */ void handleEvent(Message msg) {
|
||||
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: {
|
||||
playbackState = msg.arg1;
|
||||
for (Listener listener : listeners) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlayer.ExoPlayerComponent;
|
||||
import com.google.android.exoplayer.TrackSelector.InvalidationListener;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.PriorityHandlerThread;
|
||||
import com.google.android.exoplayer.util.TraceUtil;
|
||||
|
|
@ -41,15 +42,14 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
*/
|
||||
// TODO[REFACTOR]: Make sure renderer errors that will prevent prepare from being called again are
|
||||
// always propagated properly.
|
||||
/* package */ final class ExoPlayerImplInternal implements Handler.Callback {
|
||||
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, InvalidationListener {
|
||||
|
||||
private static final String TAG = "ExoPlayerImplInternal";
|
||||
|
||||
// External messages
|
||||
public static final int MSG_PREPARED = 1;
|
||||
public static final int MSG_STATE_CHANGED = 2;
|
||||
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 3;
|
||||
public static final int MSG_ERROR = 4;
|
||||
public static final int MSG_STATE_CHANGED = 1;
|
||||
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 2;
|
||||
public static final int MSG_ERROR = 3;
|
||||
|
||||
// Internal messages
|
||||
private static final int MSG_PREPARE = 1;
|
||||
|
|
@ -59,13 +59,14 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
private static final int MSG_RELEASE = 5;
|
||||
private static final int MSG_SEEK_TO = 6;
|
||||
private static final int MSG_DO_SOME_WORK = 7;
|
||||
private static final int MSG_SET_RENDERER_SELECTED_TRACK = 8;
|
||||
private static final int MSG_TRACK_SELECTION_INVALIDATED = 8;
|
||||
private static final int MSG_CUSTOM = 9;
|
||||
|
||||
private static final int PREPARE_INTERVAL_MS = 10;
|
||||
private static final int RENDERING_INTERVAL_MS = 10;
|
||||
private static final int IDLE_INTERVAL_MS = 1000;
|
||||
|
||||
private final TrackSelector trackSelector;
|
||||
private final TrackRenderer[] renderers;
|
||||
private final TrackRenderer rendererMediaClockSource;
|
||||
private final MediaClock rendererMediaClock;
|
||||
|
|
@ -73,9 +74,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
private final long minBufferUs;
|
||||
private final long minRebufferUs;
|
||||
private final List<TrackRenderer> enabledRenderers;
|
||||
private final int[] selectedTrackIndices;
|
||||
private final TrackSelection[][] trackSelections;
|
||||
private final Format[][][] trackFormats;
|
||||
private final TrackSelection[] trackSelections;
|
||||
private final Handler handler;
|
||||
private final HandlerThread internalPlaybackThread;
|
||||
private final Handler eventHandler;
|
||||
|
|
@ -95,13 +94,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
private volatile long positionUs;
|
||||
private volatile long bufferedPositionUs;
|
||||
|
||||
public ExoPlayerImplInternal(TrackRenderer[] renderers, int minBufferMs, int minRebufferMs,
|
||||
boolean playWhenReady, int[] selectedTrackIndices, Handler eventHandler) {
|
||||
public ExoPlayerImplInternal(TrackRenderer[] renderers, TrackSelector trackSelector,
|
||||
int minBufferMs, int minRebufferMs, boolean playWhenReady, Handler eventHandler) {
|
||||
this.renderers = renderers;
|
||||
this.trackSelector = trackSelector;
|
||||
this.minBufferUs = minBufferMs * 1000L;
|
||||
this.minRebufferUs = minRebufferMs * 1000L;
|
||||
this.playWhenReady = playWhenReady;
|
||||
this.selectedTrackIndices = Arrays.copyOf(selectedTrackIndices, selectedTrackIndices.length);
|
||||
this.eventHandler = eventHandler;
|
||||
this.state = ExoPlayer.STATE_IDLE;
|
||||
this.durationUs = C.UNKNOWN_TIME_US;
|
||||
|
|
@ -125,8 +124,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
standaloneMediaClock = new StandaloneMediaClock();
|
||||
pendingSeekCount = new AtomicInteger();
|
||||
enabledRenderers = new ArrayList<>(renderers.length);
|
||||
trackSelections = new TrackSelection[renderers.length][];
|
||||
trackFormats = new Format[renderers.length][][];
|
||||
trackSelections = new TrackSelection[renderers.length];
|
||||
|
||||
trackSelector.init(this);
|
||||
|
||||
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
|
||||
// not normally change to this priority" is incorrect.
|
||||
internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler",
|
||||
|
|
@ -171,11 +172,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
handler.sendEmptyMessage(MSG_STOP);
|
||||
}
|
||||
|
||||
public void setRendererSelectedTrack(int rendererIndex, int trackIndex) {
|
||||
handler.obtainMessage(MSG_SET_RENDERER_SELECTED_TRACK, rendererIndex, trackIndex)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
public void sendMessage(ExoPlayerComponent target, int messageType, Object message) {
|
||||
customMessagesSent++;
|
||||
handler.obtainMessage(MSG_CUSTOM, messageType, 0, Pair.create(target, message)).sendToTarget();
|
||||
|
|
@ -213,6 +209,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
internalPlaybackThread.quit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackSelectionsInvalidated() {
|
||||
handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
try {
|
||||
|
|
@ -249,8 +250,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
sendMessageInternal(msg.arg1, msg.obj);
|
||||
return true;
|
||||
}
|
||||
case MSG_SET_RENDERER_SELECTED_TRACK: {
|
||||
setRendererSelectedTrackInternal(msg.arg1, msg.arg2);
|
||||
case MSG_TRACK_SELECTION_INVALIDATED: {
|
||||
reselectTracksInternal();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
|
|
@ -299,95 +300,24 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
|
||||
durationUs = source.getDurationUs();
|
||||
bufferedPositionUs = source.getBufferedPositionUs();
|
||||
TrackGroupArray trackGroups = source.getTrackGroups();
|
||||
|
||||
// The maximum number of tracks that one renderer can support is the total number of tracks in
|
||||
// all groups, plus possibly one adaptive track per group.
|
||||
int maxTrackCount = trackGroups.length;
|
||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||
maxTrackCount += trackGroups.get(groupIndex).length;
|
||||
}
|
||||
// Construct tracks for each renderer.
|
||||
Format[][] externalTrackFormats = new Format[renderers.length][];
|
||||
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
|
||||
TrackRenderer renderer = renderers[rendererIndex];
|
||||
int rendererTrackCount = 0;
|
||||
Format[] rendererExternalTrackFormats = new Format[maxTrackCount];
|
||||
TrackSelection[] rendererTrackSelections = new TrackSelection[maxTrackCount];
|
||||
Format[][] rendererTrackFormats = new Format[maxTrackCount][];
|
||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||
TrackGroup trackGroup = trackGroups.get(groupIndex);
|
||||
// TODO[REFACTOR]: This should check that the renderer is capable of adaptive playback, in
|
||||
// addition to checking that the group is adaptive.
|
||||
if (trackGroup.adaptive) {
|
||||
// Try and build an adaptive track.
|
||||
int adaptiveTrackIndexCount = 0;
|
||||
int[] adaptiveTrackIndices = new int[trackGroup.length];
|
||||
Format[] adaptiveTrackFormats = new Format[trackGroup.length];
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
Format trackFormat = trackGroup.getFormat(trackIndex);
|
||||
if ((renderer.supportsFormat(trackFormat) & TrackRenderer.FORMAT_SUPPORT_MASK)
|
||||
== TrackRenderer.FORMAT_HANDLED) {
|
||||
adaptiveTrackIndices[adaptiveTrackIndexCount] = trackIndex;
|
||||
adaptiveTrackFormats[adaptiveTrackIndexCount++] = trackFormat;
|
||||
}
|
||||
}
|
||||
if (adaptiveTrackIndexCount > 1) {
|
||||
// We succeeded in building an adaptive track.
|
||||
rendererTrackSelections[rendererTrackCount] = new TrackSelection(groupIndex,
|
||||
Arrays.copyOf(adaptiveTrackIndices, adaptiveTrackIndexCount));
|
||||
rendererTrackFormats[rendererTrackCount] =
|
||||
Arrays.copyOf(adaptiveTrackFormats, adaptiveTrackIndexCount);
|
||||
rendererExternalTrackFormats[rendererTrackCount++] = Format.createSampleFormat(
|
||||
"auto", adaptiveTrackFormats[0].sampleMimeType, Format.NO_VALUE);
|
||||
}
|
||||
}
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
Format trackFormat = trackGroup.getFormat(trackIndex);
|
||||
if ((renderer.supportsFormat(trackFormat) & TrackRenderer.FORMAT_SUPPORT_MASK)
|
||||
== TrackRenderer.FORMAT_HANDLED) {
|
||||
rendererTrackSelections[rendererTrackCount] = new TrackSelection(groupIndex,
|
||||
trackIndex);
|
||||
rendererTrackFormats[rendererTrackCount] = new Format[] {trackFormat};
|
||||
rendererExternalTrackFormats[rendererTrackCount++] = trackFormat;
|
||||
}
|
||||
}
|
||||
}
|
||||
trackSelections[rendererIndex] = Arrays.copyOf(rendererTrackSelections, rendererTrackCount);
|
||||
trackFormats[rendererIndex] = Arrays.copyOf(rendererTrackFormats, rendererTrackCount);
|
||||
externalTrackFormats[rendererIndex] = Arrays.copyOf(rendererExternalTrackFormats,
|
||||
rendererTrackCount);
|
||||
}
|
||||
selectTracksInternal();
|
||||
|
||||
boolean allRenderersEnded = true;
|
||||
boolean allRenderersReadyOrEnded = true;
|
||||
|
||||
// Enable renderers where appropriate.
|
||||
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
|
||||
TrackRenderer renderer = renderers[rendererIndex];
|
||||
int trackIndex = selectedTrackIndices[rendererIndex];
|
||||
if (0 <= trackIndex && trackIndex < trackSelections[rendererIndex].length) {
|
||||
TrackStream trackStream = source.enable(trackSelections[rendererIndex][trackIndex],
|
||||
positionUs);
|
||||
renderer.enable(trackFormats[rendererIndex][trackIndex], trackStream, positionUs, false);
|
||||
enabledRenderers.add(renderer);
|
||||
allRenderersEnded = allRenderersEnded && renderer.isEnded();
|
||||
allRenderersReadyOrEnded = allRenderersReadyOrEnded && isReadyOrEnded(renderer);
|
||||
}
|
||||
allRenderersEnded = allRenderersEnded && renderer.isEnded();
|
||||
allRenderersReadyOrEnded = allRenderersReadyOrEnded && isReadyOrEnded(renderer);
|
||||
}
|
||||
|
||||
if (allRenderersEnded && (durationUs == C.UNKNOWN_TIME_US || durationUs <= positionUs)) {
|
||||
// We don't expect this case, but handle it anyway.
|
||||
state = ExoPlayer.STATE_ENDED;
|
||||
setState(ExoPlayer.STATE_ENDED);
|
||||
} else {
|
||||
state = allRenderersReadyOrEnded && haveSufficientBuffer() ? ExoPlayer.STATE_READY
|
||||
: ExoPlayer.STATE_BUFFERING;
|
||||
setState(allRenderersReadyOrEnded && haveSufficientBuffer() ? ExoPlayer.STATE_READY
|
||||
: ExoPlayer.STATE_BUFFERING);
|
||||
}
|
||||
|
||||
// Fire an event indicating that the player has been prepared, passing the initial state and
|
||||
// renderer track information.
|
||||
eventHandler.obtainMessage(MSG_PREPARED, state, 0, externalTrackFormats).sendToTarget();
|
||||
|
||||
// Start the renderers if required, and schedule the first piece of work.
|
||||
if (playWhenReady && state == ExoPlayer.STATE_READY) {
|
||||
startRenderers();
|
||||
|
|
@ -604,56 +534,82 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
}
|
||||
}
|
||||
|
||||
private void setRendererSelectedTrackInternal(int rendererIndex, int trackIndex)
|
||||
throws ExoPlaybackException {
|
||||
if (selectedTrackIndices[rendererIndex] == trackIndex) {
|
||||
return;
|
||||
private void selectTracksInternal() throws ExoPlaybackException {
|
||||
TrackGroupArray groups = source.getTrackGroups();
|
||||
Pair<TrackSelectionArray, Object> result = trackSelector.selectTracks(renderers, groups);
|
||||
TrackSelectionArray newTrackSelections = result.first;
|
||||
|
||||
// We disable all renderers whose track selections have changed, then enable renderers with new
|
||||
// track selections during a second pass. Doing all disables before any enables is necessary
|
||||
// because the new track selection for some renderer X may have the same track group as the old
|
||||
// selection for some other renderer Y. Trying to enable X before disabling Y would fail, since
|
||||
// sources do not support being enabled more than once per track group at a time.
|
||||
|
||||
// Disable renderers whose track selections have changed.
|
||||
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
TrackRenderer renderer = renderers[i];
|
||||
TrackSelection previousTrackSelection = trackSelections[i];
|
||||
trackSelections[i] = newTrackSelections.get(i);
|
||||
if (!Util.areEqual(previousTrackSelection, trackSelections[i])) {
|
||||
// The track selection has changed for this renderer.
|
||||
int rendererState = renderer.getState();
|
||||
boolean isEnabled = rendererState == TrackRenderer.STATE_ENABLED
|
||||
|| rendererState == TrackRenderer.STATE_STARTED;
|
||||
if (isEnabled) {
|
||||
if (trackSelections[i] == null && renderer == rendererMediaClockSource) {
|
||||
// We've been using rendererMediaClockSource to advance the current position, but it's
|
||||
// being disabled and won't be re-enabled. Sync standaloneMediaClock so that it can take
|
||||
// over timing responsibilities.
|
||||
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
|
||||
}
|
||||
ensureStopped(renderer);
|
||||
enabledRenderers.remove(renderer);
|
||||
renderer.disable();
|
||||
}
|
||||
rendererWasEnabledFlags[i] = isEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
selectedTrackIndices[rendererIndex] = trackIndex;
|
||||
// Enable renderers with their new track selections.
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
TrackRenderer renderer = renderers[i];
|
||||
TrackSelection trackSelection = trackSelections[i];
|
||||
int rendererState = renderer.getState();
|
||||
boolean isEnabled = rendererState == TrackRenderer.STATE_ENABLED
|
||||
|| rendererState == TrackRenderer.STATE_STARTED;
|
||||
if (!isEnabled && trackSelection != null) {
|
||||
// The renderer needs enabling with its new track selection.
|
||||
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
|
||||
// Consider as joining only if the renderer was previously disabled.
|
||||
boolean joining = !rendererWasEnabledFlags[i] && playing;
|
||||
// Enable the source and obtain the stream for the renderer to consume.
|
||||
TrackStream trackStream = source.enable(trackSelection, positionUs);
|
||||
// Build an array of formats contained by the new selection.
|
||||
Format[] formats = new Format[trackSelection.length];
|
||||
for (int j = 0; j < formats.length; j++) {
|
||||
formats[j] = groups.get(trackSelections[i].group).getFormat(trackSelection.getTrack(j));
|
||||
}
|
||||
// Enable the renderer.
|
||||
renderer.enable(formats, trackStream, positionUs, joining);
|
||||
enabledRenderers.add(renderer);
|
||||
if (playing) {
|
||||
renderer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The new selections have been activated.
|
||||
trackSelector.onSelectionActivated(result.second);
|
||||
}
|
||||
|
||||
private void reselectTracksInternal() throws ExoPlaybackException {
|
||||
if (state == ExoPlayer.STATE_IDLE || state == ExoPlayer.STATE_PREPARING) {
|
||||
// We don't have tracks yet, so we don't care.
|
||||
return;
|
||||
}
|
||||
|
||||
TrackRenderer renderer = renderers[rendererIndex];
|
||||
int rendererState = renderer.getState();
|
||||
if (trackSelections[rendererIndex].length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isEnabled = rendererState == TrackRenderer.STATE_ENABLED
|
||||
|| rendererState == TrackRenderer.STATE_STARTED;
|
||||
boolean shouldEnable = 0 <= trackIndex && trackIndex < trackSelections[rendererIndex].length;
|
||||
|
||||
if (isEnabled) {
|
||||
// The renderer is currently enabled. We need to disable it, so that we can either re-enable
|
||||
// it with the newly selected track (if shouldEnable is true) or because we want to leave it
|
||||
// disabled (if shouldEnable is false).
|
||||
if (!shouldEnable && renderer == rendererMediaClockSource) {
|
||||
// We've been using rendererMediaClockSource to advance the current position, but it's being
|
||||
// disabled and won't be re-enabled. Sync standaloneMediaClock so that it can take over
|
||||
// timing responsibilities.
|
||||
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
|
||||
}
|
||||
ensureStopped(renderer);
|
||||
enabledRenderers.remove(renderer);
|
||||
renderer.disable();
|
||||
}
|
||||
|
||||
if (shouldEnable) {
|
||||
// Re-enable the renderer with the newly selected track.
|
||||
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
|
||||
// Consider as joining if the renderer was previously disabled, but not when switching tracks.
|
||||
boolean joining = !isEnabled && playing;
|
||||
TrackStream trackStream = source.enable(trackSelections[rendererIndex][trackIndex],
|
||||
positionUs);
|
||||
renderer.enable(trackFormats[rendererIndex][trackIndex], trackStream, positionUs, joining);
|
||||
enabledRenderers.add(renderer);
|
||||
if (playing) {
|
||||
renderer.start();
|
||||
}
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
}
|
||||
selectTracksInternal();
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
}
|
||||
|
||||
private void ensureStopped(TrackRenderer renderer) throws ExoPlaybackException {
|
||||
|
|
|
|||
|
|
@ -306,9 +306,8 @@ public final class Format {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "Format(" + id + ", " + containerMimeType + ", " + sampleMimeType + ", " + bitrate + ", "
|
||||
+ maxInputSize + ", " + language + ", [" + width + ", " + height + ", " + frameRate + ", "
|
||||
+ rotationDegrees + ", " + pixelWidthHeightRatio + "]" + ", [" + channelCount + ", "
|
||||
+ sampleRate + "])";
|
||||
+ ", " + language + ", [" + width + ", " + height + ", " + frameRate + "]"
|
||||
+ ", [" + channelCount + ", " + sampleRate + "])";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import java.util.Arrays;
|
|||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,21 @@ public final class TrackSelection {
|
|||
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
|
||||
public int hashCode() {
|
||||
if (hashCode == 0) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
|
@ -1,81 +0,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.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;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue