Home | History | Annotate | Download | only in pictureinpicture
      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