• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.android.sdksandboxcode_1;
18 
19 import android.content.Context;
20 import android.net.Uri;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.util.Log;
24 import android.view.View;
25 
26 import androidx.media3.common.AudioAttributes;
27 import androidx.media3.common.C;
28 import androidx.media3.common.MediaItem;
29 import androidx.media3.common.PlaybackException;
30 import androidx.media3.common.Player;
31 import androidx.media3.exoplayer.ExoPlayer;
32 import androidx.media3.ui.PlayerView;
33 
34 import com.android.modules.utils.build.SdkLevel;
35 
36 import java.util.WeakHashMap;
37 import java.util.concurrent.atomic.AtomicLong;
38 
39 /** Create PlayerView with Player and controlling playback based on host activity lifecycle. */
40 class PlayerViewProvider {
41 
42     private static final String TAG = "PlayerViewProvider";
43 
44     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
45 
46     private final WeakHashMap<PlayerView, PlayerState> mCreatedViews = new WeakHashMap<>();
47 
48     private final AtomicLong mLastCreatedViewId = new AtomicLong(0);
49 
50     private boolean mHostActivityStarted = true;
51 
createPlayerView(Context windowContext, String videoUrl)52     public View createPlayerView(Context windowContext, String videoUrl) {
53         final long viewId = mLastCreatedViewId.incrementAndGet();
54         final PlayerViewLogger logger = new PlayerViewLogger(viewId);
55         logger.info("Creating PlayerView");
56 
57         final PlayerView view = new PlayerView(windowContext);
58         final PlayerState playerState = new PlayerState(windowContext, logger, videoUrl);
59 
60         mMainHandler.post(
61                 () -> {
62                     mCreatedViews.put(view, playerState);
63                     if (mHostActivityStarted) {
64                         final Player player = playerState.initializePlayer();
65                         view.setPlayer(player);
66                     }
67                 });
68 
69         return view;
70     }
71 
onHostActivityStarted()72     public void onHostActivityStarted() {
73         mMainHandler.post(
74                 () -> {
75                     mHostActivityStarted = true;
76                     mCreatedViews.forEach(
77                             (view, state) -> {
78                                 if (view.getPlayer() == null) {
79                                     final Player player = state.initializePlayer();
80                                     view.setPlayer(player);
81                                     view.onResume();
82                                 }
83                             });
84                 });
85     }
86 
onHostActivityStopped()87     public void onHostActivityStopped() {
88         mMainHandler.post(
89                 () -> {
90                     mHostActivityStarted = false;
91                     mCreatedViews.forEach(
92                             (view, state) -> {
93                                 view.onPause();
94                                 state.releasePlayer();
95                                 view.setPlayer(null);
96                             });
97                 });
98     }
99 
100     private static final class PlayerState {
101         private final Context mContext;
102         private final PlayerViewLogger mLogger;
103         private final MediaItem mMediaItem;
104         private ExoPlayer mPlayer;
105         private boolean mAutoPlay;
106         private long mAutoPlayPosition;
107 
PlayerState(Context context, PlayerViewLogger logger, String videoUrl)108         private PlayerState(Context context, PlayerViewLogger logger, String videoUrl) {
109             mContext = context;
110             mLogger = logger;
111             mMediaItem = MediaItem.fromUri(Uri.parse(videoUrl));
112             mAutoPlayPosition = C.TIME_UNSET;
113             mAutoPlay = true;
114         }
115 
initializePlayer()116         private Player initializePlayer() {
117             mLogger.info("Initializing Player");
118             if (mPlayer != null) {
119                 return mPlayer;
120             }
121 
122             AudioAttributes audioAttributes =
123                     new AudioAttributes.Builder()
124                             .setUsage(C.USAGE_MEDIA)
125                             .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
126                             .build();
127 
128             // AudioFocus was broken in 24Q2
129             boolean handleAudioFocus = SdkLevel.isAtLeastV();
130             mPlayer =
131                     new ExoPlayer.Builder(mContext)
132                             .setAudioAttributes(audioAttributes, handleAudioFocus)
133                             .build();
134             mPlayer.addListener(new PlayerListener(mPlayer, mLogger));
135             mPlayer.setPlayWhenReady(mAutoPlay);
136             mPlayer.setMediaItem(mMediaItem);
137             boolean hasStartPosition = mAutoPlayPosition != C.TIME_UNSET;
138             if (hasStartPosition) {
139                 mPlayer.seekTo(0, mAutoPlayPosition);
140             }
141             mPlayer.prepare();
142 
143             return mPlayer;
144         }
145 
releasePlayer()146         private void releasePlayer() {
147             mLogger.info("Releasing Player");
148             if (mPlayer == null) {
149                 return;
150             }
151 
152             mAutoPlay = mPlayer.getPlayWhenReady();
153             mAutoPlayPosition = mPlayer.getContentPosition();
154 
155             mPlayer.release();
156             mPlayer = null;
157         }
158     }
159 
160     private static class PlayerListener implements Player.Listener {
161 
162         private final Player mPlayer;
163 
164         private final PlayerViewLogger mLogger;
165 
PlayerListener(Player player, PlayerViewLogger logger)166         private PlayerListener(Player player, PlayerViewLogger logger) {
167             mPlayer = player;
168             mLogger = logger;
169         }
170 
171         @Override
onIsLoadingChanged(boolean isLoading)172         public void onIsLoadingChanged(boolean isLoading) {
173             mLogger.info("Player onIsLoadingChanged, isLoading = " + isLoading);
174         }
175 
176         @Override
onPlaybackStateChanged(int playbackState)177         public void onPlaybackStateChanged(int playbackState) {
178             mLogger.info("Player onPlaybackStateChanged, playbackState = " + playbackState);
179             if (playbackState == Player.STATE_READY) {
180                 // Unmute at new playback
181                 mPlayer.setVolume(1);
182             }
183         }
184 
185         @Override
onIsPlayingChanged(boolean isPlaying)186         public void onIsPlayingChanged(boolean isPlaying) {
187             mLogger.info("Player onIsPlayingChanged, isPlaying = " + isPlaying);
188             if (!isPlaying) {
189                 // For testing, mute the video when it is paused until end of current playback.
190                 mPlayer.setVolume(0);
191             }
192         }
193 
194         @Override
onPlayerError(PlaybackException error)195         public void onPlayerError(PlaybackException error) {
196             mLogger.error(error);
197         }
198     }
199 
200     private static final class PlayerViewLogger {
201 
202         private final long mViewId;
203 
PlayerViewLogger(long viewId)204         private PlayerViewLogger(long viewId) {
205             mViewId = viewId;
206         }
207 
info(String message)208         public void info(String message) {
209             Log.i(TAG, "[PlayerView#" + mViewId + "] " + message);
210         }
211 
error(Exception exception)212         public void error(Exception exception) {
213             Log.e(TAG, "[PlayerView#" + mViewId + "] " + exception.getMessage(), exception);
214         }
215     }
216 }
217