diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index 312f2ac3b3..8871e718d6 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -22,6 +22,13 @@ import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; +import com.google.android.exoplayer2.metadata.MetadataRenderer; +import com.google.android.exoplayer2.metadata.id3.ApicFrame; +import com.google.android.exoplayer2.metadata.id3.GeobFrame; +import com.google.android.exoplayer2.metadata.id3.Id3Frame; +import com.google.android.exoplayer2.metadata.id3.PrivFrame; +import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; +import com.google.android.exoplayer2.metadata.id3.TxxxFrame; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.Timeline; @@ -37,14 +44,16 @@ import android.util.Log; import java.io.IOException; import java.text.NumberFormat; +import java.util.List; import java.util.Locale; /** * Logs player events using {@link Log}. */ -public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.DebugListener, - AdaptiveMediaSourceEventListener, ExtractorMediaSource.EventListener, - StreamingDrmSessionManager.EventListener, MappingTrackSelector.EventListener { +/* package */ final class EventLogger implements ExoPlayer.EventListener, + SimpleExoPlayer.DebugListener, AdaptiveMediaSourceEventListener, + ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener, + MappingTrackSelector.EventListener, MetadataRenderer.Output> { private static final String TAG = "EventLogger"; private static final NumberFormat TIME_FORMAT; @@ -54,15 +63,10 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb TIME_FORMAT.setMaximumFractionDigits(2); } - private long sessionStartTimeMs; + private final long startTimeMs; - public void startSession() { - sessionStartTimeMs = SystemClock.elapsedRealtime(); - Log.d(TAG, "start [0]"); - } - - public void endSession() { - Log.d(TAG, "end [" + getSessionTimeString() + "]"); + public EventLogger() { + startTimeMs = SystemClock.elapsedRealtime(); } // ExoPlayer.EventListener @@ -152,6 +156,36 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb Log.d(TAG, "]"); } + // MetadataRenderer.Output> + + @Override + public void onMetadata(List id3Frames) { + for (Id3Frame id3Frame : id3Frames) { + if (id3Frame instanceof TxxxFrame) { + TxxxFrame txxxFrame = (TxxxFrame) id3Frame; + Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s, value=%s", txxxFrame.id, + txxxFrame.description, txxxFrame.value)); + } else if (id3Frame instanceof PrivFrame) { + PrivFrame privFrame = (PrivFrame) id3Frame; + Log.i(TAG, String.format("ID3 TimedMetadata %s: owner=%s", privFrame.id, privFrame.owner)); + } else if (id3Frame instanceof GeobFrame) { + GeobFrame geobFrame = (GeobFrame) id3Frame; + Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s", + geobFrame.id, geobFrame.mimeType, geobFrame.filename, geobFrame.description)); + } else if (id3Frame instanceof ApicFrame) { + ApicFrame apicFrame = (ApicFrame) id3Frame; + Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, description=%s", + apicFrame.id, apicFrame.mimeType, apicFrame.description)); + } else if (id3Frame instanceof TextInformationFrame) { + TextInformationFrame textInformationFrame = (TextInformationFrame) id3Frame; + Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s", textInformationFrame.id, + textInformationFrame.description)); + } else { + Log.i(TAG, String.format("ID3 TimedMetadata %s", id3Frame.id)); + } + } + } + // SimpleExoPlayer.DebugListener @Override @@ -282,7 +316,7 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb } private String getSessionTimeString() { - return getTimeString(SystemClock.elapsedRealtime() - sessionStartTimeMs); + return getTimeString(SystemClock.elapsedRealtime() - startTimeMs); } private static String getTimeString(long timeMs) { diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index d01fb004f3..f9dd3ea1cb 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -28,12 +28,6 @@ import com.google.android.exoplayer2.drm.UnsupportedDrmException; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; -import com.google.android.exoplayer2.metadata.id3.ApicFrame; -import com.google.android.exoplayer2.metadata.id3.GeobFrame; -import com.google.android.exoplayer2.metadata.id3.Id3Frame; -import com.google.android.exoplayer2.metadata.id3.PrivFrame; -import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; -import com.google.android.exoplayer2.metadata.id3.TxxxFrame; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; @@ -44,8 +38,6 @@ import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; -import com.google.android.exoplayer2.text.CaptionStyleCompat; -import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; @@ -53,36 +45,31 @@ import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackIn import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.DebugTextViewHelper; +import com.google.android.exoplayer2.ui.KeyCompatibleMediaController; +import com.google.android.exoplayer2.ui.MediaControllerPrevNextClickListener; import com.google.android.exoplayer2.ui.PlayerControl; import com.google.android.exoplayer2.ui.SubtitleView; -import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.util.Util; -import android.Manifest.permission; -import android.annotation.TargetApi; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; -import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; -import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnKeyListener; import android.view.View.OnTouchListener; -import android.view.accessibility.CaptioningManager; import android.widget.Button; import android.widget.LinearLayout; import android.widget.MediaController; @@ -92,15 +79,14 @@ import android.widget.Toast; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; -import java.util.List; import java.util.UUID; /** * An activity that plays media using {@link SimpleExoPlayer}. */ -public class PlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener, - ExoPlayer.EventListener, SimpleExoPlayer.VideoListener, SimpleExoPlayer.CaptionListener, - SimpleExoPlayer.Id3MetadataListener, MappingTrackSelector.EventListener { +public class PlayerActivity extends Activity implements OnKeyListener, OnTouchListener, + OnClickListener, ExoPlayer.EventListener, SimpleExoPlayer.VideoListener, + MappingTrackSelector.EventListener { public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid"; public static final String DRM_LICENSE_URL = "drm_license_url"; @@ -114,13 +100,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, public static final String URI_LIST_EXTRA = "uri_list"; public static final String EXTENSION_LIST_EXTRA = "extension_list"; - private static final String TAG = "PlayerActivity"; - - private static final CookieManager defaultCookieManager; - + private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); + private static final CookieManager DEFAULT_COOKIE_MANAGER; static { - defaultCookieManager = new CookieManager(); - defaultCookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); + DEFAULT_COOKIE_MANAGER = new CookieManager(); + DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); } private Handler mainHandler; @@ -136,9 +120,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, private Button retryButton; private String userAgent; - private DataSource.Factory manifestDataSourceFactory; - private DataSource.Factory mediaDataSourceFactory; - private DefaultBandwidthMeter bandwidthMeter; + private DefaultDataSourceFactory mediaDataSourceFactory; private SimpleExoPlayer player; private MappingTrackSelector trackSelector; private TrackSelectionHelper trackSelectionHelper; @@ -154,51 +136,28 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); - manifestDataSourceFactory = new DefaultDataSourceFactory(this, userAgent); - bandwidthMeter = new DefaultBandwidthMeter(); - mediaDataSourceFactory = new DefaultDataSourceFactory(this, userAgent, bandwidthMeter); - + mediaDataSourceFactory = new DefaultDataSourceFactory(this, userAgent, BANDWIDTH_METER); mainHandler = new Handler(); + if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { + CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER); + } + setContentView(R.layout.player_activity); rootView = findViewById(R.id.root); - rootView.setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { - toggleControlsVisibility(); - } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) { - view.performClick(); - } - return true; - } - }); - rootView.setOnKeyListener(new OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - return keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_ESCAPE - && keyCode != KeyEvent.KEYCODE_MENU && mediaController.dispatchKeyEvent(event); - } - }); - + rootView.setOnTouchListener(this); + rootView.setOnKeyListener(this); shutterView = findViewById(R.id.shutter); debugRootView = (LinearLayout) findViewById(R.id.controls_root); - videoFrame = (AspectRatioFrameLayout) findViewById(R.id.video_frame); surfaceView = (SurfaceView) findViewById(R.id.surface_view); - surfaceView.getHolder().addCallback(this); debugTextView = (TextView) findViewById(R.id.debug_text_view); subtitleView = (SubtitleView) findViewById(R.id.subtitles); + subtitleView.setUserDefaultStyle(); + subtitleView.setUserDefaultTextSize(); mediaController = new KeyCompatibleMediaController(this); mediaController.setPrevNextListeners(this, this); retryButton = (Button) findViewById(R.id.retry_button); retryButton.setOnClickListener(this); - - CookieHandler currentHandler = CookieHandler.getDefault(); - if (currentHandler != defaultCookieManager) { - CookieHandler.setDefault(defaultCookieManager); - } - - configureSubtitleView(); } @Override @@ -240,6 +199,37 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, } } + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + initializePlayer(); + } else { + showToast(R.string.storage_permission_denied); + finish(); + } + } + + // OnTouchListener methods + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { + toggleControlsVisibility(); + } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) { + view.performClick(); + } + return true; + } + + // OnKeyListener methods + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + return keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_ESCAPE + && keyCode != KeyEvent.KEYCODE_MENU && mediaController.dispatchKeyEvent(event); + } + // OnClickListener methods @Override @@ -252,19 +242,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, } } - // Permission request listener method - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - initializePlayer(); - } else { - showToast(R.string.storage_permission_denied); - finish(); - } - } - // Internal methods private void initializePlayer() { @@ -279,15 +256,17 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, try { drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmLicenseUrl); } catch (UnsupportedDrmException e) { - onUnsupportedDrmError(e); + int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported + : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME + ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); + showToast(errorStringId); return; } } eventLogger = new EventLogger(); - eventLogger.startSession(); TrackSelection.Factory videoTrackSelectionFactory = - new AdaptiveVideoTrackSelection.Factory(bandwidthMeter); + new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER); trackSelector = new DefaultTrackSelector(mainHandler, videoTrackSelectionFactory); trackSelector.addListener(this); trackSelector.addListener(eventLogger); @@ -297,15 +276,15 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, player.addListener(this); player.addListener(eventLogger); player.setDebugListener(eventLogger); + player.setId3Output(eventLogger); + player.setTextOutput(subtitleView); player.setVideoListener(this); - player.setCaptionListener(this); - player.setMetadataListener(this); + player.setVideoSurfaceHolder(surfaceView.getHolder()); player.seekTo(playerPeriodIndex, playerPosition); - player.setSurface(surfaceView.getHolder().getSurface()); player.setPlayWhenReady(true); mediaController.setMediaPlayer(new PlayerControl(player)); - mediaController.setPrevNextListeners(new PrevNextClickListener(player, true), - new PrevNextClickListener(player, false)); + mediaController.setPrevNextListeners(new MediaControllerPrevNextClickListener(player, true), + new MediaControllerPrevNextClickListener(player, false)); mediaController.setAnchorView(rootView); debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper.start(); @@ -329,17 +308,16 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, extensions = new String[uriStrings.length]; } } else { - Log.w(TAG, "Unexpected intent action: " + action); + showToast(getString(R.string.unexpected_intent_action, action)); return; } - if (maybeRequestPermission(uris)) { - // The player will be reinitialized if permission is granted. + if (Util.maybeRequestReadExternalStoragePermission(this, uris)) { + // The player will be reinitialized if the permission is granted. return; } - MediaSource[] mediaSources = new MediaSource[uris.length]; for (int i = 0; i < uris.length; i++) { - mediaSources[i] = getMediaSource(uris[i], extensions[i]); + mediaSources[i] = buildMediaSource(uris[i], extensions[i]); } MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources); @@ -349,26 +327,24 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, } } - private MediaSource getMediaSource(Uri uri, String overrideExtension) { - String lastPathSegment = !TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension - : uri.getLastPathSegment(); - int type = Util.inferContentType(lastPathSegment); + private MediaSource buildMediaSource(Uri uri, String overrideExtension) { + int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension + : uri.getLastPathSegment()); switch (type) { case Util.TYPE_SS: - DefaultSsChunkSource.Factory factory = new DefaultSsChunkSource.Factory( - mediaDataSourceFactory); - return new SsMediaSource(uri, manifestDataSourceFactory, factory, mainHandler, eventLogger); + return new SsMediaSource(uri, new DefaultDataSourceFactory(this, userAgent), + new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger); case Util.TYPE_DASH: - DefaultDashChunkSource.Factory factory2 = new DefaultDashChunkSource.Factory( - mediaDataSourceFactory); - return new DashMediaSource(uri, mediaDataSourceFactory, factory2, mainHandler, eventLogger); + return new DashMediaSource(uri, new DefaultDataSourceFactory(this, userAgent), + new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger); case Util.TYPE_HLS: return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger); case Util.TYPE_OTHER: return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), mainHandler, eventLogger); - default: + default: { throw new IllegalStateException("Unsupported type: " + type); + } } } @@ -387,37 +363,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, return new StreamingDrmSessionManager(uuid, drmCallback, null, mainHandler, eventLogger); } - private void onUnsupportedDrmError(UnsupportedDrmException e) { - int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported - : e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME - ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown; - showToast(errorStringId); - } - - /** - * Checks whether it is necessary to ask for permission to read storage. If necessary, it also - * requests permission. - * - * @param uris URIs that may require {@link permission#READ_EXTERNAL_STORAGE} to play. - * @return Whether a permission request was made. - */ - @TargetApi(23) - private boolean maybeRequestPermission(Uri... uris) { - if (Util.SDK_INT >= 23) { - for (Uri uri : uris) { - if (Util.isLocalFileUri(uri)) { - if (checkSelfPermission(permission.READ_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0); - return true; - } - break; - } - } - } - return false; - } - private void releasePlayer() { if (player != null) { shutterView.setVisibility(View.VISIBLE); @@ -429,7 +374,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, player = null; trackSelector = null; trackSelectionHelper = null; - eventLogger.endSession(); eventLogger = null; } } @@ -505,8 +449,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthAspectRatio) { - videoFrame.setAspectRatio( - height == 0 ? 1 : (width * pixelWidthAspectRatio) / height); + videoFrame.setAspectRatio(height == 0 ? 1 : (width * pixelWidthAspectRatio) / height); } @Override @@ -519,20 +462,10 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, @Override public void onTracksChanged(TrackInfo trackInfo) { updateButtonVisibilities(); - // Print toasts if there exist only unplayable video or audio tracks. - int videoRendererSupport = TrackInfo.RENDERER_SUPPORT_NO_TRACKS; - int audioRendererSupport = TrackInfo.RENDERER_SUPPORT_NO_TRACKS; - for (int i = 0; i < trackInfo.rendererCount; i++) { - if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO) { - videoRendererSupport = Math.max(videoRendererSupport, trackInfo.getRendererSupport(i)); - } else if (player.getRendererType(i) == C.TRACK_TYPE_AUDIO) { - audioRendererSupport = Math.max(audioRendererSupport, trackInfo.getRendererSupport(i)); - } - } - if (videoRendererSupport == TrackInfo.RENDERER_SUPPORT_UNPLAYABLE_TRACKS) { + if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) { showToast(R.string.error_unsupported_video); } - if (audioRendererSupport == TrackInfo.RENDERER_SUPPORT_UNPLAYABLE_TRACKS) { + if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_AUDIO)) { showToast(R.string.error_unsupported_audio); } } @@ -561,10 +494,17 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, Button button = new Button(this); int label; switch (player.getRendererType(i)) { - case C.TRACK_TYPE_AUDIO: label = R.string.audio; break; - case C.TRACK_TYPE_VIDEO: label = R.string.video; break; - case C.TRACK_TYPE_TEXT: label = R.string.text; break; - default: continue; + case C.TRACK_TYPE_AUDIO: + label = R.string.audio; + break; + case C.TRACK_TYPE_VIDEO: + label = R.string.video; + break; + case C.TRACK_TYPE_TEXT: + label = R.string.text; + break; + default: + continue; } button.setText(label); button.setTag(i); @@ -588,92 +528,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, debugRootView.setVisibility(View.VISIBLE); } - // SimpleExoPlayer.CaptionListener implementation - - @Override - public void onCues(List cues) { - subtitleView.setCues(cues); - } - - // SimpleExoPlayer.MetadataListener implementation - - @Override - public void onId3Metadata(List id3Frames) { - for (Id3Frame id3Frame : id3Frames) { - if (id3Frame instanceof TxxxFrame) { - TxxxFrame txxxFrame = (TxxxFrame) id3Frame; - Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s, value=%s", txxxFrame.id, - txxxFrame.description, txxxFrame.value)); - } else if (id3Frame instanceof PrivFrame) { - PrivFrame privFrame = (PrivFrame) id3Frame; - Log.i(TAG, String.format("ID3 TimedMetadata %s: owner=%s", privFrame.id, privFrame.owner)); - } else if (id3Frame instanceof GeobFrame) { - GeobFrame geobFrame = (GeobFrame) id3Frame; - Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s", - geobFrame.id, geobFrame.mimeType, geobFrame.filename, geobFrame.description)); - } else if (id3Frame instanceof ApicFrame) { - ApicFrame apicFrame = (ApicFrame) id3Frame; - Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, description=%s", - apicFrame.id, apicFrame.mimeType, apicFrame.description)); - } else if (id3Frame instanceof TextInformationFrame) { - TextInformationFrame textInformationFrame = (TextInformationFrame) id3Frame; - Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s", textInformationFrame.id, - textInformationFrame.description)); - } else { - Log.i(TAG, String.format("ID3 TimedMetadata %s", id3Frame.id)); - } - } - } - - // SurfaceHolder.Callback implementation - - @Override - public void surfaceCreated(SurfaceHolder holder) { - if (player != null) { - player.setSurface(holder.getSurface()); - } - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - // Do nothing. - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - if (player != null) { - player.setSurface(null); - } - } - - private void configureSubtitleView() { - CaptionStyleCompat style; - float fontScale; - if (Util.SDK_INT >= 19) { - style = getUserCaptionStyleV19(); - fontScale = getUserCaptionFontScaleV19(); - } else { - style = CaptionStyleCompat.DEFAULT; - fontScale = 1.0f; - } - subtitleView.setStyle(style); - subtitleView.setFractionalTextSize(SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * fontScale); - } - - @TargetApi(19) - private float getUserCaptionFontScaleV19() { - CaptioningManager captioningManager = - (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE); - return captioningManager.getFontScale(); - } - - @TargetApi(19) - private CaptionStyleCompat getUserCaptionStyleV19() { - CaptioningManager captioningManager = - (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE); - return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); - } - private void showToast(int messageId) { showToast(getString(messageId)); } @@ -682,71 +536,4 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); } - private static final class PrevNextClickListener implements OnClickListener { - - private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS_PERIOD = 3000; - - private final SimpleExoPlayer player; - private final boolean forward; - - public PrevNextClickListener(SimpleExoPlayer player, boolean forward) { - this.player = player; - this.forward = forward; - } - - @Override - public void onClick(View v) { - int currentPeriodIndex = player.getCurrentPeriodIndex(); - if (forward) { - if (currentPeriodIndex < player.getCurrentTimeline().getPeriodCount() - 1) { - player.seekTo(currentPeriodIndex + 1, 0); - } - } else { - if (currentPeriodIndex > 0 - && player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS_PERIOD) { - player.seekTo(currentPeriodIndex - 1, 0); - } else { - player.seekTo(currentPeriodIndex, 0); - } - } - } - - } - - private static final class KeyCompatibleMediaController extends MediaController { - - private MediaController.MediaPlayerControl playerControl; - - public KeyCompatibleMediaController(Context context) { - super(context); - } - - @Override - public void setMediaPlayer(MediaController.MediaPlayerControl playerControl) { - super.setMediaPlayer(playerControl); - this.playerControl = playerControl; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - int keyCode = event.getKeyCode(); - if (playerControl.canSeekForward() && (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD - || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - playerControl.seekTo(playerControl.getCurrentPosition() + 15000); // milliseconds - show(); - } - return true; - } else if (playerControl.canSeekBackward() && (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND - || keyCode == KeyEvent.KEYCODE_DPAD_LEFT)) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - playerControl.seekTo(playerControl.getCurrentPosition() - 5000); // milliseconds - show(); - } - return true; - } - return super.dispatchKeyEvent(event); - } - } - } diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index 01b5221a05..c0e1488fe5 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -33,6 +33,8 @@ Default (none) + Unexpected intent action: %1$s + Enable random adaptation Protected content not supported on API levels below 18 diff --git a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 27b1059d28..51fb0bedb4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -41,6 +41,7 @@ import android.media.PlaybackParams; import android.os.Handler; import android.util.Log; import android.view.Surface; +import android.view.SurfaceHolder; import java.lang.reflect.Constructor; import java.util.ArrayList; @@ -82,20 +83,6 @@ public final class SimpleExoPlayer implements ExoPlayer { void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); } - /** - * A listener for receiving notifications of timed text. - */ - public interface CaptionListener { - void onCues(List cues); - } - - /** - * A listener for receiving ID3 metadata parsed from the media stream. - */ - public interface Id3MetadataListener { - void onId3Metadata(List id3Frames); - } - private static final String TAG = "SimpleExoPlayer"; private static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; @@ -109,8 +96,9 @@ public final class SimpleExoPlayer implements ExoPlayer { private Format videoFormat; private Format audioFormat; - private CaptionListener captionListener; - private Id3MetadataListener id3MetadataListener; + private SurfaceHolder surfaceHolder; + private TextRenderer.Output textOutput; + private MetadataRenderer.Output> id3Output; private VideoListener videoListener; private DebugListener debugListener; private DecoderCounters videoDecoderCounters; @@ -176,23 +164,40 @@ public final class SimpleExoPlayer implements ExoPlayer { } /** - * Sets the {@link Surface} onto which video will be rendered. + * Sets the {@link Surface} onto which video will be rendered. The caller is responsible for + * tracking the lifecycle of the surface, and must clear the surface by calling + * {@code setVideoSurface(null)} if the surface is destroyed. + *

+ * If the surface is held by a {@link SurfaceHolder} then it's recommended to use + * {@link #setVideoSurfaceHolder(SurfaceHolder)} rather than this method, since passing the + * holder allows the player to track the lifecycle of the surface automatically. * * @param surface The {@link Surface}. */ - public void setSurface(Surface surface) { - ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; - int count = 0; - for (Renderer renderer : renderers) { - if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { - messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_SURFACE, surface); - } + public void setVideoSurface(Surface surface) { + if (surfaceHolder != null) { + surfaceHolder.removeCallback(componentListener); + surfaceHolder = null; } - if (surface == null) { - // Block to ensure that the surface is not accessed after the method returns. - player.blockingSendMessages(messages); + setVideoSurfaceInternal(surface); + } + + /** + * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be + * rendered. The player will track the lifecycle of the surface automatically. + * + * @param surfaceHolder The surface holder. + */ + public void setVideoSurfaceHolder(SurfaceHolder surfaceHolder) { + if (this.surfaceHolder != null) { + this.surfaceHolder.removeCallback(componentListener); + } + this.surfaceHolder = surfaceHolder; + if (surfaceHolder == null) { + setVideoSurfaceInternal(null); } else { - player.sendMessages(messages); + setVideoSurfaceInternal(surfaceHolder.getSurface()); + surfaceHolder.addCallback(componentListener); } } @@ -287,21 +292,21 @@ public final class SimpleExoPlayer implements ExoPlayer { } /** - * Sets a listener to receive caption events. + * Sets an output to receive text events. * - * @param listener The listener. + * @param output The output. */ - public void setCaptionListener(CaptionListener listener) { - captionListener = listener; + public void setTextOutput(TextRenderer.Output output) { + textOutput = output; } /** - * Sets a listener to receive metadata events. + * Sets a listener to receive ID3 metadata events. * - * @param listener The listener. + * @param output The output. */ - public void setMetadataListener(Id3MetadataListener listener) { - id3MetadataListener = listener; + public void setId3Output(MetadataRenderer.Output> output) { + id3Output = output; } // ExoPlayer implementation @@ -488,8 +493,25 @@ public final class SimpleExoPlayer implements ExoPlayer { } } + private void setVideoSurfaceInternal(Surface surface) { + ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_SURFACE, surface); + } + } + if (surface == null) { + // Block to ensure that the surface is not accessed after the method returns. + player.blockingSendMessages(messages); + } else { + player.sendMessages(messages); + } + } + private final class ComponentListener implements VideoRendererEventListener, - AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output> { + AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output>, + SurfaceHolder.Callback { // VideoRendererEventListener implementation @@ -603,21 +625,42 @@ public final class SimpleExoPlayer implements ExoPlayer { audioSessionId = AudioTrack.SESSION_ID_NOT_SET; } - // TextRendererOutput implementation + // TextRenderer.Output implementation @Override public void onCues(List cues) { - if (captionListener != null) { - captionListener.onCues(cues); + if (textOutput != null) { + textOutput.onCues(cues); } } - // MetadataRenderer implementation + // MetadataRenderer.Output> implementation @Override public void onMetadata(List id3Frames) { - if (id3MetadataListener != null) { - id3MetadataListener.onId3Metadata(id3Frames); + if (id3Output != null) { + id3Output.onMetadata(id3Frames); + } + } + + // SurfaceHolder.Callback implementation + + @Override + public void surfaceCreated(SurfaceHolder holder) { + if (player != null) { + setVideoSurfaceInternal(holder.getSurface()); + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + // Do nothing. + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + if (player != null) { + setVideoSurfaceInternal(null); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 6e0fdce015..ae2c4af3da 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.trackselection; import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -258,11 +257,13 @@ public abstract class MappingTrackSelector extends TrackSelector { // Create a track group array for each renderer, and trim each rendererFormatSupports entry. TrackGroupArray[] rendererTrackGroupArrays = new TrackGroupArray[rendererCapabilities.length]; + int[] rendererTrackTypes = new int[rendererCapabilities.length]; for (int i = 0; i < rendererCapabilities.length; i++) { int rendererTrackGroupCount = rendererTrackGroupCounts[i]; rendererTrackGroupArrays[i] = new TrackGroupArray( Arrays.copyOf(rendererTrackGroups[i], rendererTrackGroupCount)); rendererFormatSupports[i] = Arrays.copyOf(rendererFormatSupports[i], rendererTrackGroupCount); + rendererTrackTypes[i] = rendererCapabilities[i].getTrackType(); } // Create a track group array for track groups not associated with a renderer. @@ -289,8 +290,9 @@ public abstract class MappingTrackSelector extends TrackSelector { // Package up the track information and selections. TrackSelectionArray trackSelectionArray = new TrackSelectionArray(trackSelections); - TrackInfo trackInfo = new TrackInfo(rendererTrackGroupArrays, trackSelections, - mixedMimeTypeAdaptationSupport, rendererFormatSupports, unassociatedTrackGroupArray); + TrackInfo trackInfo = new TrackInfo(rendererTrackTypes, rendererTrackGroupArrays, + trackSelections, mixedMimeTypeAdaptationSupport, rendererFormatSupports, + unassociatedTrackGroupArray); return Pair.create(trackSelectionArray, trackInfo); } @@ -352,13 +354,13 @@ public abstract class MappingTrackSelector extends TrackSelector { } /** - * Calls {@link RendererCapabilities#supportsFormat(Format)} for each track in the specified + * Calls {@link RendererCapabilities#supportsFormat} for each track in the specified * {@link TrackGroup}, returning the results in an array. * * @param rendererCapabilities The {@link RendererCapabilities} of the renderer. * @param group The {@link TrackGroup} to evaluate. * @return An array containing the result of calling - * {@link RendererCapabilities#supportsFormat(Format)} for each track in the group. + * {@link RendererCapabilities#supportsFormat} for each track in the group. * @throws ExoPlaybackException If an error occurs determining the format support. */ private static int[] getFormatSupport(RendererCapabilities rendererCapabilities, TrackGroup group) @@ -424,6 +426,7 @@ public abstract class MappingTrackSelector extends TrackSelector { */ public final int rendererCount; + private final int[] rendererTrackTypes; private final TrackGroupArray[] trackGroups; private final TrackSelection[] trackSelections; private final int[] mixedMimeTypeAdaptiveSupport; @@ -431,17 +434,19 @@ public abstract class MappingTrackSelector extends TrackSelector { private final TrackGroupArray unassociatedTrackGroups; /** + * @param rendererTrackTypes The track type supported by each renderer. * @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 RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer. - * @param formatSupport The result of {@link RendererCapabilities#supportsFormat(Format)} for - * each track, indexed by renderer index, group index and track index (in that order). + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} 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) { + /* package */ TrackInfo(int[] rendererTrackTypes, TrackGroupArray[] trackGroups, + TrackSelection[] trackSelections, int[] mixedMimeTypeAdaptiveSupport, + int[][][] formatSupport, TrackGroupArray unassociatedTrackGroups) { + this.rendererTrackTypes = rendererTrackTypes; this.trackGroups = trackGroups; this.trackSelections = trackSelections; this.formatSupport = formatSupport; @@ -586,6 +591,25 @@ public abstract class MappingTrackSelector extends TrackSelector { return unassociatedTrackGroups; } + /** + * Returns true if tracks of the specified type exist and have been associated with renderers, + * but are all unplayable. Returns false in all other cases. + * + * @param trackType The track type. + * @return True if tracks of the specified type exist, if at least one renderer exists that + * handles tracks of the specified type, and if all of the tracks if the specified type are + * unplayable. False in all other cases. + */ + public boolean hasOnlyUnplayableTracks(int trackType) { + int rendererSupport = TrackInfo.RENDERER_SUPPORT_NO_TRACKS; + for (int i = 0; i < rendererCount; i++) { + if (rendererTrackTypes[i] == trackType) { + rendererSupport = Math.max(rendererSupport, getRendererSupport(i)); + } + } + return rendererSupport == RENDERER_SUPPORT_UNPLAYABLE_TRACKS; + } + } } diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/KeyCompatibleMediaController.java b/library/src/main/java/com/google/android/exoplayer2/ui/KeyCompatibleMediaController.java new file mode 100644 index 0000000000..a9e3515178 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/ui/KeyCompatibleMediaController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 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.exoplayer2.ui; + +import android.content.Context; +import android.view.KeyEvent; +import android.widget.MediaController; + +/** + * An extension of {@link MediaController} with enhanced support for D-pad and media keys. + */ +public class KeyCompatibleMediaController extends MediaController { + + private MediaController.MediaPlayerControl playerControl; + + public KeyCompatibleMediaController(Context context) { + super(context); + } + + @Override + public void setMediaPlayer(MediaController.MediaPlayerControl playerControl) { + super.setMediaPlayer(playerControl); + this.playerControl = playerControl; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + if (playerControl.canSeekForward() && (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD + || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + playerControl.seekTo(playerControl.getCurrentPosition() + 15000); // milliseconds + show(); + } + return true; + } else if (playerControl.canSeekBackward() && (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND + || keyCode == KeyEvent.KEYCODE_DPAD_LEFT)) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + playerControl.seekTo(playerControl.getCurrentPosition() - 5000); // milliseconds + show(); + } + return true; + } + return super.dispatchKeyEvent(event); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/MediaControllerPrevNextClickListener.java b/library/src/main/java/com/google/android/exoplayer2/ui/MediaControllerPrevNextClickListener.java new file mode 100644 index 0000000000..1fbb1fcc20 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/ui/MediaControllerPrevNextClickListener.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 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.exoplayer2.ui; + +import com.google.android.exoplayer2.ExoPlayer; + +import android.view.View; +import android.view.View.OnClickListener; + +/** + * An {@link OnClickListener} that can be passed to + * {@link android.widget.MediaController#setPrevNextListeners(OnClickListener, OnClickListener)} to + * make the controller's "previous" and "next" buttons visible and seek to the previous and next + * periods in the timeline of the media being played. + */ +public class MediaControllerPrevNextClickListener implements OnClickListener { + + /** + * If a previous button is clicked the player is seeked to the start of the previous period if the + * playback position in the current period is less than or equal to this constant (and if a + * previous period exists). Else the player is seeked to the start of the current period. + */ + private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS_PERIOD = 3000; + + private final ExoPlayer player; + private final boolean isNext; + + /** + * @param player The player to operate on. + * @param isNext True if this instance if for the "next" button. False for "previous". + */ + public MediaControllerPrevNextClickListener(ExoPlayer player, boolean isNext) { + this.player = player; + this.isNext = isNext; + } + + @Override + public void onClick(View v) { + int currentPeriodIndex = player.getCurrentPeriodIndex(); + if (isNext) { + if (currentPeriodIndex < player.getCurrentTimeline().getPeriodCount() - 1) { + player.seekTo(currentPeriodIndex + 1, 0); + } + } else { + if (currentPeriodIndex > 0 + && player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS_PERIOD) { + player.seekTo(currentPeriodIndex - 1, 0); + } else { + player.seekTo(currentPeriodIndex, 0); + } + } + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java index 1bd34f9445..751f4855c1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java @@ -17,13 +17,17 @@ package com.google.android.exoplayer2.ui; import com.google.android.exoplayer2.text.CaptionStyleCompat; import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.text.TextRenderer; +import com.google.android.exoplayer2.util.Util; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; +import android.view.accessibility.CaptioningManager; import java.util.ArrayList; import java.util.List; @@ -31,7 +35,7 @@ import java.util.List; /** * A view for displaying subtitle {@link Cue}s. */ -public final class SubtitleView extends View { +public final class SubtitleView extends View implements TextRenderer.Output { /** * The default fractional text size. @@ -75,6 +79,11 @@ public final class SubtitleView extends View { bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION; } + @Override + public void onCues(List cues) { + setCues(cues); + } + /** * Sets the cues to be displayed by the view. * @@ -113,6 +122,15 @@ public final class SubtitleView extends View { setTextSize(ABSOLUTE, TypedValue.applyDimension(unit, size, resources.getDisplayMetrics())); } + /** + * Sets the text size to one derived from {@link CaptioningManager#getFontScale()}, or to a + * default size on API level 19 and earlier. + */ + public void setUserDefaultTextSize() { + float fontScale = Util.SDK_INT >= 19 ? getUserCaptionFontScaleV19() : 1f; + setFractionalTextSize(DEFAULT_TEXT_SIZE_FRACTION * fontScale); + } + /** * Sets the text size to be a fraction of the view's remaining height after its top and bottom * padding have been subtracted. @@ -162,6 +180,14 @@ public final class SubtitleView extends View { invalidate(); } + /** + * Sets the caption style to be equivalent to the one returned by + * {@link CaptioningManager#getUserStyle()}, or to a default style on API level 19 and earlier. + */ + public void setUserDefaultStyle() { + setStyle(Util.SDK_INT >= 19 ? getUserCaptionStyleV19() : CaptionStyleCompat.DEFAULT); + } + /** * Sets the caption style. * @@ -223,4 +249,18 @@ public final class SubtitleView extends View { } } + @TargetApi(19) + private float getUserCaptionFontScaleV19() { + CaptioningManager captioningManager = + (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); + return captioningManager.getFontScale(); + } + + @TargetApi(19) + private CaptionStyleCompat getUserCaptionStyleV19() { + CaptioningManager captioningManager = + (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); + return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); + } + } diff --git a/library/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/src/main/java/com/google/android/exoplayer2/util/Util.java index 3a6a21148c..01629d78b1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -20,8 +20,12 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; +import android.Manifest.permission; +import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Build; @@ -129,6 +133,31 @@ public final class Util { return outputStream.toByteArray(); } + /** + * Checks whether it's necessary to request the {@link permission#READ_EXTERNAL_STORAGE} + * permission read the specified {@link Uri}s, requesting the permission if necessary. + * + * @param uris {@link Uri}s that may require {@link permission#READ_EXTERNAL_STORAGE} to read. + * @return Whether a permission request was made. + */ + @TargetApi(23) + public static boolean maybeRequestReadExternalStoragePermission(Activity activity, Uri... uris) { + if (Util.SDK_INT < 23) { + return false; + } + for (Uri uri : uris) { + if (Util.isLocalFileUri(uri)) { + if (activity.checkSelfPermission(permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0); + return true; + } + break; + } + } + return false; + } + /** * Returns true if the URI is a path to a local file or a reference to a local file. * diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java index 043525026e..cde2c06aa1 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java @@ -302,7 +302,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen MappingTrackSelector trackSelector, DrmSessionManager drmSessionManager) { SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector, new DefaultLoadControl(), drmSessionManager); - player.setSurface(surface); + player.setVideoSurface(surface); return player; }