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