1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.pictureinpicture; 18 19 import android.app.PictureInPictureParams; 20 import android.content.Intent; 21 import android.content.res.Configuration; 22 import android.os.Bundle; 23 import android.support.v4.media.MediaMetadataCompat; 24 import android.support.v4.media.session.MediaControllerCompat; 25 import android.support.v4.media.session.MediaSessionCompat; 26 import android.support.v4.media.session.PlaybackStateCompat; 27 import android.support.v7.app.AppCompatActivity; 28 import android.util.Rational; 29 import android.view.View; 30 import android.widget.Button; 31 import android.widget.ScrollView; 32 33 import com.example.android.pictureinpicture.widget.MovieView; 34 35 /** 36 * Demonstrates usage of Picture-in-Picture when using {@link 37 * android.support.v4.media.session.MediaSessionCompat}. 38 */ 39 public class MediaSessionPlaybackActivity extends AppCompatActivity { 40 41 private static final String TAG = "MediaSessionPlaybackActivity"; 42 43 public static final long MEDIA_ACTIONS_PLAY_PAUSE = 44 PlaybackStateCompat.ACTION_PLAY 45 | PlaybackStateCompat.ACTION_PAUSE 46 | PlaybackStateCompat.ACTION_PLAY_PAUSE; 47 48 public static final long MEDIA_ACTIONS_ALL = 49 MEDIA_ACTIONS_PLAY_PAUSE 50 | PlaybackStateCompat.ACTION_SKIP_TO_NEXT 51 | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; 52 53 private MediaSessionCompat mSession; 54 55 /** The arguments to be used for Picture-in-Picture mode. */ 56 private final PictureInPictureParams.Builder mPictureInPictureParamsBuilder = 57 new PictureInPictureParams.Builder(); 58 59 /** This shows the video. */ 60 private MovieView mMovieView; 61 62 /** The bottom half of the screen; hidden on landscape */ 63 private ScrollView mScrollView; 64 65 private final View.OnClickListener mOnClickListener = 66 new View.OnClickListener() { 67 @Override 68 public void onClick(View view) { 69 switch (view.getId()) { 70 case R.id.pip: 71 minimize(); 72 break; 73 } 74 } 75 }; 76 77 /** Callbacks from the {@link MovieView} showing the video playback. */ 78 private MovieView.MovieListener mMovieListener = 79 new MovieView.MovieListener() { 80 81 @Override 82 public void onMovieStarted() { 83 // We are playing the video now. Update the media session state and the PiP 84 // window will 85 // update the actions. 86 updatePlaybackState( 87 PlaybackStateCompat.STATE_PLAYING, 88 mMovieView.getCurrentPosition(), 89 mMovieView.getVideoResourceId()); 90 } 91 92 @Override 93 public void onMovieStopped() { 94 // The video stopped or reached its end. Update the media session state and the 95 // PiP window will 96 // update the actions. 97 updatePlaybackState( 98 PlaybackStateCompat.STATE_PAUSED, 99 mMovieView.getCurrentPosition(), 100 mMovieView.getVideoResourceId()); 101 } 102 103 @Override 104 public void onMovieMinimized() { 105 // The MovieView wants us to minimize it. We enter Picture-in-Picture mode now. 106 minimize(); 107 } 108 }; 109 110 @Override 111 protected void onCreate(Bundle savedInstanceState) { 112 super.onCreate(savedInstanceState); 113 setContentView(R.layout.activity_main); 114 115 // View references 116 mMovieView = findViewById(R.id.movie); 117 mScrollView = findViewById(R.id.scroll); 118 Button switchExampleButton = findViewById(R.id.switch_example); 119 switchExampleButton.setText(getString(R.string.switch_custom)); 120 switchExampleButton.setOnClickListener(new SwitchActivityOnClick()); 121 122 // Set up the video; it automatically starts. 123 mMovieView.setMovieListener(mMovieListener); 124 findViewById(R.id.pip).setOnClickListener(mOnClickListener); 125 } 126 127 @Override 128 protected void onStart() { 129 super.onStart(); 130 initializeMediaSession(); 131 } 132 133 private void initializeMediaSession() { 134 mSession = new MediaSessionCompat(this, TAG); 135 mSession.setFlags( 136 MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS 137 | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); 138 mSession.setActive(true); 139 MediaControllerCompat.setMediaController(this, mSession.getController()); 140 141 MediaMetadataCompat metadata = new MediaMetadataCompat.Builder() 142 .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, mMovieView.getTitle()) 143 .build(); 144 mSession.setMetadata(metadata); 145 146 MediaSessionCallback mMediaSessionCallback = new MediaSessionCallback(mMovieView); 147 mSession.setCallback(mMediaSessionCallback); 148 149 int state = 150 mMovieView.isPlaying() 151 ? PlaybackStateCompat.STATE_PLAYING 152 : PlaybackStateCompat.STATE_PAUSED; 153 updatePlaybackState( 154 state, 155 MEDIA_ACTIONS_ALL, 156 mMovieView.getCurrentPosition(), 157 mMovieView.getVideoResourceId()); 158 } 159 160 @Override 161 protected void onStop() { 162 super.onStop(); 163 // On entering Picture-in-Picture mode, onPause is called, but not onStop. 164 // For this reason, this is the place where we should pause the video playback. 165 mMovieView.pause(); 166 mSession.release(); 167 mSession = null; 168 } 169 170 @Override 171 protected void onRestart() { 172 super.onRestart(); 173 if (!isInPictureInPictureMode()) { 174 // Show the video controls so the video can be easily resumed. 175 mMovieView.showControls(); 176 } 177 } 178 179 @Override 180 public void onConfigurationChanged(Configuration newConfig) { 181 super.onConfigurationChanged(newConfig); 182 adjustFullScreen(newConfig); 183 } 184 185 @Override 186 public void onWindowFocusChanged(boolean hasFocus) { 187 super.onWindowFocusChanged(hasFocus); 188 if (hasFocus) { 189 adjustFullScreen(getResources().getConfiguration()); 190 } 191 } 192 193 @Override 194 public void onPictureInPictureModeChanged( 195 boolean isInPictureInPictureMode, Configuration configuration) { 196 super.onPictureInPictureModeChanged(isInPictureInPictureMode, configuration); 197 if (!isInPictureInPictureMode) { 198 // Show the video controls if the video is not playing 199 if (mMovieView != null && !mMovieView.isPlaying()) { 200 mMovieView.showControls(); 201 } 202 } 203 } 204 205 /** Enters Picture-in-Picture mode. */ 206 void minimize() { 207 if (mMovieView == null) { 208 return; 209 } 210 // Hide the controls in picture-in-picture mode. 211 mMovieView.hideControls(); 212 // Calculate the aspect ratio of the PiP screen. 213 Rational aspectRatio = new Rational(mMovieView.getWidth(), mMovieView.getHeight()); 214 mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build(); 215 enterPictureInPictureMode(mPictureInPictureParamsBuilder.build()); 216 } 217 218 /** 219 * Adjusts immersive full-screen flags depending on the screen orientation. 220 * 221 * @param config The current {@link Configuration}. 222 */ 223 private void adjustFullScreen(Configuration config) { 224 final View decorView = getWindow().getDecorView(); 225 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 226 decorView.setSystemUiVisibility( 227 View.SYSTEM_UI_FLAG_LAYOUT_STABLE 228 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 229 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 230 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 231 | View.SYSTEM_UI_FLAG_FULLSCREEN 232 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 233 mScrollView.setVisibility(View.GONE); 234 mMovieView.setAdjustViewBounds(false); 235 } else { 236 decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 237 mScrollView.setVisibility(View.VISIBLE); 238 mMovieView.setAdjustViewBounds(true); 239 } 240 } 241 242 /** 243 * Overloaded method that persists previously set media actions. 244 * 245 * @param state The state of the video, e.g. playing, paused, etc. 246 * @param position The position of playback in the video. 247 * @param mediaId The media id related to the video in the media session. 248 */ 249 private void updatePlaybackState( 250 @PlaybackStateCompat.State int state, int position, int mediaId) { 251 long actions = mSession.getController().getPlaybackState().getActions(); 252 updatePlaybackState(state, actions, position, mediaId); 253 } 254 255 private void updatePlaybackState( 256 @PlaybackStateCompat.State int state, long playbackActions, int position, int mediaId) { 257 PlaybackStateCompat.Builder builder = 258 new PlaybackStateCompat.Builder() 259 .setActions(playbackActions) 260 .setActiveQueueItemId(mediaId) 261 .setState(state, position, 1.0f); 262 mSession.setPlaybackState(builder.build()); 263 } 264 265 /** 266 * Updates the {@link MovieView} based on the callback actions. <br> 267 * Simulates a playlist that will disable actions when you cannot skip through the playlist in a 268 * certain direction. 269 */ 270 private class MediaSessionCallback extends MediaSessionCompat.Callback { 271 272 private static final int PLAYLIST_SIZE = 2; 273 274 private MovieView movieView; 275 private int indexInPlaylist; 276 277 public MediaSessionCallback(MovieView movieView) { 278 this.movieView = movieView; 279 indexInPlaylist = 1; 280 } 281 282 @Override 283 public void onPlay() { 284 super.onPlay(); 285 movieView.play(); 286 } 287 288 @Override 289 public void onPause() { 290 super.onPause(); 291 movieView.pause(); 292 } 293 294 @Override 295 public void onSkipToNext() { 296 super.onSkipToNext(); 297 movieView.startVideo(); 298 if (indexInPlaylist < PLAYLIST_SIZE) { 299 indexInPlaylist++; 300 if (indexInPlaylist >= PLAYLIST_SIZE) { 301 updatePlaybackState( 302 PlaybackStateCompat.STATE_PLAYING, 303 MEDIA_ACTIONS_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS, 304 movieView.getCurrentPosition(), 305 movieView.getVideoResourceId()); 306 } else { 307 updatePlaybackState( 308 PlaybackStateCompat.STATE_PLAYING, 309 MEDIA_ACTIONS_ALL, 310 movieView.getCurrentPosition(), 311 movieView.getVideoResourceId()); 312 } 313 } 314 } 315 316 @Override 317 public void onSkipToPrevious() { 318 super.onSkipToPrevious(); 319 movieView.startVideo(); 320 if (indexInPlaylist > 0) { 321 indexInPlaylist--; 322 if (indexInPlaylist <= 0) { 323 updatePlaybackState( 324 PlaybackStateCompat.STATE_PLAYING, 325 MEDIA_ACTIONS_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT, 326 movieView.getCurrentPosition(), 327 movieView.getVideoResourceId()); 328 } else { 329 updatePlaybackState( 330 PlaybackStateCompat.STATE_PLAYING, 331 MEDIA_ACTIONS_ALL, 332 movieView.getCurrentPosition(), 333 movieView.getVideoResourceId()); 334 } 335 } 336 } 337 } 338 339 private class SwitchActivityOnClick implements View.OnClickListener { 340 @Override 341 public void onClick(View view) { 342 startActivity(new Intent(view.getContext(), MainActivity.class)); 343 finish(); 344 } 345 } 346 } 347