blob: 0027501511d6b3b39e991b1c8d2e3525592e123f [file] [log] [blame]
/*
* Copyright (C) 2017 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.pictureinpicture
import android.app.PictureInPictureParams
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.support.v4.media.session.MediaControllerCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import android.support.v7.app.AppCompatActivity
import android.util.Rational
import android.view.View
import android.widget.Button
import android.widget.ScrollView
import com.example.android.pictureinpicture.widget.MovieView
/**
* Demonstrates usage of Picture-in-Picture when using
* [android.support.v4.media.session.MediaSessionCompat].
*/
class MediaSessionPlaybackActivity : AppCompatActivity() {
companion object {
private val TAG = "MediaSessionPlaybackActivity"
val MEDIA_ACTIONS_PLAY_PAUSE =
PlaybackStateCompat.ACTION_PLAY or
PlaybackStateCompat.ACTION_PAUSE or
PlaybackStateCompat.ACTION_PLAY_PAUSE
val MEDIA_ACTIONS_ALL =
MEDIA_ACTIONS_PLAY_PAUSE or
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
}
private lateinit var mSession: MediaSessionCompat
/** The arguments to be used for Picture-in-Picture mode. */
private val mPictureInPictureParamsBuilder = PictureInPictureParams.Builder()
/** This shows the video. */
private lateinit var mMovieView: MovieView
/** The bottom half of the screen; hidden on landscape */
private lateinit var mScrollView: ScrollView
private val mOnClickListener = View.OnClickListener { view ->
when (view.id) {
R.id.pip -> minimize()
}
}
/**
* Callbacks from the [MovieView] showing the video playback.
*/
private val mMovieListener = object : MovieView.MovieListener() {
override fun onMovieStarted() {
// We are playing the video now. Update the media session state and the PiP window will
// update the actions.
mMovieView?.let { view ->
updatePlaybackState(
PlaybackStateCompat.STATE_PLAYING,
view.getCurrentPosition(),
view.getVideoResourceId())
}
}
override fun onMovieStopped() {
// The video stopped or reached its end. Update the media session state and the PiP window will
// update the actions.
mMovieView?.let { view ->
updatePlaybackState(
PlaybackStateCompat.STATE_PAUSED,
view.getCurrentPosition(),
view.getVideoResourceId())
}
}
override fun onMovieMinimized() {
// The MovieView wants us to minimize it. We enter Picture-in-Picture mode now.
minimize()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// View references
mMovieView = findViewById<MovieView>(R.id.movie)
mScrollView = findViewById<ScrollView>(R.id.scroll)
val switchExampleButton = findViewById<Button>(R.id.switch_example)
switchExampleButton.text = getString(R.string.switch_custom)
switchExampleButton.setOnClickListener(SwitchActivityOnClick())
// Set up the video; it automatically starts.
mMovieView.setMovieListener(mMovieListener)
findViewById<View>(R.id.pip).setOnClickListener(mOnClickListener)
}
override fun onStart() {
super.onStart()
initializeMediaSession()
}
private fun initializeMediaSession() {
mSession = MediaSessionCompat(this, TAG)
mSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
mSession.isActive = true
MediaControllerCompat.setMediaController(this, mSession.controller)
val mMediaSessionCallback = MediaSessionCallback(mMovieView)
mSession.setCallback(mMediaSessionCallback)
val state = if (mMovieView.isPlaying)
PlaybackStateCompat.STATE_PLAYING
else
PlaybackStateCompat.STATE_PAUSED
updatePlaybackState(
state,
MEDIA_ACTIONS_ALL,
mMovieView.getCurrentPosition(),
mMovieView.getVideoResourceId())
}
override fun onStop() {
super.onStop()
// On entering Picture-in-Picture mode, onPause is called, but not onStop.
// For this reason, this is the place where we should pause the video playback.
mMovieView.pause()
mSession.release()
}
override fun onRestart() {
super.onRestart()
if (!isInPictureInPictureMode) {
// Show the video controls so the video can be easily resumed.
mMovieView.showControls()
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
adjustFullScreen(newConfig)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
adjustFullScreen(resources.configuration)
}
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean, newConfig: Configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (!isInPictureInPictureMode) {
// Show the video controls if the video is not playing
if (!mMovieView.isPlaying) {
mMovieView.showControls()
}
}
}
/**
* Enters Picture-in-Picture mode.
*/
internal fun minimize() {
// Hide the controls in picture-in-picture mode.
mMovieView.hideControls()
// Calculate the aspect ratio of the PiP screen.
val aspectRatio = Rational(mMovieView.width, mMovieView.height)
mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build()
enterPictureInPictureMode(mPictureInPictureParamsBuilder.build())
}
/**
* Adjusts immersive full-screen flags depending on the screen orientation.
* @param config The current [Configuration].
*/
private fun adjustFullScreen(config: Configuration) {
val decorView = window.decorView
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
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
mScrollView.visibility = View.GONE
mMovieView.setAdjustViewBounds(false)
} else {
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
mScrollView.visibility = View.VISIBLE
mMovieView.setAdjustViewBounds(true)
}
}
/**
* Overloaded method that persists previously set media actions.
* @param state The state of the video, e.g. playing, paused, etc.
* *
* @param position The position of playback in the video.
* *
* @param mediaId The media id related to the video in the media session.
*/
private fun updatePlaybackState(
@PlaybackStateCompat.State state: Int,
position: Int,
mediaId: Int) {
val actions = mSession.controller.playbackState.actions
updatePlaybackState(state, actions, position, mediaId)
}
private fun updatePlaybackState(
@PlaybackStateCompat.State state: Int,
playbackActions: Long,
position: Int,
mediaId: Int) {
val builder = PlaybackStateCompat.Builder()
.setActions(playbackActions)
.setActiveQueueItemId(mediaId.toLong())
.setState(state, position.toLong(), 1.0f)
mSession.setPlaybackState(builder.build())
}
/**
* Updates the [MovieView] based on the callback actions. <br></br>
* Simulates a playlist that will disable actions when you cannot skip through the playlist in a
* certain direction.
*/
private inner class MediaSessionCallback(private val movieView: MovieView) : MediaSessionCompat.Callback() {
private val PLAYLIST_SIZE = 2
private var indexInPlaylist: Int = 0
init {
indexInPlaylist = 1
}
override fun onPlay() {
super.onPlay()
movieView.play()
}
override fun onPause() {
super.onPause()
movieView.pause()
}
override fun onSkipToNext() {
super.onSkipToNext()
movieView.startVideo()
if( indexInPlaylist < PLAYLIST_SIZE ) {
indexInPlaylist++
if (indexInPlaylist >= PLAYLIST_SIZE) {
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING,
MEDIA_ACTIONS_PLAY_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS,
movieView.getCurrentPosition(),
movieView.getVideoResourceId())
} else {
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING,
MEDIA_ACTIONS_ALL,
movieView.getCurrentPosition(),
movieView.getVideoResourceId())
}
}
}
override fun onSkipToPrevious() {
super.onSkipToPrevious()
movieView.startVideo()
if( indexInPlaylist > 0 ) {
indexInPlaylist--
if (indexInPlaylist <= 0) {
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING,
MEDIA_ACTIONS_PLAY_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_NEXT,
movieView.getCurrentPosition(),
movieView.getVideoResourceId())
} else {
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING,
MEDIA_ACTIONS_ALL,
movieView.getCurrentPosition(),
movieView.getVideoResourceId())
}
}
}
}
private inner class SwitchActivityOnClick : View.OnClickListener {
override fun onClick(view: View) {
startActivity(Intent(view.context, MainActivity::class.java))
finish()
}
}
}