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  * 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