blob: 90043f1b9dbd8bedd452adbdb380a68d8377f62b [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 {@link
* android.support.v4.media.session.MediaSessionCompat}.
*/
public class MediaSessionPlaybackActivity extends AppCompatActivity {
private static final String TAG = "MediaSessionPlaybackActivity";
public static final long MEDIA_ACTIONS_PLAY_PAUSE =
PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY_PAUSE;
public static final long MEDIA_ACTIONS_ALL =
MEDIA_ACTIONS_PLAY_PAUSE
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
private MediaSessionCompat mSession;
/** The arguments to be used for Picture-in-Picture mode. */
private final PictureInPictureParams.Builder mPictureInPictureParamsBuilder =
new PictureInPictureParams.Builder();
/** This shows the video. */
private MovieView mMovieView;
/** The bottom half of the screen; hidden on landscape */
private ScrollView mScrollView;
private final View.OnClickListener mOnClickListener =
new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.pip:
minimize();
break;
}
}
};
/** Callbacks from the {@link MovieView} showing the video playback. */
private MovieView.MovieListener mMovieListener =
new MovieView.MovieListener() {
@Override
public void onMovieStarted() {
// We are playing the video now. Update the media session state and the PiP
// window will
// update the actions.
updatePlaybackState(
PlaybackStateCompat.STATE_PLAYING,
mMovieView.getCurrentPosition(),
mMovieView.getVideoResourceId());
}
@Override
public void onMovieStopped() {
// The video stopped or reached its end. Update the media session state and the
// PiP window will
// update the actions.
updatePlaybackState(
PlaybackStateCompat.STATE_PAUSED,
mMovieView.getCurrentPosition(),
mMovieView.getVideoResourceId());
}
@Override
public void onMovieMinimized() {
// The MovieView wants us to minimize it. We enter Picture-in-Picture mode now.
minimize();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// View references
mMovieView = findViewById(R.id.movie);
mScrollView = findViewById(R.id.scroll);
Button switchExampleButton = findViewById(R.id.switch_example);
switchExampleButton.setText(getString(R.string.switch_custom));
switchExampleButton.setOnClickListener(new SwitchActivityOnClick());
// Set up the video; it automatically starts.
mMovieView.setMovieListener(mMovieListener);
findViewById(R.id.pip).setOnClickListener(mOnClickListener);
}
@Override
protected void onStart() {
super.onStart();
initializeMediaSession();
}
private void initializeMediaSession() {
mSession = new MediaSessionCompat(this, TAG);
mSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setActive(true);
MediaControllerCompat.setMediaController(this, mSession.getController());
MediaSessionCallback mMediaSessionCallback = new MediaSessionCallback(mMovieView);
mSession.setCallback(mMediaSessionCallback);
int state =
mMovieView.isPlaying()
? PlaybackStateCompat.STATE_PLAYING
: PlaybackStateCompat.STATE_PAUSED;
updatePlaybackState(
state,
MEDIA_ACTIONS_ALL,
mMovieView.getCurrentPosition(),
mMovieView.getVideoResourceId());
}
@Override
protected void 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();
mSession = null;
}
@Override
protected void onRestart() {
super.onRestart();
if (!isInPictureInPictureMode()) {
// Show the video controls so the video can be easily resumed.
mMovieView.showControls();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
adjustFullScreen(newConfig);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
adjustFullScreen(getResources().getConfiguration());
}
}
@Override
public void onPictureInPictureModeChanged(
boolean isInPictureInPictureMode, Configuration configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, configuration);
if (!isInPictureInPictureMode) {
// Show the video controls if the video is not playing
if (mMovieView != null && !mMovieView.isPlaying()) {
mMovieView.showControls();
}
}
}
/** Enters Picture-in-Picture mode. */
void minimize() {
if (mMovieView == null) {
return;
}
// Hide the controls in picture-in-picture mode.
mMovieView.hideControls();
// Calculate the aspect ratio of the PiP screen.
Rational aspectRatio = new Rational(mMovieView.getWidth(), mMovieView.getHeight());
mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build();
enterPictureInPictureMode(mPictureInPictureParamsBuilder.build());
}
/**
* Adjusts immersive full-screen flags depending on the screen orientation.
*
* @param config The current {@link Configuration}.
*/
private void adjustFullScreen(Configuration config) {
final View decorView = getWindow().getDecorView();
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
mScrollView.setVisibility(View.GONE);
mMovieView.setAdjustViewBounds(false);
} else {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
mScrollView.setVisibility(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 void updatePlaybackState(
@PlaybackStateCompat.State int state, int position, int mediaId) {
long actions = mSession.getController().getPlaybackState().getActions();
updatePlaybackState(state, actions, position, mediaId);
}
private void updatePlaybackState(
@PlaybackStateCompat.State int state, long playbackActions, int position, int mediaId) {
PlaybackStateCompat.Builder builder =
new PlaybackStateCompat.Builder()
.setActions(playbackActions)
.setActiveQueueItemId(mediaId)
.setState(state, position, 1.0f);
mSession.setPlaybackState(builder.build());
}
/**
* Updates the {@link MovieView} based on the callback actions. <br>
* Simulates a playlist that will disable actions when you cannot skip through the playlist in a
* certain direction.
*/
private class MediaSessionCallback extends MediaSessionCompat.Callback {
private static final int PLAYLIST_SIZE = 2;
private MovieView movieView;
private int indexInPlaylist;
public MediaSessionCallback(MovieView movieView) {
this.movieView = movieView;
indexInPlaylist = 1;
}
@Override
public void onPlay() {
super.onPlay();
movieView.play();
}
@Override
public void onPause() {
super.onPause();
movieView.pause();
}
@Override
public void onSkipToNext() {
super.onSkipToNext();
movieView.startVideo();
if (indexInPlaylist < PLAYLIST_SIZE) {
indexInPlaylist++;
if (indexInPlaylist >= PLAYLIST_SIZE) {
updatePlaybackState(
PlaybackStateCompat.STATE_PLAYING,
MEDIA_ACTIONS_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS,
movieView.getCurrentPosition(),
movieView.getVideoResourceId());
} else {
updatePlaybackState(
PlaybackStateCompat.STATE_PLAYING,
MEDIA_ACTIONS_ALL,
movieView.getCurrentPosition(),
movieView.getVideoResourceId());
}
}
}
@Override
public void onSkipToPrevious() {
super.onSkipToPrevious();
movieView.startVideo();
if (indexInPlaylist > 0) {
indexInPlaylist--;
if (indexInPlaylist <= 0) {
updatePlaybackState(
PlaybackStateCompat.STATE_PLAYING,
MEDIA_ACTIONS_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT,
movieView.getCurrentPosition(),
movieView.getVideoResourceId());
} else {
updatePlaybackState(
PlaybackStateCompat.STATE_PLAYING,
MEDIA_ACTIONS_ALL,
movieView.getCurrentPosition(),
movieView.getVideoResourceId());
}
}
}
}
private class SwitchActivityOnClick implements View.OnClickListener {
@Override
public void onClick(View view) {
startActivity(new Intent(view.getContext(), MainActivity.class));
finish();
}
}
}