| /* |
| * 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.example.android.tvleanback.ui; |
| |
| import android.app.Activity; |
| import android.content.Intent; |
| import android.graphics.Bitmap; |
| import android.media.MediaMetadata; |
| import android.media.MediaPlayer; |
| import android.media.session.MediaSession; |
| import android.media.session.PlaybackState; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.util.DisplayMetrics; |
| import android.util.Log; |
| import android.widget.FrameLayout; |
| import android.widget.VideoView; |
| |
| import com.bumptech.glide.Glide; |
| import com.bumptech.glide.request.animation.GlideAnimation; |
| import com.bumptech.glide.request.target.SimpleTarget; |
| import com.example.android.tvleanback.R; |
| import com.example.android.tvleanback.model.Movie; |
| |
| /** |
| * PlaybackOverlayActivity for video playback that loads PlaybackOverlayFragment |
| */ |
| public class PlaybackOverlayActivity extends Activity implements |
| PlaybackOverlayFragment.OnPlayPauseClickedListener { |
| private static final String TAG = "PlaybackOverlayActivity"; |
| |
| private static final double MEDIA_HEIGHT = 0.95; |
| private static final double MEDIA_WIDTH = 0.95; |
| private static final double MEDIA_TOP_MARGIN = 0.025; |
| private static final double MEDIA_RIGHT_MARGIN = 0.025; |
| private static final double MEDIA_BOTTOM_MARGIN = 0.025; |
| private static final double MEDIA_LEFT_MARGIN = 0.025; |
| private VideoView mVideoView; |
| private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE; |
| private MediaSession mSession; |
| |
| /** |
| * Called when the activity is first created. |
| */ |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setContentView(R.layout.playback_controls); |
| loadViews(); |
| //Example for handling resizing view for overscan |
| //overScan(); |
| |
| mSession = new MediaSession (this, "LeanbackSampleApp"); |
| mSession.setCallback(new MediaSessionCallback()); |
| mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | |
| MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); |
| |
| mSession.setActive(true); |
| |
| } |
| |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| mVideoView.suspend(); |
| } |
| |
| /** |
| * Implementation of OnPlayPauseClickedListener |
| */ |
| public void onFragmentPlayPause(Movie movie, int position, Boolean playPause) { |
| mVideoView.setVideoPath(movie.getVideoUrl()); |
| |
| if (position == 0 || mPlaybackState == LeanbackPlaybackState.IDLE) { |
| setupCallbacks(); |
| mPlaybackState = LeanbackPlaybackState.IDLE; |
| } |
| |
| if (playPause && mPlaybackState != LeanbackPlaybackState.PLAYING) { |
| mPlaybackState = LeanbackPlaybackState.PLAYING; |
| if (position > 0) { |
| mVideoView.seekTo(position); |
| mVideoView.start(); |
| } |
| } else { |
| mPlaybackState = LeanbackPlaybackState.PAUSED; |
| mVideoView.pause(); |
| } |
| updatePlaybackState(position); |
| updateMetadata(movie); |
| } |
| |
| /** |
| * Implementation of OnPlayPauseClickedListener |
| */ |
| public void onFragmentFfwRwd(Movie movie, int position) { |
| mVideoView.setVideoPath(movie.getVideoUrl()); |
| |
| Log.d(TAG, "seek current time: " + position); |
| if (mPlaybackState == LeanbackPlaybackState.PLAYING) { |
| if (position > 0) { |
| mVideoView.seekTo(position); |
| mVideoView.start(); |
| } |
| } |
| } |
| |
| private void updatePlaybackState(int position) { |
| PlaybackState.Builder stateBuilder = new PlaybackState.Builder() |
| .setActions(getAvailableActions()); |
| int state = PlaybackState.STATE_PLAYING; |
| if (mPlaybackState == LeanbackPlaybackState.PAUSED) { |
| state = PlaybackState.STATE_PAUSED; |
| } |
| stateBuilder.setState(state, position, 1.0f); |
| mSession.setPlaybackState(stateBuilder.build()); |
| } |
| |
| private long getAvailableActions() { |
| long actions = PlaybackState.ACTION_PLAY | |
| PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | |
| PlaybackState.ACTION_PLAY_FROM_SEARCH; |
| |
| if (mPlaybackState == LeanbackPlaybackState.PLAYING) { |
| actions |= PlaybackState.ACTION_PAUSE; |
| } |
| |
| return actions; |
| } |
| |
| private void updateMetadata(final Movie movie) { |
| final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(); |
| |
| String title = movie.getTitle().replace("_", " -"); |
| |
| metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title); |
| metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, |
| movie.getDescription()); |
| metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, |
| movie.getCardImageUrl()); |
| |
| // And at minimum the title and artist for legacy support |
| metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, title); |
| metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, movie.getStudio()); |
| |
| Glide.with(this) |
| .load(Uri.parse(movie.getCardImageUrl())) |
| .asBitmap() |
| .into(new SimpleTarget<Bitmap>(500, 500) { |
| @Override |
| public void onResourceReady(Bitmap bitmap, GlideAnimation anim) { |
| metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap); |
| mSession.setMetadata(metadataBuilder.build()); |
| } |
| }); |
| } |
| |
| private void loadViews() { |
| mVideoView = (VideoView) findViewById(R.id.videoView); |
| } |
| |
| /** |
| * Example for handling resizing content for overscan. Typically you won't need to resize which |
| * is why overScan(); is commented out. |
| */ |
| private void overScan() { |
| DisplayMetrics metrics = new DisplayMetrics(); |
| getWindowManager().getDefaultDisplay().getMetrics(metrics); |
| int w = (int) (metrics.widthPixels * MEDIA_WIDTH); |
| int h = (int) (metrics.heightPixels * MEDIA_HEIGHT); |
| int marginLeft = (int) (metrics.widthPixels * MEDIA_LEFT_MARGIN); |
| int marginTop = (int) (metrics.heightPixels * MEDIA_TOP_MARGIN); |
| int marginRight = (int) (metrics.widthPixels * MEDIA_RIGHT_MARGIN); |
| int marginBottom = (int) (metrics.heightPixels * MEDIA_BOTTOM_MARGIN); |
| FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h); |
| lp.setMargins(marginLeft, marginTop, marginRight, marginBottom); |
| mVideoView.setLayoutParams(lp); |
| } |
| |
| private void setupCallbacks() { |
| |
| mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { |
| |
| @Override |
| public boolean onError(MediaPlayer mp, int what, int 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_inaccessible); |
| } else { |
| msg = getString(R.string.video_error_unknown_error); |
| } |
| mVideoView.stopPlayback(); |
| mPlaybackState = LeanbackPlaybackState.IDLE; |
| return false; |
| } |
| }); |
| |
| |
| mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { |
| @Override |
| public void onPrepared(MediaPlayer mp) { |
| if (mPlaybackState == LeanbackPlaybackState.PLAYING) { |
| mVideoView.start(); |
| } |
| } |
| }); |
| |
| |
| mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { |
| @Override |
| public void onCompletion(MediaPlayer mp) { |
| mPlaybackState = LeanbackPlaybackState.IDLE; |
| } |
| }); |
| |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| if (mVideoView.isPlaying()) { |
| if (!requestVisibleBehind(true)) { |
| // Try to play behind launcher, but if it fails, stop playback. |
| stopPlayback(); |
| } |
| } else { |
| requestVisibleBehind(false); |
| } |
| } |
| |
| @Override |
| protected void onStop() { |
| super.onStop(); |
| mSession.release(); |
| } |
| |
| @Override |
| public void onVisibleBehindCanceled() { |
| super.onVisibleBehindCanceled(); |
| stopPlayback(); |
| } |
| |
| private void stopPlayback() { |
| if (mVideoView != null) { |
| mVideoView.stopPlayback(); |
| } |
| } |
| |
| @Override |
| public boolean onSearchRequested() { |
| startActivity(new Intent(this, SearchActivity.class)); |
| return true; |
| } |
| |
| /* |
| * List of various states that we can be in |
| */ |
| public static enum LeanbackPlaybackState { |
| PLAYING, PAUSED, BUFFERING, IDLE; |
| } |
| |
| private class MediaSessionCallback extends MediaSession.Callback { |
| } |
| } |