package com.blundell.chromecastexample.app.cast; import android.annotation.SuppressLint; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.ActionBarActivity; import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.view.*; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.widget.*; import android.widget.SeekBar.OnSeekBarChangeListener; import com.blundell.chromecastexample.app.R; import com.blundell.chromecastexample.app.CastApplication; import com.google.android.gms.cast.ApplicationMetadata; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaMetadata; import com.google.sample.castcompanionlibrary.cast.VideoCastManager; import com.google.sample.castcompanionlibrary.cast.callbacks.VideoCastConsumerImpl; import com.google.sample.castcompanionlibrary.widgets.MiniController; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Timer; import java.util.TimerTask; import static com.google.sample.castcompanionlibrary.utils.Utils.*; public class LocalPlayerActivity extends ActionBarActivity { private static final String TAG = "LocalPlayerActivity"; private static final float ASPECT_RATIO = 72f / 128; private VideoView mVideoView; private TextView mTitleView; private TextView mDescriptionView; private TextView mStartText; private TextView mEndText; private SeekBar mSeekbar; private ImageView mPlayPause; private ProgressBar mLoading; private View mControllers; private View mContainer; private ImageView mCoverArt; private VideoCastManager mCastManager; private Timer mSeekbarTimer; private Timer mControllersTimer; private PlaybackLocation mLocation; private PlaybackState mPlaybackState; private final Handler mHandler = new Handler(); private MediaInfo mSelectedMedia; private boolean mControlersVisible; private int mDuration; private MiniController mMini; protected MediaInfo mRemoteMediaInformation; private VideoCastConsumerImpl mCastConsumer; private TextView mAuthorView; /* * indicates whether we are doing a local or a remote playback */ public static enum PlaybackLocation { LOCAL, REMOTE } /* * List of various states that we can be in */ public static enum PlaybackState { PLAYING, PAUSED, BUFFERING, IDLE } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.player_activity); loadViews(); mCastManager = CastApplication.getCastManager(this); setupActionBar(); setupControlsCallbacks(); setupMiniController(); setupCastListener(); // see what we need to play and were Bundle b = getIntent().getExtras(); if (null != b) { mSelectedMedia = toMediaInfo(getIntent().getBundleExtra("media")); boolean mShouldStartPlayback = b.getBoolean("shouldStart"); int startPosition = b.getInt("startPosition", 0); mVideoView.setVideoURI(Uri.parse(mSelectedMedia.getContentId())); Log.d(TAG, "Setting url of the VideoView to: " + mSelectedMedia.getContentId()); if (mShouldStartPlayback) { // this will be the case only if we are coming from the by disconnecting from a device mPlaybackState = PlaybackState.PLAYING; updatePlaybackLocation(PlaybackLocation.LOCAL); updatePlayButton(mPlaybackState); if (startPosition > 0) { mVideoView.seekTo(startPosition); } mVideoView.start(); startControllersTimer(); } else { // we should load the video but pause it // and show the album art. if (mCastManager.isConnected()) { updatePlaybackLocation(PlaybackLocation.REMOTE); } else { updatePlaybackLocation(PlaybackLocation.LOCAL); } mPlaybackState = PlaybackState.PAUSED; updatePlayButton(mPlaybackState); } } if (null != mTitleView) { updateMetadata(true); } } private void setupCastListener() { mCastConsumer = new VideoCastConsumerImpl() { @Override public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { Log.d(TAG, "onApplicationLaunched() is reached"); if (null != mSelectedMedia) { if (mPlaybackState == PlaybackState.PLAYING) { mVideoView.pause(); try { loadRemoteMedia(mSeekbar.getProgress(), true); finish(); } catch (Exception e) { Utils.handleException(LocalPlayerActivity.this, e); } } else { updatePlaybackLocation(PlaybackLocation.REMOTE); } } } @Override public void onApplicationDisconnected(int errorCode) { Log.d(TAG, "onApplicationDisconnected() is reached with errorCode: " + errorCode); updatePlaybackLocation(PlaybackLocation.LOCAL); } @Override public void onDisconnected() { Log.d(TAG, "onDisconnected() is reached"); mPlaybackState = PlaybackState.PAUSED; mLocation = PlaybackLocation.LOCAL; } @Override public void onRemoteMediaPlayerMetadataUpdated() { try { mRemoteMediaInformation = mCastManager.getRemoteMediaInformation(); } catch (Exception e) { // silent } } @Override public void onFailed(int resourceId, int statusCode) { } @Override public void onConnectionSuspended(int cause) { Utils.showToast(LocalPlayerActivity.this, R.string.connection_temp_lost); } @Override public void onConnectivityRecovered() { Utils.showToast(LocalPlayerActivity.this, R.string.connection_recovered); } }; } private void setupMiniController() { mMini = (MiniController) findViewById(R.id.miniController1); mCastManager.addMiniController(mMini); } private void updatePlaybackLocation(PlaybackLocation location) { this.mLocation = location; if (location == PlaybackLocation.LOCAL) { if (mPlaybackState == PlaybackState.PLAYING || mPlaybackState == PlaybackState.BUFFERING) { setCoverArtStatus(null); startControllersTimer(); } else { stopControllersTimer(); setCoverArtStatus(getImageUrl(mSelectedMedia, 0)); } getActionBar().setTitle(""); } else { stopControllersTimer(); setCoverArtStatus(getImageUrl(mSelectedMedia, 0)); updateControlersVisibility(true); } } private void play(int position) { startControllersTimer(); switch (mLocation) { case LOCAL: mVideoView.seekTo(position); mVideoView.start(); break; case REMOTE: mPlaybackState = PlaybackState.BUFFERING; updatePlayButton(mPlaybackState); try { mCastManager.play(position); } catch (Exception e) { Utils.handleException(this, e); } break; default: break; } restartTrickplayTimer(); } private void togglePlayback() { stopControllersTimer(); switch (mPlaybackState) { case PAUSED: switch (mLocation) { case LOCAL: mVideoView.start(); mPlaybackState = PlaybackState.PLAYING; startControllersTimer(); restartTrickplayTimer(); updatePlaybackLocation(PlaybackLocation.LOCAL); break; case REMOTE: try { mCastManager.checkConnectivity(); loadRemoteMedia(0, true); finish(); } catch (Exception e) { Utils.handleException(LocalPlayerActivity.this, e); return; } break; default: break; } break; case PLAYING: mPlaybackState = PlaybackState.PAUSED; mVideoView.pause(); break; case IDLE: mVideoView.seekTo(0); mVideoView.start(); mPlaybackState = PlaybackState.PLAYING; restartTrickplayTimer(); break; default: break; } updatePlayButton(mPlaybackState); } private void loadRemoteMedia(int position, boolean autoPlay) { mCastManager.startCastControllerActivity(this, mSelectedMedia, position, autoPlay); } private void setCoverArtStatus(final String url) { if (null != url) { new AsyncTask<Void, Void, Drawable>() { @Override protected Drawable doInBackground(Void... params) { try { return Drawable.createFromStream(((InputStream) new URL(url).getContent()), "name"); } catch (IOException swallow) { Log.e(TAG, "image load fail"); } return null; } @Override protected void onPostExecute(Drawable img) { super.onPostExecute(img); if (mCoverArt != null && img != null) { mCoverArt.setImageDrawable(img); } } }.execute(); mCoverArt.setVisibility(View.VISIBLE); mVideoView.setVisibility(View.INVISIBLE); } else { mCoverArt.setVisibility(View.GONE); mVideoView.setVisibility(View.VISIBLE); } } private void stopTrickplayTimer() { Log.d(TAG, "Stopped TrickPlay Timer"); if (null != mSeekbarTimer) { mSeekbarTimer.cancel(); } } private void restartTrickplayTimer() { stopTrickplayTimer(); mSeekbarTimer = new Timer(); mSeekbarTimer.scheduleAtFixedRate(new UpdateSeekbarTask(), 100, 1000); Log.d(TAG, "Restarted TrickPlay Timer"); } private void stopControllersTimer() { if (null != mControllersTimer) { mControllersTimer.cancel(); } } private void startControllersTimer() { if (null != mControllersTimer) { mControllersTimer.cancel(); } if (mLocation == PlaybackLocation.REMOTE) { return; } mControllersTimer = new Timer(); mControllersTimer.schedule(new HideControllersTask(), 5000); } // should be called from the main thread private void updateControlersVisibility(boolean show) { if (show) { getActionBar().show(); mControllers.setVisibility(View.VISIBLE); } else { getActionBar().hide(); mControllers.setVisibility(View.INVISIBLE); } } @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause() was called"); if (mLocation == PlaybackLocation.LOCAL) { if (null != mSeekbarTimer) { mSeekbarTimer.cancel(); mSeekbarTimer = null; } if (null != mControllersTimer) { mControllersTimer.cancel(); } // since we are playing locally, we need to stop the playback of // video (if user is not watching, pause it!) mVideoView.pause(); mPlaybackState = PlaybackState.PAUSED; updatePlayButton(PlaybackState.PAUSED); } mCastManager.removeVideoCastConsumer(mCastConsumer); mMini.removeOnMiniControllerChangedListener(mCastManager); mCastManager.decrementUiCounter(); } @Override protected void onStop() { Log.d(TAG, "onStop() was called"); super.onStop(); } @Override protected void onDestroy() { Log.d(TAG, "onDestroy() is called"); if (null != mCastManager) { mMini.removeOnMiniControllerChangedListener(mCastManager); mCastConsumer = null; } stopControllersTimer(); stopTrickplayTimer(); super.onDestroy(); } @Override protected void onStart() { Log.d(TAG, "onStart was called"); super.onStart(); } @Override protected void onResume() { Log.d(TAG, "onResume() was called"); mCastManager = CastApplication.getCastManager(this); mCastManager.addVideoCastConsumer(mCastConsumer); mCastManager.incrementUiCounter(); super.onResume(); } private class HideControllersTask extends TimerTask { @Override public void run() { mHandler.post(new Runnable() { @Override public void run() { updateControlersVisibility(false); mControlersVisible = false; } }); } } private class UpdateSeekbarTask extends TimerTask { @Override public void run() { mHandler.post(new Runnable() { @Override public void run() { if (mLocation == PlaybackLocation.LOCAL) { int currentPos = mVideoView.getCurrentPosition(); updateSeekbar(currentPos, mDuration); } } }); } } private void setupControlsCallbacks() { mVideoView.setOnErrorListener(new OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { Log.e(TAG, "OnErrorListener.onError(): VideoView encountered an " + "error, what: " + what + ", extra: " + extra); String msg; if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) { msg = getString(R.string.video_error_media_load_timeout); } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) { msg = getString(R.string.video_error_server_unaccessible); } else { msg = getString(R.string.video_error_unknown_error); } Utils.showErrorDialog(LocalPlayerActivity.this, msg); mVideoView.stopPlayback(); mPlaybackState = PlaybackState.IDLE; return false; } }); mVideoView.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { Log.d(TAG, "onPrepared is reached"); mDuration = mp.getDuration(); mEndText.setText( formatMillis(mDuration)); mSeekbar.setMax(mDuration); restartTrickplayTimer(); } }); mVideoView.setOnCompletionListener(new OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { stopTrickplayTimer(); mPlaybackState = PlaybackState.IDLE; updatePlayButton(PlaybackState.IDLE); } }); mVideoView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (!mControlersVisible) { updateControlersVisibility(true); } startControllersTimer(); return false; } }); mSeekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { if (mPlaybackState == PlaybackState.PLAYING) { play(seekBar.getProgress()); } else { mVideoView.seekTo(seekBar.getProgress()); } startControllersTimer(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { stopTrickplayTimer(); mVideoView.pause(); stopControllersTimer(); } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { mStartText.setText( formatMillis(progress)); } }); mPlayPause.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { togglePlayback(); } }); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mLocation == PlaybackLocation.LOCAL) { return super.onKeyDown(keyCode, event); } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { onVolumeChange(CastApplication.VOLUME_INCREMENT); } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { onVolumeChange(-CastApplication.VOLUME_INCREMENT); } else { return super.onKeyDown(keyCode, event); } return true; } private void onVolumeChange(double volumeIncrement) { if (mCastManager == null) { return; } try { mCastManager.incrementVolume(volumeIncrement); } catch (Exception e) { Log.e(TAG, "onVolumeChange() Failed to change volume", e); } } private void updateSeekbar(int position, int duration) { mSeekbar.setProgress(position); mSeekbar.setMax(duration); mStartText.setText( formatMillis(position)); mEndText.setText(formatMillis(duration)); } private void updatePlayButton(PlaybackState state) { switch (state) { case PLAYING: mLoading.setVisibility(View.INVISIBLE); mPlayPause.setVisibility(View.VISIBLE); mPlayPause.setImageDrawable( getResources().getDrawable(R.drawable.ic_av_pause_dark)); break; case PAUSED: case IDLE: mLoading.setVisibility(View.INVISIBLE); mPlayPause.setVisibility(View.VISIBLE); mPlayPause.setImageDrawable( getResources().getDrawable(R.drawable.ic_av_play_dark)); break; case BUFFERING: mPlayPause.setVisibility(View.INVISIBLE); mLoading.setVisibility(View.VISIBLE); break; default: break; } } @SuppressLint("NewApi") @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); } updateMetadata(false); mContainer.setBackgroundColor(getResources().getColor(R.color.black)); } else { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); getWindow().clearFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } updateMetadata(true); mContainer.setBackgroundColor(getResources().getColor(R.color.white)); } } private void updateMetadata(boolean visible) { if (!visible) { mDescriptionView.setVisibility(View.GONE); mTitleView.setVisibility(View.GONE); mAuthorView.setVisibility(View.GONE); Point mDisplaySize = Utils.getDisplaySize(this); RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(mDisplaySize.x, mDisplaySize.y + getActionBar().getHeight()); lp.addRule(RelativeLayout.CENTER_IN_PARENT); mVideoView.setLayoutParams(lp); mVideoView.invalidate(); } else { MediaMetadata mm = mSelectedMedia.getMetadata(); mDescriptionView.setText(mm.getString(MediaMetadata.KEY_STUDIO)); mTitleView.setText(mm.getString(MediaMetadata.KEY_TITLE)); mAuthorView.setText(mm.getString(MediaMetadata.KEY_SUBTITLE)); mDescriptionView.setVisibility(View.VISIBLE); mTitleView.setVisibility(View.VISIBLE); mAuthorView.setVisibility(View.VISIBLE); Point mDisplaySize = Utils.getDisplaySize(this); RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(mDisplaySize.x, (int) (mDisplaySize.x * ASPECT_RATIO)); lp.addRule(RelativeLayout.ALIGN_PARENT_TOP); mVideoView.setLayoutParams(lp); mVideoView.invalidate(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.spike_main, menu); mCastManager.addMediaRouterButton(menu, R.id.media_route_menu_item); return true; } private void setupActionBar() { getActionBar().setDisplayHomeAsUpEnabled(false); getActionBar().setDisplayUseLogoEnabled(false); getActionBar().setDisplayShowHomeEnabled(false); getActionBar().setDisplayShowTitleEnabled(false); getActionBar().setBackgroundDrawable(getResources().getDrawable(R.drawable.spike_ab_transparent_democastoverlay)); } private void loadViews() { mVideoView = (VideoView) findViewById(R.id.videoView1); mTitleView = (TextView) findViewById(R.id.textView1); mDescriptionView = (TextView) findViewById(R.id.textView2); mDescriptionView.setMovementMethod(new ScrollingMovementMethod()); mAuthorView = (TextView) findViewById(R.id.textView3); mStartText = (TextView) findViewById(R.id.startText); mEndText = (TextView) findViewById(R.id.endText); mSeekbar = (SeekBar) findViewById(R.id.seekBar1); // mVolBar = (SeekBar) findViewById(R.id.seekBar2); mPlayPause = (ImageView) findViewById(R.id.imageView2); mLoading = (ProgressBar) findViewById(R.id.progressBar1); // mVolumeMute = (ImageView) findViewById(R.id.imageView2); mControllers = findViewById(R.id.controllers); mContainer = findViewById(R.id.container); mCoverArt = (ImageView) findViewById(R.id.coverArtView); } }