That fuck shit the fascists are using
at master 434 lines 13 kB view raw
1/* 2 * Copyright (C) 2017 Whisper Systems 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17package org.tm.archive.video; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.util.AttributeSet; 22import android.view.Window; 23import android.view.WindowManager; 24import android.widget.FrameLayout; 25 26import androidx.annotation.NonNull; 27import androidx.annotation.Nullable; 28 29import androidx.annotation.OptIn; 30import androidx.media3.common.C; 31import androidx.media3.common.MediaItem; 32import androidx.media3.common.PlaybackException; 33import androidx.media3.common.Player; 34import androidx.media3.common.Tracks; 35import androidx.media3.common.util.UnstableApi; 36import androidx.media3.exoplayer.ExoPlayer; 37import androidx.media3.exoplayer.source.ClippingMediaSource; 38import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; 39import androidx.media3.exoplayer.source.MediaSource; 40import androidx.media3.ui.AspectRatioFrameLayout; 41import androidx.media3.ui.LegacyPlayerControlView; 42import androidx.media3.ui.PlayerView; 43 44import org.signal.core.util.logging.Log; 45import org.tm.archive.R; 46import org.tm.archive.dependencies.ApplicationDependencies; 47import org.tm.archive.mediapreview.MediaPreviewPlayerControlView; 48import org.tm.archive.mms.VideoSlide; 49 50import java.util.Objects; 51import java.util.concurrent.TimeUnit; 52 53@OptIn(markerClass = UnstableApi.class) 54public class VideoPlayer extends FrameLayout { 55 56 @SuppressWarnings("unused") 57 private static final String TAG = Log.tag(VideoPlayer.class); 58 59 private final PlayerView exoView; 60 private final DefaultMediaSourceFactory mediaSourceFactory; 61 62 private ExoPlayer exoPlayer; 63 private LegacyPlayerControlView exoControls; 64 private Window window; 65 private PlayerStateCallback playerStateCallback; 66 private PlayerPositionDiscontinuityCallback playerPositionDiscontinuityCallback; 67 private PlayerCallback playerCallback; 68 private boolean clipped; 69 private long clippedStartUs; 70 private ExoPlayerListener exoPlayerListener; 71 private Player.Listener playerListener; 72 private boolean muted; 73 74 public VideoPlayer(Context context) { 75 this(context, null); 76 } 77 78 public VideoPlayer(Context context, AttributeSet attrs) { 79 this(context, attrs, 0); 80 } 81 82 public VideoPlayer(Context context, AttributeSet attrs, int defStyleAttr) { 83 super(context, attrs, defStyleAttr); 84 85 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VideoPlayer); 86 int videPlayerLayout = typedArray.getResourceId(R.styleable.VideoPlayer_playerLayoutId, R.layout.video_player); 87 88 typedArray.recycle(); 89 inflate(context, videPlayerLayout, this); 90 91 this.mediaSourceFactory = new DefaultMediaSourceFactory(context); 92 93 this.exoView = findViewById(R.id.video_view); 94 this.exoControls = createPlayerControls(getContext()); 95 96 this.exoPlayerListener = new ExoPlayerListener(); 97 this.playerListener = new Player.Listener() { 98 @Override 99 public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { 100 onPlaybackStateChanged(playWhenReady, exoPlayer.getPlaybackState()); 101 } 102 103 @Override 104 public void onPlaybackStateChanged(int playbackState) { 105 onPlaybackStateChanged(exoPlayer.getPlayWhenReady(), playbackState); 106 } 107 108 private void onPlaybackStateChanged(boolean playWhenReady, int playbackState) { 109 if (playerCallback != null) { 110 switch (playbackState) { 111 case Player.STATE_READY: 112 playerCallback.onReady(); 113 if (playWhenReady) { 114 playerCallback.onPlaying(); 115 } else { 116 playerCallback.onStopped(); 117 } 118 break; 119 case Player.STATE_ENDED: 120 playerCallback.onStopped(); 121 break; 122 } 123 } 124 } 125 126 @Override 127 public void onPlayerError(@NonNull PlaybackException error) { 128 Log.w(TAG, "A player error occurred", error); 129 if (playerCallback != null) { 130 playerCallback.onError(); 131 } 132 } 133 }; 134 } 135 136 private LegacyPlayerControlView createPlayerControls(Context context) { 137 final LegacyPlayerControlView playerControlView = new LegacyPlayerControlView(context); 138 playerControlView.setShowTimeoutMs(-1); 139 playerControlView.setShowNextButton(false); 140 playerControlView.setShowPreviousButton(false); 141 return playerControlView; 142 } 143 144 private MediaItem mediaItem; 145 146 public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay, String poolTag) { 147 setVideoSource(videoSource, autoplay, poolTag, 0, 0); 148 } 149 150 public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay, String poolTag, long clipStartMs, long clipEndMs) { 151 if (exoPlayer == null) { 152 exoPlayer = ApplicationDependencies.getExoPlayerPool().require(poolTag); 153 exoPlayer.addListener(exoPlayerListener); 154 exoPlayer.addListener(playerListener); 155 exoView.setPlayer(exoPlayer); 156 exoControls.setPlayer(exoPlayer); 157 if (muted) { 158 mute(); 159 } 160 } 161 162 mediaItem = MediaItem.fromUri(Objects.requireNonNull(videoSource.getUri())).buildUpon() 163 .setClippingConfiguration(getClippingConfiguration(clipStartMs, clipEndMs)) 164 .build(); 165 166 exoPlayer.setMediaItem(mediaItem); 167 exoPlayer.prepare(); 168 exoPlayer.setPlayWhenReady(autoplay); 169 } 170 171 public void mute() { 172 this.muted = true; 173 if (exoPlayer != null) { 174 exoPlayer.setVolume(0f); 175 } 176 } 177 178 public void unmute() { 179 this.muted = false; 180 if (exoPlayer != null) { 181 exoPlayer.setVolume(1f); 182 } 183 } 184 185 public boolean hasAudioTrack() { 186 if (exoPlayer != null) { 187 Tracks tracks = exoPlayer.getCurrentTracks(); 188 return tracks.containsType(C.TRACK_TYPE_AUDIO); 189 } 190 191 return false; 192 } 193 194 public boolean isInitialized() { 195 return exoPlayer != null; 196 } 197 198 public void setResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) { 199 exoView.setResizeMode(resizeMode); 200 } 201 202 public boolean isPlaying() { 203 if (this.exoPlayer != null) { 204 return this.exoPlayer.isPlaying(); 205 } else { 206 return false; 207 } 208 } 209 210 public void pause() { 211 if (this.exoPlayer != null) { 212 this.exoPlayer.setPlayWhenReady(false); 213 } 214 } 215 216 public void hideControls() { 217 if (this.exoView != null) { 218 this.exoView.hideController(); 219 } 220 } 221 222 public void setKeepContentOnPlayerReset(boolean keepContentOnPlayerReset) { 223 if (this.exoView != null) { 224 this.exoView.setKeepContentOnPlayerReset(keepContentOnPlayerReset); 225 } 226 } 227 228 @Override 229 public void setOnClickListener(@Nullable OnClickListener l) { 230 if (this.exoView != null) { 231 this.exoView.setClickable(false); 232 } 233 234 super.setOnClickListener(l); 235 } 236 237 public @Nullable LegacyPlayerControlView getControlView() { 238 return this.exoControls; 239 } 240 241 public void setControlView(MediaPreviewPlayerControlView controller) { 242 exoControls = controller; 243 exoControls.setPlayer(exoPlayer); 244 } 245 246 public void stop() { 247 if (this.exoPlayer != null) { 248 exoPlayer.stop(); 249 exoPlayer.clearMediaItems(); 250 } 251 } 252 253 public void cleanup() { 254 stop(); 255 256 if (this.exoPlayer != null) { 257 exoView.setPlayer(null); 258 259 if (exoPlayer.equals(exoControls.getPlayer())) { 260 exoControls.setPlayer(null); 261 } 262 263 exoPlayer.removeListener(playerListener); 264 exoPlayer.removeListener(exoPlayerListener); 265 266 ApplicationDependencies.getExoPlayerPool().pool(exoPlayer); 267 this.exoPlayer = null; 268 } 269 } 270 271 public void loopForever() { 272 if (this.exoPlayer != null) { 273 exoPlayer.setRepeatMode(Player.REPEAT_MODE_ONE); 274 } 275 } 276 277 public long getDuration() { 278 if (this.exoPlayer != null) { 279 return this.exoPlayer.getDuration(); 280 } 281 return 0L; 282 } 283 284 public long getPlaybackPosition() { 285 if (this.exoPlayer != null) { 286 return this.exoPlayer.getCurrentPosition(); 287 } 288 return 0L; 289 } 290 291 public long getPlaybackPositionUs() { 292 if (this.exoPlayer != null) { 293 return TimeUnit.MILLISECONDS.toMicros(this.exoPlayer.getCurrentPosition()); 294 } 295 return -1L; 296 } 297 298 public void setPlaybackPosition(long positionMs) { 299 if (this.exoPlayer != null) { 300 this.exoPlayer.seekTo(positionMs); 301 } 302 } 303 304 public void clip(long fromUs, long toUs, boolean playWhenReady) { 305 if (this.exoPlayer != null && mediaItem != null) { 306 MediaSource mediaItemSource = mediaSourceFactory.createMediaSource(mediaItem); 307 ClippingMediaSource clippedSource = new ClippingMediaSource(mediaItemSource, fromUs, toUs); 308 309 exoPlayer.setMediaSource(clippedSource); 310 exoPlayer.prepare(); 311 exoPlayer.setPlayWhenReady(playWhenReady); 312 clipped = true; 313 clippedStartUs = fromUs; 314 } 315 } 316 317 public void removeClip(boolean playWhenReady) { 318 if (exoPlayer != null && mediaItem != null) { 319 if (clipped) { 320 exoPlayer.setMediaItem(mediaItem); 321 exoPlayer.prepare(); 322 clipped = false; 323 clippedStartUs = 0; 324 } 325 exoPlayer.setPlayWhenReady(playWhenReady); 326 } 327 } 328 329 public void setWindow(@Nullable Window window) { 330 this.window = window; 331 } 332 333 public void setPlayerStateCallbacks(@Nullable PlayerStateCallback playerStateCallback) { 334 this.playerStateCallback = playerStateCallback; 335 } 336 337 public void setPlayerCallback(PlayerCallback playerCallback) { 338 this.playerCallback = playerCallback; 339 } 340 341 public void setPlayerPositionDiscontinuityCallback(@NonNull PlayerPositionDiscontinuityCallback playerPositionDiscontinuityCallback) { 342 this.playerPositionDiscontinuityCallback = playerPositionDiscontinuityCallback; 343 } 344 345 /** 346 * Resumes a paused video, or restarts if at end of video. 347 */ 348 public void play() { 349 if (exoPlayer != null) { 350 exoPlayer.setPlayWhenReady(true); 351 if (exoPlayer.getCurrentPosition() >= exoPlayer.getDuration()) { 352 exoPlayer.seekTo(0); 353 } 354 } 355 } 356 357 private @NonNull MediaItem.ClippingConfiguration getClippingConfiguration(long startMs, long endMs) { 358 return startMs != endMs ? new MediaItem.ClippingConfiguration.Builder() 359 .setStartPositionMs(startMs) 360 .setEndPositionMs(endMs) 361 .build() 362 : MediaItem.ClippingConfiguration.UNSET; 363 } 364 365 private class ExoPlayerListener implements Player.Listener { 366 367 @Override 368 public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { 369 onPlaybackStateChanged(playWhenReady, exoPlayer.getPlaybackState()); 370 } 371 372 @Override 373 public void onPlaybackStateChanged(int playbackState) { 374 onPlaybackStateChanged(exoPlayer.getPlayWhenReady(), playbackState); 375 } 376 377 private void onPlaybackStateChanged(boolean playWhenReady, int playbackState) { 378 switch (playbackState) { 379 case Player.STATE_IDLE: 380 case Player.STATE_BUFFERING: 381 case Player.STATE_ENDED: 382 if (window != null) { 383 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 384 } 385 break; 386 case Player.STATE_READY: 387 if (window != null) { 388 if (playWhenReady) { 389 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 390 } else { 391 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 392 } 393 } 394 notifyPlayerReady(); 395 break; 396 default: 397 break; 398 } 399 } 400 401 @Override 402 public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, 403 @NonNull Player.PositionInfo newPosition, 404 int reason) 405 { 406 if (playerPositionDiscontinuityCallback != null) { 407 playerPositionDiscontinuityCallback.onPositionDiscontinuity(VideoPlayer.this, reason); 408 } 409 } 410 411 private void notifyPlayerReady() { 412 if (playerStateCallback != null) playerStateCallback.onPlayerReady(); 413 } 414 } 415 416 public interface PlayerStateCallback { 417 void onPlayerReady(); 418 } 419 420 public interface PlayerPositionDiscontinuityCallback { 421 void onPositionDiscontinuity(@NonNull VideoPlayer player, int reason); 422 } 423 424 public interface PlayerCallback { 425 426 default void onReady() {} 427 428 void onPlaying(); 429 430 void onStopped(); 431 432 void onError(); 433 } 434}