diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java index 43681ca39c..0195cb12cb 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java @@ -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]" : "[ ]"; } } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index 8ada1a5d0e..6b5c7abfe3 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -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() { diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java index 76e8be6348..3582ee9c13 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java @@ -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>, 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>, + 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.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 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 metadata) { - if (id3MetadataListener != null && getSelectedTrack(TYPE_METADATA) != TRACK_DISABLED) { + if (id3MetadataListener != null && trackInfo.getTrackSelection(TYPE_METADATA) != null) { id3MetadataListener.onId3Metadata(metadata); } } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/ui/TrackSelectionHelper.java b/demo/src/main/java/com/google/android/exoplayer/demo/ui/TrackSelectionHelper.java new file mode 100644 index 0000000000..f96749af4d --- /dev/null +++ b/demo/src/main/java/com/google/android/exoplayer/demo/ui/TrackSelectionHelper.java @@ -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 tag = (Pair) 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 + ")"; + } + +} diff --git a/demo/src/main/res/layout/list_divider.xml b/demo/src/main/res/layout/list_divider.xml new file mode 100644 index 0000000000..21b2feb5ea --- /dev/null +++ b/demo/src/main/res/layout/list_divider.xml @@ -0,0 +1,19 @@ + + + diff --git a/demo/src/main/res/layout/player_activity.xml b/demo/src/main/res/layout/player_activity.xml index ae0aa5f1d2..4e189292c1 100644 --- a/demo/src/main/res/layout/player_activity.xml +++ b/demo/src/main/res/layout/player_activity.xml @@ -94,13 +94,6 @@ android:visibility="gone" android:onClick="showTextPopup"/> -