/* * Copyright 2014 Google Inc. All rights reserved. * * 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.android.example.leanback; import android.app.Activity; import android.media.MediaCodec; import android.media.MediaCodec.CryptoException; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.View; import android.widget.MediaController; import android.widget.Toast; import com.android.example.leanback.data.Video; import com.android.example.leanback.fastlane.PlaybackOverlayFragment; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.ExoPlayer; import com.google.android.exoplayer.FrameworkSampleSource; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.VideoSurfaceView; import com.google.android.exoplayer.util.PlayerControl; /** * An activity that plays media using {@link com.google.android.exoplayer.ExoPlayer}. */ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener, PlaybackOverlayFragment.OnPlayPauseClickedListener { public static final int RENDERER_COUNT = 2; private static final String TAG = PlayerActivity.class.getSimpleName(); private Video mVideo; private MediaController mediaController; private View shutterView; private VideoSurfaceView surfaceView; private ExoPlayer player; private MediaCodecVideoTrackRenderer videoRenderer; private boolean autoPlay = true; private PlaybackOverlayFragment mPlaybackOverlayFragment; private boolean mIsOnTv; private PlayerControl playerControl; private int mPlayerPosition; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate()"); setContentView(R.layout.activity_player); mVideo = (Video)getIntent().getSerializableExtra(Video.INTENT_EXTRA_VIDEO); // We will use the PlaybackOverlayFragment when running on TV. mIsOnTv = MyUtil.isRunningInTvMode(this); if (mIsOnTv) { // On TV we will use the resource in layout-televsion mPlaybackOverlayFragment = (PlaybackOverlayFragment) getFragmentManager().findFragmentById(R.id.playback_controls_fragment); } else { shutterView = findViewById(R.id.shutter); View root = findViewById(R.id.root); mediaController = new MediaController(this); //overscan safe on 1980 * 1080 TV mediaController.setPadding(48, 27, 48, 27); mediaController.setAnchorView(root); } surfaceView = (VideoSurfaceView) findViewById(R.id.surface_view); surfaceView.getHolder().addCallback(this); preparePlayer(); } private void preparePlayer() { Log.d(TAG, "preparePlayer()"); SampleSource sampleSource = new FrameworkSampleSource(this, Uri.parse(mVideo.getContentUrl()), /* headers */ null, RENDERER_COUNT); // Build the track renderers videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource); // Setup the player player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000); player.addListener(this); player.prepare(videoRenderer, audioRenderer); if (mIsOnTv) { // This PlayerControl must stay in sync with PlaybackOverlayFragment. // We created methods such as PlaybackOverlayFragment.pressPlay() to request // that the fragment change the playback state. When the fragment receives a playback // request, it updates the UI and then calls a method in this activity according to // PlaybackOverlayFragment.OnPlayPauseClickedListener. playerControl = new PlayerControl(player); } else { // Build the player controls mediaController.setMediaPlayer(new PlayerControl(player)); mediaController.setEnabled(true); } maybeStartPlayback(); } private void releasePlayer() { Log.d(TAG, "releasePlayer()"); if (player != null) { player.release(); player = null; } videoRenderer = null; } private void reloadVideo() { Log.d(TAG, "reloadVideo()"); releasePlayer(); preparePlayer(); } @Override public void onResume() { super.onResume(); } @Override public void onPause() { if (mIsOnTv) { if (playerControl != null && playerControl.isPlaying()) { // Allows video to play behind the launcher screen when the user preses // the Home button. // This is only available on API level 21+, and we are assuming // all TV devices on running Android 21+. requestVisibleBehind(true); } } super.onPause(); } @Override public void onVisibleBehindCanceled() { super.onVisibleBehindCanceled(); releasePlayer(); if (!mIsOnTv) { shutterView.setVisibility(View.VISIBLE); } } @Override protected void onStop() { super.onStop(); releasePlayer(); if (!mIsOnTv) { shutterView.setVisibility(View.VISIBLE); } } private void maybeStartPlayback() { Log.d(TAG, "maybeStartPlayback"); Surface surface = surfaceView.getHolder().getSurface(); if (videoRenderer == null || surface == null || !surface.isValid()) { // We're not ready yet. return; } player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); if (autoPlay) { if (mIsOnTv) { // This will update the player controls and the activity will receive the callback // OnPlayPauseClickedListener.onFragmentPlayPause(Video, int, Boolean) mPlaybackOverlayFragment.pressPlay(); } else { player.setPlayWhenReady(true); } autoPlay = false; } } private void onError(Exception e) { Log.e(TAG, "Playback failed", e); Toast.makeText(this, "Playback failed", Toast.LENGTH_SHORT).show(); finish(); } // ExoPlayer.Listener implementation @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { Log.d(TAG, "onPlayerStateChanged(playbackState=" + playbackState + ")"); if (!mIsOnTv && playbackState == ExoPlayer.STATE_READY) { shutterView.setVisibility(View.GONE); mediaController.show(0); } } @Override public void onPlayWhenReadyCommitted() { // Do nothing. } @Override public void onPlayerError(ExoPlaybackException e) { onError(e); } @Override public void onVideoSizeChanged(int width, int height) { surfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (float) width / height); } @Override public void onDrawnToSurface(Surface surface) { if (!mIsOnTv) { shutterView.setVisibility(View.GONE); mediaController.show(0); } } @Override public void onDroppedFrames(int count, long elapsed) { Log.d(TAG, "Dropped frames: " + count); } @Override public void onDecoderInitializationError(DecoderInitializationException e) { // This is for informational purposes only. Do nothing. } @Override public void onCryptoError(CryptoException e) { // This is for informational purposes only. Do nothing. } // SurfaceHolder.Callback implementation @Override public void surfaceCreated(SurfaceHolder holder) { Log.d(TAG, "maybeStartPlayback"); maybeStartPlayback(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // Do nothing. } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (videoRenderer != null) { player.blockingSendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null); } } /** * Implementation of PlaybackOverlayFragment.OnPlayPauseClickedListener */ public void onFragmentPlayPause(Video video, int position, Boolean playPause) { Log.d(TAG, "onFragmentPlayPause()"); if (mVideo == null || !mVideo.getTitle().equals(video.getTitle())) { // When the user selects another video from the PlaybackOverlayFragment, we need // to recognize that the video has changed and reload the player. mVideo = video; reloadVideo(); } if (mVideo == null) { return; } mPlayerPosition = position; // seekTo(), start(), pause() are ONLY be called in response to the fragment callbacks playerControl.seekTo(mPlayerPosition); if (playPause) { Log.d(TAG, "Play"); playerControl.start(); } else { Log.d(TAG, "Pause"); playerControl.pause(); } } /** * Implementation of PlaybackOverlayFragment.OnPlayPauseClickedListener */ public void onFragmentFfwRwd(Video video, int position) { Log.d(TAG, "onFragmentFfwRwd() seek to " + position); if (mVideo == null || !mVideo.getTitle().equals(video.getTitle())) { // When the user selects another video from the PlaybackOverlayFragment, we need // to recognize that the video has changed and reload the player. mVideo = video; reloadVideo(); } if (mVideo == null) { return; } mPlayerPosition = position; // seekTo() is ONLY be called in response to the fragment callbacks playerControl.seekTo(mPlayerPosition); } }