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 /** 37 * Demonstrates usage of Picture-in-Picture when using 38 * [android.support.v4.media.session.MediaSessionCompat]. 39 */ 40 class MediaSessionPlaybackActivity : AppCompatActivity() { 41 42 companion object { 43 44 private val TAG = "MediaSessionPlaybackActivity" 45 46 val MEDIA_ACTIONS_PLAY_PAUSE = 47 PlaybackStateCompat.ACTION_PLAY or 48 PlaybackStateCompat.ACTION_PAUSE or 49 PlaybackStateCompat.ACTION_PLAY_PAUSE 50 51 val MEDIA_ACTIONS_ALL = 52 MEDIA_ACTIONS_PLAY_PAUSE or 53 PlaybackStateCompat.ACTION_SKIP_TO_NEXT or 54 PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS 55 } 56 57 private lateinit var mSession: MediaSessionCompat 58 59 /** The arguments to be used for Picture-in-Picture mode. */ 60 private val mPictureInPictureParamsBuilder = PictureInPictureParams.Builder() 61 62 /** This shows the video. */ 63 private lateinit var mMovieView: MovieView 64 65 /** The bottom half of the screen; hidden on landscape */ 66 private lateinit var mScrollView: ScrollView 67 68 private val mOnClickListener = View.OnClickListener { view -> 69 when (view.id) { 70 R.id.pip -> minimize() 71 } 72 } 73 74 /** 75 * Callbacks from the [MovieView] showing the video playback. 76 */ 77 private val mMovieListener = object : MovieView.MovieListener() { 78 79 override fun onMovieStarted() { 80 // We are playing the video now. Update the media session state and the PiP window will 81 // update the actions. 82 mMovieView?.let { view -> 83 updatePlaybackState( 84 PlaybackStateCompat.STATE_PLAYING, 85 view.getCurrentPosition(), 86 view.getVideoResourceId()) 87 } 88 } 89 90 override fun onMovieStopped() { 91 // The video stopped or reached its end. Update the media session state and the PiP window will 92 // update the actions. 93 mMovieView?.let { view -> 94 updatePlaybackState( 95 PlaybackStateCompat.STATE_PAUSED, 96 view.getCurrentPosition(), 97 view.getVideoResourceId()) 98 } 99 } 100 101 override fun onMovieMinimized() { 102 // The MovieView wants us to minimize it. We enter Picture-in-Picture mode now. 103 minimize() 104 } 105 106 } 107 108 override fun onCreate(savedInstanceState: Bundle?) { 109 super.onCreate(savedInstanceState) 110 setContentView(R.layout.activity_main) 111 112 // View references 113 mMovieView = findViewById<MovieView>(R.id.movie) 114 mScrollView = findViewById<ScrollView>(R.id.scroll) 115 val switchExampleButton = findViewById<Button>(R.id.switch_example) 116 switchExampleButton.text = getString(R.string.switch_custom) 117 switchExampleButton.setOnClickListener(SwitchActivityOnClick()) 118 119 // Set up the video; it automatically starts. 120 mMovieView.setMovieListener(mMovieListener) 121 findViewById<View>(R.id.pip).setOnClickListener(mOnClickListener) 122 } 123 124 override fun onStart() { 125 super.onStart() 126 initializeMediaSession() 127 } 128 129 private fun initializeMediaSession() { 130 mSession = MediaSessionCompat(this, TAG) 131 mSession.setFlags( 132 MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS) 133 mSession.isActive = true 134 MediaControllerCompat.setMediaController(this, mSession.controller) 135 136 val metadata = MediaMetadataCompat.Builder() 137 .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, mMovieView.title) 138 .build() 139 mSession.setMetadata(metadata) 140 141 val mMediaSessionCallback = MediaSessionCallback(mMovieView) 142 mSession.setCallback(mMediaSessionCallback) 143 144 val state = if (mMovieView.isPlaying) 145 PlaybackStateCompat.STATE_PLAYING 146 else 147 PlaybackStateCompat.STATE_PAUSED 148 updatePlaybackState( 149 state, 150 MEDIA_ACTIONS_ALL, 151 mMovieView.getCurrentPosition(), 152 mMovieView.getVideoResourceId()) 153 } 154 155 override fun onStop() { 156 super.onStop() 157 // On entering Picture-in-Picture mode, onPause is called, but not onStop. 158 // For this reason, this is the place where we should pause the video playback. 159 mMovieView.pause() 160 mSession.release() 161 } 162 163 override fun onRestart() { 164 super.onRestart() 165 if (!isInPictureInPictureMode) { 166 // Show the video controls so the video can be easily resumed. 167 mMovieView.showControls() 168 } 169 } 170 171 override fun onConfigurationChanged(newConfig: Configuration) { 172 super.onConfigurationChanged(newConfig) 173 adjustFullScreen(newConfig) 174 } 175 176 override fun onWindowFocusChanged(hasFocus: Boolean) { 177 super.onWindowFocusChanged(hasFocus) 178 if (hasFocus) { 179 adjustFullScreen(resources.configuration) 180 } 181 } 182 183 override fun onPictureInPictureModeChanged( 184 isInPictureInPictureMode: Boolean, newConfig: Configuration) { 185 super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) 186 if (!isInPictureInPictureMode) { 187 // Show the video controls if the video is not playing 188 if (!mMovieView.isPlaying) { 189 mMovieView.showControls() 190 } 191 } 192 } 193 194 /** 195 * Enters Picture-in-Picture mode. 196 */ 197 internal fun minimize() { 198 // Hide the controls in picture-in-picture mode. 199 mMovieView.hideControls() 200 // Calculate the aspect ratio of the PiP screen. 201 val aspectRatio = Rational(mMovieView.width, mMovieView.height) 202 mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build() 203 enterPictureInPictureMode(mPictureInPictureParamsBuilder.build()) 204 } 205 206 /** 207 * Adjusts immersive full-screen flags depending on the screen orientation. 208 209 * @param config The current [Configuration]. 210 */ 211 private fun adjustFullScreen(config: Configuration) { 212 val decorView = window.decorView 213 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 214 decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 215 mScrollView.visibility = View.GONE 216 mMovieView.setAdjustViewBounds(false) 217 } else { 218 decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE 219 mScrollView.visibility = View.VISIBLE 220 mMovieView.setAdjustViewBounds(true) 221 } 222 } 223 224 /** 225 * Overloaded method that persists previously set media actions. 226 227 * @param state The state of the video, e.g. playing, paused, etc. 228 * * 229 * @param position The position of playback in the video. 230 * * 231 * @param mediaId The media id related to the video in the media session. 232 */ 233 private fun updatePlaybackState( 234 @PlaybackStateCompat.State state: Int, 235 position: Int, 236 mediaId: Int) { 237 val actions = mSession.controller.playbackState.actions 238 updatePlaybackState(state, actions, position, mediaId) 239 } 240 241 private fun updatePlaybackState( 242 @PlaybackStateCompat.State state: Int, 243 playbackActions: Long, 244 position: Int, 245 mediaId: Int) { 246 val builder = PlaybackStateCompat.Builder() 247 .setActions(playbackActions) 248 .setActiveQueueItemId(mediaId.toLong()) 249 .setState(state, position.toLong(), 1.0f) 250 mSession.setPlaybackState(builder.build()) 251 } 252 253 /** 254 * Updates the [MovieView] based on the callback actions. <br></br> 255 * Simulates a playlist that will disable actions when you cannot skip through the playlist in a 256 * certain direction. 257 */ 258 private inner class MediaSessionCallback(private val movieView: MovieView) : MediaSessionCompat.Callback() { 259 private val PLAYLIST_SIZE = 2 260 261 private var indexInPlaylist: Int = 0 262 263 init { 264 indexInPlaylist = 1 265 } 266 267 override fun onPlay() { 268 super.onPlay() 269 movieView.play() 270 } 271 272 override fun onPause() { 273 super.onPause() 274 movieView.pause() 275 } 276 277 override fun onSkipToNext() { 278 super.onSkipToNext() 279 movieView.startVideo() 280 if( indexInPlaylist < PLAYLIST_SIZE ) { 281 indexInPlaylist++ 282 if (indexInPlaylist >= PLAYLIST_SIZE) { 283 updatePlaybackState(PlaybackStateCompat.STATE_PLAYING, 284 MEDIA_ACTIONS_PLAY_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS, 285 movieView.getCurrentPosition(), 286 movieView.getVideoResourceId()) 287 } else { 288 updatePlaybackState(PlaybackStateCompat.STATE_PLAYING, 289 MEDIA_ACTIONS_ALL, 290 movieView.getCurrentPosition(), 291 movieView.getVideoResourceId()) 292 } 293 } 294 } 295 296 override fun onSkipToPrevious() { 297 super.onSkipToPrevious() 298 movieView.startVideo() 299 if( indexInPlaylist > 0 ) { 300 indexInPlaylist-- 301 if (indexInPlaylist <= 0) { 302 updatePlaybackState(PlaybackStateCompat.STATE_PLAYING, 303 MEDIA_ACTIONS_PLAY_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_NEXT, 304 movieView.getCurrentPosition(), 305 movieView.getVideoResourceId()) 306 } else { 307 updatePlaybackState(PlaybackStateCompat.STATE_PLAYING, 308 MEDIA_ACTIONS_ALL, 309 movieView.getCurrentPosition(), 310 movieView.getVideoResourceId()) 311 } 312 } 313 } 314 } 315 316 private inner class SwitchActivityOnClick : View.OnClickListener { 317 override fun onClick(view: View) { 318 startActivity(Intent(view.context, MainActivity::class.java)) 319 finish() 320 } 321 } 322 } 323