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