diff --git a/README.md b/README.md
index ab9715d4..9c396a2f 100644
--- a/README.md
+++ b/README.md
@@ -489,7 +489,7 @@ Platforms: iOS
* **landscape**
* **portrait**
-Platforms: iOS
+Platforms: Android ExoPlayer, iOS
#### headers
Pass headers to the HTTP client. Can be used for authorization. Headers must be a part of the source object.
diff --git a/android-exoplayer/src/main/AndroidManifest.xml b/android-exoplayer/src/main/AndroidManifest.xml
index 3535ad44..53e507e0 100644
--- a/android-exoplayer/src/main/AndroidManifest.xml
+++ b/android-exoplayer/src/main/AndroidManifest.xml
@@ -1,3 +1,9 @@
+
+
+
diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerFullscreenVideoActivity.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerFullscreenVideoActivity.java
new file mode 100644
index 00000000..d2e6f2d3
--- /dev/null
+++ b/android-exoplayer/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.SimpleExoPlayer;
+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 SimpleExoPlayer 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();
+ }
+}
diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java
index 61cb0dd6..2e2f3169 100644
--- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java
+++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java
@@ -129,9 +129,6 @@ public final class ExoPlayerView extends FrameLayout {
* @param player The {@link SimpleExoPlayer} to use.
*/
public void setPlayer(SimpleExoPlayer player) {
- if (this.player == player) {
- return;
- }
if (this.player != null) {
this.player.removeTextOutput(componentListener);
this.player.removeVideoListener(componentListener);
diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
index fe95fdf4..482ff939 100644
--- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
+++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
@@ -1,8 +1,8 @@
package com.brentvatne.exoplayer;
import android.annotation.SuppressLint;
-import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
@@ -10,7 +10,6 @@ import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
-import android.view.Window;
import android.view.accessibility.CaptioningManager;
import android.widget.FrameLayout;
import android.widget.ImageButton;
@@ -30,7 +29,6 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
@@ -39,7 +37,6 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
@@ -61,8 +58,8 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
+import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
@@ -76,9 +73,10 @@ import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Locale;
-import java.util.UUID;
import java.util.Map;
+import java.util.UUID;
@SuppressLint("ViewConstructor")
class ReactExoplayerView extends FrameLayout implements
@@ -100,6 +98,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;
@@ -118,7 +119,9 @@ class ReactExoplayerView extends FrameLayout implements
private long resumePosition;
private boolean loadVideoStarted;
private boolean isFullscreen;
+ private String fullScreenOrientation;
private boolean isInBackground;
+ private boolean isInFullscreen;
private boolean isPaused;
private boolean isBuffering;
private boolean muted = false;
@@ -134,8 +137,6 @@ class ReactExoplayerView extends FrameLayout implements
private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
- private Handler mainHandler;
-
// Props from React
private Uri srcUri;
private String extension;
@@ -202,7 +203,6 @@ class ReactExoplayerView extends FrameLayout implements
createViews();
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- themedReactContext.addLifecycleEventListener(this);
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
}
@@ -227,8 +227,6 @@ class ReactExoplayerView extends FrameLayout implements
exoPlayerView.setLayoutParams(layoutParams);
addView(exoPlayerView, 0, layoutParams);
-
- mainHandler = new Handler();
}
@Override
@@ -251,7 +249,15 @@ class ReactExoplayerView extends FrameLayout implements
@Override
public void onHostResume() {
if (!playInBackground || !isInBackground) {
- setPlayWhenReady(!isPaused);
+ if (isInFullscreen) {
+ if (player != null) {
+ exoPlayerView.setPlayer(player);
+ syncPlayerState();
+ }
+ isInFullscreen = false;
+ } else {
+ setPlayWhenReady(!isPaused);
+ }
}
isInBackground = false;
}
@@ -272,6 +278,7 @@ class ReactExoplayerView extends FrameLayout implements
public void cleanUpResources() {
stopPlayback();
+ instances.remove(this.getId());
}
//BandwidthMeter.EventListener implementation
@@ -290,6 +297,29 @@ class ReactExoplayerView extends FrameLayout implements
}
}
+ public static ReactExoplayerView getViewInstance(Integer uid) {
+ return instances.get(uid);
+ }
+
+ public SimpleExoPlayer 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
/**
@@ -305,6 +335,15 @@ class ReactExoplayerView extends FrameLayout implements
}
}
+ 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
*/
@@ -317,6 +356,7 @@ class ReactExoplayerView extends FrameLayout implements
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() {
@@ -389,6 +429,7 @@ class ReactExoplayerView extends FrameLayout implements
}
private void initializePlayer() {
+ themedReactContext.addLifecycleEventListener(this);
ReactExoplayerView self = this;
// This ensures all props have been settled, to avoid async racing conditions.
new Handler().postDelayed(new Runnable() {
@@ -411,7 +452,7 @@ class ReactExoplayerView extends FrameLayout implements
new DefaultRenderersFactory(getContext())
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
player = new SimpleExoPlayer.Builder(getContext(), renderersFactory)
- .setTrackSelector​(trackSelector)
+ .setTrackSelector(trackSelector)
.setBandwidthMeter(bandwidthMeter)
.setLoadControl(defaultLoadControl)
.build();
@@ -641,9 +682,6 @@ class ReactExoplayerView extends FrameLayout implements
}
private void onStopPlayback() {
- if (isFullscreen) {
- setFullscreen(false);
- }
audioManager.abandonAudioFocus(this);
}
@@ -1297,34 +1335,23 @@ class ReactExoplayerView extends FrameLayout implements
return; // Avoid generating events when nothing is changing
}
isFullscreen = fullscreen;
-
- 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;
- }
eventEmitter.fullscreenWillPresent();
- decorView.setSystemUiVisibility(uiOptions);
+ showFullscreen();
eventEmitter.fullscreenDidPresent();
} else {
- uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
eventEmitter.fullscreenWillDismiss();
- decorView.setSystemUiVisibility(uiOptions);
+ if (fullScreenDelegate != null) {
+ fullScreenDelegate.closeFullScreen();
+ }
eventEmitter.fullscreenDidDismiss();
}
}
+ public void setFullscreenOrientation(String orientation) {
+ this.fullScreenOrientation = orientation;
+ }
+
public void setUseTextureView(boolean useTextureView) {
boolean finallyUseTextureView = useTextureView && this.drmUUID == null;
exoPlayerView.setUseTextureView(finallyUseTextureView);
@@ -1394,4 +1421,8 @@ class ReactExoplayerView extends FrameLayout implements
}
}
}
+
+ public interface FullScreenDelegate {
+ void closeFullScreen();
+ }
}
diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java
index eccbee75..02ec4d48 100644
--- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java
+++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java
@@ -64,6 +64,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager
-
+
+
+
+
+
+
diff --git a/android-exoplayer/src/main/res/layout/exo_player_fullscreen_video.xml b/android-exoplayer/src/main/res/layout/exo_player_fullscreen_video.xml
new file mode 100644
index 00000000..671a00ee
--- /dev/null
+++ b/android-exoplayer/src/main/res/layout/exo_player_fullscreen_video.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 3535ad44..42a828cc 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,3 +1,3 @@
+ package="com.brentvatne.react">