diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 3535ad445f..7fbf91596d 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,3 +1,9 @@ + + + diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerFullscreenVideoActivity.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerFullscreenVideoActivity.java new file mode 100644 index 0000000000..0fdd1e403d --- /dev/null +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerFullscreenVideoActivity.java @@ -0,0 +1,155 @@ +package com.brentvatne.exoplayer; + +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +import androidx.appcompat.app.AppCompatActivity; + +import com.brentvatne.react.R; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.ui.PlayerControlView; + +public class ExoPlayerFullscreenVideoActivity extends AppCompatActivity implements ReactExoplayerView.FullScreenDelegate { + public static final String EXTRA_EXO_PLAYER_VIEW_ID = "extra_id"; + public static final String EXTRA_ORIENTATION = "extra_orientation"; + + private ReactExoplayerView exoplayerView; + private PlayerControlView playerControlView; + private ExoPlayer player; + + @Override + public void onCreate(Bundle savedInstanceState) { + int exoplayerViewId = getIntent().getIntExtra(EXTRA_EXO_PLAYER_VIEW_ID, -1); + exoplayerView = ReactExoplayerView.getViewInstance(exoplayerViewId); + if (exoplayerView == null) { + finish(); + return; + } + super.onCreate(savedInstanceState); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + String orientation = getIntent().getStringExtra(EXTRA_ORIENTATION); + if ("landscape".equals(orientation)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + } else if ("portrait".equals(orientation)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + } + setContentView(R.layout.exo_player_fullscreen_video); + player = exoplayerView.getPlayer(); + + ExoPlayerView playerView = findViewById(R.id.player_view); + playerView.setPlayer(player); + playerView.setOnClickListener(v -> togglePlayerControlVisibility()); + + playerControlView = findViewById(R.id.player_controls); + playerControlView.setPlayer(player); + // Set the fullscreen button to "close fullscreen" icon + ImageView fullscreenIcon = playerControlView.findViewById(R.id.exo_fullscreen_icon); + fullscreenIcon.setImageResource(R.drawable.exo_controls_fullscreen_exit); + playerControlView.findViewById(R.id.exo_fullscreen_button) + .setOnClickListener(v -> { + if (exoplayerView != null) { + exoplayerView.setFullscreen(false); + } + }); + //Handling the playButton click event + playerControlView.findViewById(R.id.exo_play).setOnClickListener(v -> { + if (player != null && player.getPlaybackState() == Player.STATE_ENDED) { + player.seekTo(0); + } + if (exoplayerView != null) { + exoplayerView.setPausedModifier(false); + } + }); + + //Handling the pauseButton click event + playerControlView.findViewById(R.id.exo_pause).setOnClickListener(v -> { + if (exoplayerView != null) { + exoplayerView.setPausedModifier(true); + } + }); + } + + @Override + public void onResume() { + super.onResume(); + if (exoplayerView != null) { + exoplayerView.syncPlayerState(); + exoplayerView.registerFullScreenDelegate(this); + } + } + + @Override + public void onPause() { + super.onPause(); + player.setPlayWhenReady(false); + if (exoplayerView != null) { + exoplayerView.registerFullScreenDelegate(null); + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + playerControlView.postDelayed(this::hideSystemUI, 200); + } + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if ((keyCode == KeyEvent.KEYCODE_BACK)) { + if (exoplayerView != null) { + exoplayerView.setFullscreen(false); + return false; + } + return true; + } + return super.onKeyDown(keyCode, event); + } + + private void togglePlayerControlVisibility() { + if (playerControlView.isVisible()) { + playerControlView.hide(); + } else { + playerControlView.show(); + } + } + + /** + * Enables regular immersive mode. + */ + private void hideSystemUI() { + View decorView = getWindow().getDecorView(); + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_IMMERSIVE + // Set the content to appear under the system bars so that the + // content doesn't resize when the system bars hide and show. + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + // Hide the nav bar and status bar + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN); + } + + /** + * Shows the system bars by removing all the flags + * except for the ones that make the content appear under the system bars. + */ + private void showSystemUI() { + View decorView = getWindow().getDecorView(); + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + + @Override + public void closeFullScreen() { + finish(); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index a6cd1c4f24..764323ef54 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -136,9 +136,9 @@ private void updateShutterViewVisibility() { * @param player The {@link ExoPlayer} to use. */ public void setPlayer(ExoPlayer player) { - if (this.player == player) { - return; - } + // if (this.player == player) { + // return; + // } if (this.player != null) { this.player.removeListener(componentListener); clearVideoView(); diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 3dccad9398..dee050f9c7 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.app.Activity; +import android.content.Intent; import android.app.ActivityManager; import android.content.Context; import android.media.AudioManager; @@ -94,6 +95,7 @@ import java.net.CookieManager; import java.net.CookiePolicy; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.UUID; @@ -132,6 +134,9 @@ class ReactExoplayerView extends FrameLayout implements DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); } + private static Map instances = new HashMap<>(); + private FullScreenDelegate fullScreenDelegate; + private final VideoEventEmitter eventEmitter; private final ReactExoplayerConfig config; private final DefaultBandwidthMeter bandwidthMeter; @@ -150,6 +155,8 @@ class ReactExoplayerView extends FrameLayout implements private long resumePosition; private boolean loadVideoStarted; private boolean isFullscreen; + private String fullScreenOrientation; + private boolean isInFullscreen; private boolean isInBackground; private boolean isPaused; private boolean isBuffering; @@ -171,7 +178,7 @@ class ReactExoplayerView extends FrameLayout implements private double maxHeapAllocationPercent = ReactExoplayerView.DEFAULT_MAX_HEAP_ALLOCATION_PERCENT; private double minBackBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE; private double minBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; - private Handler mainHandler; + // private Handler mainHandler; // Props from React private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS; @@ -254,7 +261,7 @@ public ReactExoplayerView(ThemedReactContext context, ReactExoplayerConfig confi createViews(); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - themedReactContext.addLifecycleEventListener(this); + // themedReactContext.addLifecycleEventListener(this); audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext); } @@ -280,7 +287,7 @@ private void createViews() { addView(exoPlayerView, 0, layoutParams); - mainHandler = new Handler(); + // mainHandler = new Handler(); } @Override @@ -303,7 +310,16 @@ protected void onDetachedFromWindow() { @Override public void onHostResume() { if (!playInBackground || !isInBackground) { - setPlayWhenReady(!isPaused); + // setPlayWhenReady(!isPaused); + if (isInFullscreen) { + if (player != null) { + exoPlayerView.setPlayer(player); + syncPlayerState(); + } + isInFullscreen = false; + } else { + setPlayWhenReady(!isPaused); + } } isInBackground = false; } @@ -324,6 +340,7 @@ public void onHostDestroy() { public void cleanUpResources() { stopPlayback(); + instances.remove(this.getId()); } //BandwidthMeter.EventListener implementation @@ -342,6 +359,29 @@ public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) { } } + public static ReactExoplayerView getViewInstance(Integer uid) { + return instances.get(uid); + } + + public ExoPlayer getPlayer() { + return player; + } + + public void syncPlayerState() { + if (player == null) return; + if (player.getPlaybackState() == Player.STATE_ENDED) { + // Try to get last frame displayed + player.seekTo(player.getDuration() - 200); + player.setPlayWhenReady(true); + } else { + player.setPlayWhenReady(!isPaused); + } + } + + public void registerFullScreenDelegate(FullScreenDelegate delegate) { + this.fullScreenDelegate = delegate; + } + // Internal methods /** @@ -357,6 +397,16 @@ private void togglePlayerControlVisibility() { } } + private void showFullscreen() { + instances.put(this.getId(), this); + Intent intent = new Intent(getContext(), ExoPlayerFullscreenVideoActivity.class); + intent.putExtra(ExoPlayerFullscreenVideoActivity.EXTRA_EXO_PLAYER_VIEW_ID, this.getId()); + intent.putExtra(ExoPlayerFullscreenVideoActivity.EXTRA_ORIENTATION, this.fullScreenOrientation); + getContext().startActivity(intent); + isInFullscreen = true; + } + + /** * Initializing Player control */ @@ -369,6 +419,7 @@ private void initializePlayerControl() { playerControlView.setPlayer(player); playerControlView.show(); playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container); + playerControlView.findViewById(R.id.exo_fullscreen_button).setOnClickListener(v -> setFullscreen(true)); // Invoking onClick event for exoplayerView exoPlayerView.setOnClickListener(new OnClickListener() { @@ -495,11 +546,12 @@ public boolean shouldContinueLoading(long playbackPositionUs, long bufferedDurat private void startBufferCheckTimer() { Player player = this.player; VideoEventEmitter eventEmitter = this.eventEmitter; - Handler mainHandler = this.mainHandler; + // Handler mainHandler = this.mainHandler; } private void initializePlayer() { + themedReactContext.addLifecycleEventListener(this); ReactExoplayerView self = this; Activity activity = themedReactContext.getCurrentActivity(); // This ensures all props have been settled, to avoid async racing conditions. @@ -858,9 +910,9 @@ private void stopPlayback() { } private void onStopPlayback() { - if (isFullscreen) { - setFullscreen(false); - } + // if (isFullscreen) { + // setFullscreen(false); + // } audioManager.abandonAudioFocus(this); } @@ -1735,32 +1787,39 @@ public void setFullscreen(boolean fullscreen) { } isFullscreen = fullscreen; - Activity activity = themedReactContext.getCurrentActivity(); - if (activity == null) { - return; - } - Window window = activity.getWindow(); - View decorView = window.getDecorView(); - int uiOptions; + // Activity activity = themedReactContext.getCurrentActivity(); + // if (activity == null) { + // return; + // } + // Window window = activity.getWindow(); + // View decorView = window.getDecorView(); + // int uiOptions; if (isFullscreen) { - if (Util.SDK_INT >= 19) { // 4.4+ - uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION - | SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | SYSTEM_UI_FLAG_FULLSCREEN; - } else { - uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION - | SYSTEM_UI_FLAG_FULLSCREEN; - } + // if (Util.SDK_INT >= 19) { // 4.4+ + // uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION + // | SYSTEM_UI_FLAG_IMMERSIVE_STICKY + // | SYSTEM_UI_FLAG_FULLSCREEN; + // } else { + // uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION + // | SYSTEM_UI_FLAG_FULLSCREEN; + // } eventEmitter.fullscreenWillPresent(); - decorView.setSystemUiVisibility(uiOptions); + // decorView.setSystemUiVisibility(uiOptions); + showFullscreen(); eventEmitter.fullscreenDidPresent(); } else { - uiOptions = View.SYSTEM_UI_FLAG_VISIBLE; + // uiOptions = View.SYSTEM_UI_FLAG_VISIBLE; eventEmitter.fullscreenWillDismiss(); - decorView.setSystemUiVisibility(uiOptions); + // decorView.setSystemUiVisibility(uiOptions); eventEmitter.fullscreenDidDismiss(); + if (fullScreenDelegate != null) { + fullScreenDelegate.closeFullScreen(); + } } } + public void setFullscreenOrientation(String orientation) { + this.fullScreenOrientation = orientation; + } public void setUseTextureView(boolean useTextureView) { boolean finallyUseTextureView = useTextureView && this.drmUUID == null; @@ -1838,4 +1897,8 @@ public void setControls(boolean controls) { } } } + + public interface FullScreenDelegate { + void closeFullScreen(); + } } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 3744fcaea9..a7b7d4ef39 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -71,6 +71,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager - + + + + + diff --git a/android/src/main/res/layout/exo_player_fullscreen_video.xml b/android/src/main/res/layout/exo_player_fullscreen_video.xml new file mode 100644 index 0000000000..49cb934767 --- /dev/null +++ b/android/src/main/res/layout/exo_player_fullscreen_video.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file