1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package com.example.android.leanback;
15 
16 import android.net.Uri;
17 import android.os.Bundle;
18 import android.support.v4.media.session.MediaSessionCompat;
19 
20 import androidx.leanback.app.VideoFragmentGlueHost;
21 import androidx.leanback.media.MediaPlayerAdapter;
22 import androidx.leanback.media.PlaybackGlue;
23 import androidx.leanback.media.PlaybackTransportControlGlue;
24 import androidx.leanback.widget.PlaybackControlsRow;
25 
26 import org.jspecify.annotations.NonNull;
27 
28 /**
29  * Fragment demonstrating the use of {@link androidx.leanback.app.VideoFragment} to
30  * render video with playback controls. And demonstrates video seeking with thumbnails.
31  *
32  * Generate 1 frame per second thumbnail bitmaps and put on sdcard:
33  * <pre>
34  * sudo apt-get install libav-tools
35  * avconv -i input.mp4 -s 240x135 -vsync 1 -r 1 -an -y -qscale 8 frame_%04d.jpg
36  * adb shell mkdir /sdcard/seek
37  * adb push frame_*.jpg /sdcard/seek/
38  * </pre>
39  * Change to 1 frame per minute: use "-r 1/60".
40  * For more options, see https://wiki.libav.org/Snippets/avconv
41  *
42  * <p>
43  * Showcase:
44  * </p>
45  * <li>Auto play when ready</li>
46  * <li>Set seek provider</li>
47  * <li>switch MediaSource</li>
48  * <li>switch PlaybackGlue</li>
49  */
50 public class SampleVideoFragment extends androidx.leanback.app.VideoFragment {
51 
52     // Media Session Token
53     private static final String MEDIA_SESSION_COMPAT_TOKEN = "media session support video";
54 
55     private PlaybackTransportControlGlueSample<MediaPlayerAdapter> mMediaPlayerGlue;
56 
57     private MediaSessionCompat mMediaSessionCompat;
58 
59     final VideoFragmentGlueHost mHost = new VideoFragmentGlueHost(SampleVideoFragment.this);
60 
playWhenReady(PlaybackGlue glue)61     static void playWhenReady(PlaybackGlue glue) {
62         if (glue.isPrepared()) {
63             glue.play();
64         } else {
65             glue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
66                 @Override
67                 public void onPreparedStateChanged(@NonNull PlaybackGlue glue) {
68                     if (glue.isPrepared()) {
69                         glue.removePlayerCallback(this);
70                         glue.play();
71                     }
72                 }
73             });
74         }
75     }
76 
loadSeekData(final PlaybackTransportControlGlue glue)77     static void loadSeekData(final PlaybackTransportControlGlue glue) {
78         if (glue.isPrepared()) {
79             glue.setSeekProvider(new PlaybackSeekDiskDataProvider(
80                     glue.getDuration(),
81                     1000,
82                     "/sdcard/seek/frame_%04d.jpg"));
83         } else {
84             glue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
85                 @Override
86                 public void onPreparedStateChanged(@NonNull PlaybackGlue glue) {
87                     if (glue.isPrepared()) {
88                         glue.removePlayerCallback(this);
89                         PlaybackTransportControlGlue transportControlGlue =
90                                 (PlaybackTransportControlGlue) glue;
91                         transportControlGlue.setSeekProvider(new PlaybackSeekDiskDataProvider(
92                                 transportControlGlue.getDuration(),
93                                 1000,
94                                 "/sdcard/seek/frame_%04d.jpg"));
95                     }
96                 }
97             });
98         }
99     }
100 
101     @Override
onCreate(Bundle savedInstanceState)102     public void onCreate(Bundle savedInstanceState) {
103         super.onCreate(savedInstanceState);
104         mMediaPlayerGlue = new PlaybackTransportControlGlueSample(getActivity(),
105                 new MediaPlayerAdapter(getActivity()));
106 
107         // create a media session inside of a fragment, and app developer can determine if connect
108         // this media session to glue or not
109         // as requested in b/64935838
110         mMediaSessionCompat = new MediaSessionCompat(getActivity(), MEDIA_SESSION_COMPAT_TOKEN);
111         mMediaPlayerGlue.connectToMediaSession(mMediaSessionCompat);
112 
113         mMediaPlayerGlue.setHost(mHost);
114         mMediaPlayerGlue.setMode(PlaybackControlsRow.RepeatAction.INDEX_NONE);
115         mMediaPlayerGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
116             boolean mSecondCompleted = false;
117             @Override
118             public void onPlayCompleted(@NonNull PlaybackGlue glue) {
119                 if (!mSecondCompleted) {
120                     mSecondCompleted = true;
121                     mMediaPlayerGlue.setSubtitle("Leanback artist Changed!");
122                     mMediaPlayerGlue.setTitle("Leanback team at work");
123                     String uriPath = "https://storage.googleapis.com/android-tv/Sample videos/"
124                             + "April Fool's 2013/Explore Treasure Mode with Google Maps.mp4";
125                     mMediaPlayerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
126                     loadSeekData(mMediaPlayerGlue);
127                     playWhenReady(mMediaPlayerGlue);
128                 } else {
129                     mMediaPlayerGlue.removePlayerCallback(this);
130                     switchAnotherGlue();
131                 }
132             }
133         });
134         mMediaPlayerGlue.setSubtitle("Leanback artist");
135         mMediaPlayerGlue.setTitle("Leanback team at work");
136         String uriPath = "https://storage.googleapis.com/android-tv/Sample videos/"
137                 + "April Fool's 2013/Explore Treasure Mode with Google Maps.mp4";
138         mMediaPlayerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
139         loadSeekData(mMediaPlayerGlue);
140         playWhenReady(mMediaPlayerGlue);
141     }
142 
143     @Override
onPause()144     public void onPause() {
145         if (mMediaPlayerGlue != null) {
146             mMediaPlayerGlue.pause();
147         }
148         super.onPause();
149     }
150 
151     @Override
onDestroy()152     public void onDestroy() {
153         super.onDestroy();
154         mMediaPlayerGlue.disconnectToMediaSession();
155     }
156 
switchAnotherGlue()157     void switchAnotherGlue() {
158         mMediaPlayerGlue = new PlaybackTransportControlGlueSample(getActivity(),
159                 new MediaPlayerAdapter(getActivity()));
160 
161         // If the glue is switched, re-register the media session
162         mMediaPlayerGlue.connectToMediaSession(mMediaSessionCompat);
163 
164         mMediaPlayerGlue.setMode(PlaybackControlsRow.RepeatAction.INDEX_ONE);
165         mMediaPlayerGlue.setSubtitle("A Googler");
166         mMediaPlayerGlue.setTitle("Swimming with the fishes");
167         mMediaPlayerGlue.getPlayerAdapter().setDataSource(
168                 Uri.parse("http://techslides.com/demos/sample-videos/small.mp4"));
169         mMediaPlayerGlue.setHost(mHost);
170         loadSeekData(mMediaPlayerGlue);
171         playWhenReady(mMediaPlayerGlue);
172     }
173 }
174