1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package com.example.android.tvleanback.ui; 15 16 import android.app.Activity; 17 import android.content.Intent; 18 import android.graphics.Bitmap; 19 import android.media.MediaMetadata; 20 import android.media.MediaPlayer; 21 import android.media.session.MediaSession; 22 import android.media.session.PlaybackState; 23 import android.net.Uri; 24 import android.os.Bundle; 25 import android.util.DisplayMetrics; 26 import android.util.Log; 27 import android.widget.FrameLayout; 28 import android.widget.VideoView; 29 30 import com.bumptech.glide.Glide; 31 import com.bumptech.glide.request.animation.GlideAnimation; 32 import com.bumptech.glide.request.target.SimpleTarget; 33 import com.example.android.tvleanback.R; 34 import com.example.android.tvleanback.model.Movie; 35 36 /** 37 * PlaybackOverlayActivity for video playback that loads PlaybackOverlayFragment 38 */ 39 public class PlaybackOverlayActivity extends Activity implements 40 PlaybackOverlayFragment.OnPlayPauseClickedListener { 41 private static final String TAG = "PlaybackOverlayActivity"; 42 43 private static final double MEDIA_HEIGHT = 0.95; 44 private static final double MEDIA_WIDTH = 0.95; 45 private static final double MEDIA_TOP_MARGIN = 0.025; 46 private static final double MEDIA_RIGHT_MARGIN = 0.025; 47 private static final double MEDIA_BOTTOM_MARGIN = 0.025; 48 private static final double MEDIA_LEFT_MARGIN = 0.025; 49 private VideoView mVideoView; 50 private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE; 51 private MediaSession mSession; 52 53 /** 54 * Called when the activity is first created. 55 */ 56 @Override onCreate(Bundle savedInstanceState)57 public void onCreate(Bundle savedInstanceState) { 58 super.onCreate(savedInstanceState); 59 setContentView(R.layout.playback_controls); 60 loadViews(); 61 //Example for handling resizing view for overscan 62 //overScan(); 63 64 mSession = new MediaSession (this, "LeanbackSampleApp"); 65 mSession.setCallback(new MediaSessionCallback()); 66 mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | 67 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 68 69 mSession.setActive(true); 70 71 } 72 73 @Override onDestroy()74 public void onDestroy() { 75 super.onDestroy(); 76 mVideoView.suspend(); 77 } 78 79 /** 80 * Implementation of OnPlayPauseClickedListener 81 */ onFragmentPlayPause(Movie movie, int position, Boolean playPause)82 public void onFragmentPlayPause(Movie movie, int position, Boolean playPause) { 83 mVideoView.setVideoPath(movie.getVideoUrl()); 84 85 if (position == 0 || mPlaybackState == LeanbackPlaybackState.IDLE) { 86 setupCallbacks(); 87 mPlaybackState = LeanbackPlaybackState.IDLE; 88 } 89 90 if (playPause && mPlaybackState != LeanbackPlaybackState.PLAYING) { 91 mPlaybackState = LeanbackPlaybackState.PLAYING; 92 if (position > 0) { 93 mVideoView.seekTo(position); 94 mVideoView.start(); 95 } 96 } else { 97 mPlaybackState = LeanbackPlaybackState.PAUSED; 98 mVideoView.pause(); 99 } 100 updatePlaybackState(position); 101 updateMetadata(movie); 102 } 103 104 /** 105 * Implementation of OnPlayPauseClickedListener 106 */ onFragmentFfwRwd(Movie movie, int position)107 public void onFragmentFfwRwd(Movie movie, int position) { 108 mVideoView.setVideoPath(movie.getVideoUrl()); 109 110 Log.d(TAG, "seek current time: " + position); 111 if (mPlaybackState == LeanbackPlaybackState.PLAYING) { 112 if (position > 0) { 113 mVideoView.seekTo(position); 114 mVideoView.start(); 115 } 116 } 117 } 118 updatePlaybackState(int position)119 private void updatePlaybackState(int position) { 120 PlaybackState.Builder stateBuilder = new PlaybackState.Builder() 121 .setActions(getAvailableActions()); 122 int state = PlaybackState.STATE_PLAYING; 123 if (mPlaybackState == LeanbackPlaybackState.PAUSED) { 124 state = PlaybackState.STATE_PAUSED; 125 } 126 stateBuilder.setState(state, position, 1.0f); 127 mSession.setPlaybackState(stateBuilder.build()); 128 } 129 getAvailableActions()130 private long getAvailableActions() { 131 long actions = PlaybackState.ACTION_PLAY | 132 PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | 133 PlaybackState.ACTION_PLAY_FROM_SEARCH; 134 135 if (mPlaybackState == LeanbackPlaybackState.PLAYING) { 136 actions |= PlaybackState.ACTION_PAUSE; 137 } 138 139 return actions; 140 } 141 updateMetadata(final Movie movie)142 private void updateMetadata(final Movie movie) { 143 final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(); 144 145 String title = movie.getTitle().replace("_", " -"); 146 147 metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title); 148 metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, 149 movie.getDescription()); 150 metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, 151 movie.getCardImageUrl()); 152 153 // And at minimum the title and artist for legacy support 154 metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, title); 155 metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, movie.getStudio()); 156 157 Glide.with(this) 158 .load(Uri.parse(movie.getCardImageUrl())) 159 .asBitmap() 160 .into(new SimpleTarget<Bitmap>(500, 500) { 161 @Override 162 public void onResourceReady(Bitmap bitmap, GlideAnimation anim) { 163 metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap); 164 mSession.setMetadata(metadataBuilder.build()); 165 } 166 }); 167 } 168 loadViews()169 private void loadViews() { 170 mVideoView = (VideoView) findViewById(R.id.videoView); 171 } 172 173 /** 174 * Example for handling resizing content for overscan. Typically you won't need to resize which 175 * is why overScan(); is commented out. 176 */ overScan()177 private void overScan() { 178 DisplayMetrics metrics = new DisplayMetrics(); 179 getWindowManager().getDefaultDisplay().getMetrics(metrics); 180 int w = (int) (metrics.widthPixels * MEDIA_WIDTH); 181 int h = (int) (metrics.heightPixels * MEDIA_HEIGHT); 182 int marginLeft = (int) (metrics.widthPixels * MEDIA_LEFT_MARGIN); 183 int marginTop = (int) (metrics.heightPixels * MEDIA_TOP_MARGIN); 184 int marginRight = (int) (metrics.widthPixels * MEDIA_RIGHT_MARGIN); 185 int marginBottom = (int) (metrics.heightPixels * MEDIA_BOTTOM_MARGIN); 186 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h); 187 lp.setMargins(marginLeft, marginTop, marginRight, marginBottom); 188 mVideoView.setLayoutParams(lp); 189 } 190 setupCallbacks()191 private void setupCallbacks() { 192 193 mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { 194 195 @Override 196 public boolean onError(MediaPlayer mp, int what, int extra) { 197 String msg = ""; 198 if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) { 199 msg = getString(R.string.video_error_media_load_timeout); 200 } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) { 201 msg = getString(R.string.video_error_server_inaccessible); 202 } else { 203 msg = getString(R.string.video_error_unknown_error); 204 } 205 mVideoView.stopPlayback(); 206 mPlaybackState = LeanbackPlaybackState.IDLE; 207 return false; 208 } 209 }); 210 211 212 mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 213 @Override 214 public void onPrepared(MediaPlayer mp) { 215 if (mPlaybackState == LeanbackPlaybackState.PLAYING) { 216 mVideoView.start(); 217 } 218 } 219 }); 220 221 222 mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 223 @Override 224 public void onCompletion(MediaPlayer mp) { 225 mPlaybackState = LeanbackPlaybackState.IDLE; 226 } 227 }); 228 229 } 230 231 @Override onResume()232 public void onResume() { 233 super.onResume(); 234 } 235 236 @Override onPause()237 public void onPause() { 238 super.onPause(); 239 if (mVideoView.isPlaying()) { 240 if (!requestVisibleBehind(true)) { 241 // Try to play behind launcher, but if it fails, stop playback. 242 stopPlayback(); 243 } 244 } else { 245 requestVisibleBehind(false); 246 } 247 } 248 249 @Override onStop()250 protected void onStop() { 251 super.onStop(); 252 mSession.release(); 253 } 254 255 @Override onVisibleBehindCanceled()256 public void onVisibleBehindCanceled() { 257 super.onVisibleBehindCanceled(); 258 stopPlayback(); 259 } 260 stopPlayback()261 private void stopPlayback() { 262 if (mVideoView != null) { 263 mVideoView.stopPlayback(); 264 } 265 } 266 267 @Override onSearchRequested()268 public boolean onSearchRequested() { 269 startActivity(new Intent(this, SearchActivity.class)); 270 return true; 271 } 272 273 /* 274 * List of various states that we can be in 275 */ 276 public static enum LeanbackPlaybackState { 277 PLAYING, PAUSED, BUFFERING, IDLE; 278 } 279 280 private class MediaSessionCallback extends MediaSession.Callback { 281 } 282 } 283