Skip to content

Commit

Permalink
Merge branch 'master' of github.com:react-native-video/react-native-v…
Browse files Browse the repository at this point in the history
…ideo
  • Loading branch information
freeboub committed May 28, 2024
2 parents 92df10d + c7f4d7b commit aa61388
Show file tree
Hide file tree
Showing 22 changed files with 342 additions and 237 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.facebook.react.bridge.ReadableMap

class ControlsConfig {
var hideSeekBar: Boolean = false
var seekIncrementMS: Int = 10000

companion object {
@JvmStatic
Expand All @@ -13,6 +14,7 @@ class ControlsConfig {

if (src != null) {
config.hideSeekBar = ReactBridgeUtils.safeGetBool(src, "hideSeekBar", false)
config.seekIncrementMS = ReactBridgeUtils.safeGetInt(src, "seekIncrementMS", 10000)
}

return config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ public ExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
adOverlayFrameLayout = new FrameLayout(context);

layout.addView(shutterView, 1, layoutParams);
layout.addView(subtitleLayout, 2, layoutParams);
layout.addView(adOverlayFrameLayout, 3, layoutParams);
layout.addView(adOverlayFrameLayout, 2, layoutParams);

addViewInLayout(layout, 0, aspectRatioParams);
addViewInLayout(subtitleLayout, 1, layoutParams);
}

private void clearVideoView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@ import java.io.File
object RNVSimpleCache {
// TODO: when to release? how to check if cache is released?
private var simpleCache: SimpleCache? = null
var cacheDataSourceFactory: DataSource.Factory? = null

fun setSimpleCache(context: Context, cacheSize: Int, factory: HttpDataSource.Factory) {
if (cacheDataSourceFactory != null || cacheSize <= 0) return
fun setSimpleCache(context: Context, cacheSize: Int) {
if (simpleCache != null || cacheSize <= 0) return
simpleCache = SimpleCache(
File(context.cacheDir, "RNVCache"),
LeastRecentlyUsedCacheEvictor(
cacheSize.toLong() * 1024 * 1024
),
StandaloneDatabaseProvider(context)
)
cacheDataSourceFactory =
CacheDataSource.Factory()
.setCache(simpleCache!!)
.setUpstreamDataSourceFactory(factory)
}

fun getCacheFactory(factory: HttpDataSource.Factory): DataSource.Factory {
if (simpleCache == null) return factory
return CacheDataSource.Factory()
.setCache(simpleCache!!)
.setUpstreamDataSourceFactory(factory)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
import com.brentvatne.receiver.AudioBecomingNoisyReceiver;
import com.brentvatne.receiver.BecomingNoisyListener;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.uimanager.ThemedReactContext;
import com.google.ads.interactivemedia.v3.api.AdError;
Expand All @@ -139,6 +140,7 @@
import java.util.Map;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
Expand Down Expand Up @@ -214,7 +216,7 @@ public class ReactExoplayerView extends FrameLayout implements
private boolean selectTrackWhenReady = false;
private Handler mainHandler;
private Runnable mainRunnable;
private DataSource.Factory cacheDataSourceFactory;
private boolean useCache = false;
private ControlsConfig controlsConfig = new ControlsConfig();

// Props from React
Expand Down Expand Up @@ -443,6 +445,17 @@ public void handleOnBackPressed() {
setPausedModifier(false);
});

//Handling the rewind and forward button click events
ImageButton exoRewind = playerControlView.findViewById(R.id.exo_rew);
ImageButton exoForward = playerControlView.findViewById(R.id.exo_ffwd);
exoRewind.setOnClickListener((View v) -> {
seekTo(player.getCurrentPosition() - controlsConfig.getSeekIncrementMS());
});

exoForward.setOnClickListener((View v) -> {
seekTo(player.getCurrentPosition() + controlsConfig.getSeekIncrementMS());
});

//Handling the pauseButton click event
ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause);
pauseButton.setOnClickListener((View v) ->
Expand Down Expand Up @@ -685,6 +698,15 @@ private void initializePlayer() {
mainHandler.postDelayed(mainRunnable, 1);
}

public void getCurrentPosition(Promise promise) {
if (player != null) {
double currentPosition = player.getCurrentPosition() / 1000;
promise.resolve(currentPosition);
} else {
promise.reject("PLAYER_NOT_AVAILABLE", "Player is not initialized.");
}
}

private void initializePlayerCore(ReactExoplayerView self) {
ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
self.trackSelector = new DefaultTrackSelector(getContext(), videoTrackSelectionFactory);
Expand All @@ -708,8 +730,8 @@ private void initializePlayerCore(ReactExoplayerView self) {
.setAdErrorListener(this)
.build();
DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory);
if (cacheDataSourceFactory != null) {
mediaSourceFactory.setDataSourceFactory(cacheDataSourceFactory);
if (useCache) {
mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)));
}

if (adsLoader != null) {
Expand Down Expand Up @@ -842,7 +864,8 @@ public void onServiceConnected(ComponentName name, IBinder service) {
playbackServiceBinder = (PlaybackServiceBinder) service;

try {
playbackServiceBinder.getService().registerPlayer(player);
playbackServiceBinder.getService().registerPlayer(player,
Objects.requireNonNull((Class<Activity>) (themedReactContext.getCurrentActivity()).getClass()));
} catch (Exception e) {
DebugLog.e(TAG, "Cloud not register ExoPlayer");
}
Expand Down Expand Up @@ -1014,13 +1037,13 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessi
throw new IllegalStateException("cannot open input file" + srcUri);
}
} else if ("file".equals(srcUri.getScheme()) ||
cacheDataSourceFactory == null) {
!useCache) {
mediaSourceFactory = new ProgressiveMediaSource.Factory(
mediaDataSourceFactory
);
} else {
mediaSourceFactory = new ProgressiveMediaSource.Factory(
cacheDataSourceFactory
RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))
);

}
Expand Down Expand Up @@ -2239,12 +2262,11 @@ public void setBufferConfig(BufferConfig config) {
if (bufferConfig.getCacheSize() > 0) {
RNVSimpleCache.INSTANCE.setSimpleCache(
this.getContext(),
bufferConfig.getCacheSize(),
buildHttpDataSourceFactory(false)
bufferConfig.getCacheSize()
);
cacheDataSourceFactory = RNVSimpleCache.INSTANCE.getCacheDataSourceFactory();
useCache = true;
} else {
cacheDataSourceFactory = null;
useCache = false;
}
releasePlayer();
initializePlayer();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.brentvatne.exoplayer

import android.annotation.SuppressLint
import android.app.Activity
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Binder
Expand All @@ -23,6 +25,7 @@ class PlaybackServiceBinder(val service: VideoPlaybackService) : Binder()
class VideoPlaybackService : MediaSessionService() {
private var mediaSessionsList = mutableMapOf<ExoPlayer, MediaSession>()
private var binder = PlaybackServiceBinder(this)
private var sourceActivity: Class<Activity>? = null

// Controls
private val commandSeekForward = SessionCommand(COMMAND_SEEK_FORWARD, Bundle.EMPTY)
Expand All @@ -44,10 +47,11 @@ class VideoPlaybackService : MediaSessionService() {

// Player Registry

fun registerPlayer(player: ExoPlayer) {
fun registerPlayer(player: ExoPlayer, from: Class<Activity>) {
if (mediaSessionsList.containsKey(player)) {
return
}
sourceActivity = from

val mediaSession = MediaSession.Builder(this, player)
.setId("RNVideoPlaybackService_" + player.hashCode())
Expand All @@ -63,6 +67,7 @@ class VideoPlaybackService : MediaSessionService() {
hidePlayerNotification(player)
val session = mediaSessionsList.remove(player)
session?.release()
sourceActivity = null

if (mediaSessionsList.isEmpty()) {
cleanup()
Expand Down Expand Up @@ -110,9 +115,13 @@ class VideoPlaybackService : MediaSessionService() {
return
}

val returnToPlayer = Intent(this, sourceActivity).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
val notificationCompact = NotificationCompat.Builder(this, NOTIFICATION_CHANEL_ID)
.setSmallIcon(androidx.media3.session.R.drawable.media3_icon_circular_play)
.setStyle(MediaStyleNotificationHelper.MediaStyle(session))
.setContentIntent(PendingIntent.getActivity(this, 0, returnToPlayer, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE))
.build()

notificationManager.notify(session.player.hashCode(), notificationCompact)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.brentvatne.react

import com.brentvatne.common.toolbox.ReactBridgeUtils
import com.brentvatne.exoplayer.ReactExoplayerView
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
Expand Down Expand Up @@ -61,6 +62,13 @@ class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextB
}
}

@ReactMethod
fun getCurrentPosition(reactTag: Int, promise: Promise) {
performOnPlayerView(reactTag) {
it?.getCurrentPosition(promise)
}
}

companion object {
private const val REACT_CLASS = "VideoManager"
}
Expand Down
32 changes: 15 additions & 17 deletions android/src/main/res/layout/exo_legacy_player_control_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layoutDirection="ltr"
android:background="#CC000000"
android:background="@color/midnight_black"
android:orientation="vertical">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:paddingTop="@dimen/controller_wrapper_padding_top"
android:orientation="horizontal">

<ImageButton android:id="@+id/exo_prev"
Expand Down Expand Up @@ -42,42 +42,40 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginTop="@dimen/seekBar_wrapper_margin_top"
android:gravity="center_vertical"
android:orientation="horizontal">

<TextView android:id="@+id/exo_position"
android:layout_width="50dp"
android:layout_width="@dimen/position_duration_width"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textSize="@dimen/position_duration_text_size"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingHorizontal="@dimen/position_duration_horizontal_padding"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
android:textColor="@color/silver_gray"/>

<androidx.media3.ui.DefaultTimeBar
android:id="@+id/exo_progress"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="26dp"/>
android:layout_height="@dimen/seekBar_height"/>

<TextView android:id="@+id/exo_duration"
android:layout_width="50dp"
android:layout_width="@dimen/position_duration_width"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textSize="@dimen/position_duration_text_size"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingHorizontal="@dimen/position_duration_horizontal_padding"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
android:textColor="@color/silver_gray"/>

<ImageButton
android:id="@+id/exo_fullscreen"
style="@style/ExoMediaButton.FullScreen"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_margin="4dp"
android:layout_width="@dimen/full_screen_size"
android:layout_height="@dimen/full_screen_size"
android:layout_margin="@dimen/full_screen_margin"
android:scaleType="fitCenter" />
</LinearLayout>

Expand Down
5 changes: 5 additions & 0 deletions android/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="silver_gray">#FFBEBEBE</color>
<color name="midnight_black">#CC000000</color>
</resources>
16 changes: 16 additions & 0 deletions android/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- margin & padding-->
<dimen name="controller_wrapper_padding_top">4dp</dimen>
<dimen name="seekBar_wrapper_margin_top">4dp</dimen>
<dimen name="position_duration_horizontal_padding">4dp</dimen>
<dimen name="full_screen_margin">4dp</dimen>

<!-- width & height-->
<dimen name="position_duration_width">50dp</dimen>
<dimen name="seekBar_height">26dp</dimen>
<dimen name="full_screen_size">30dp</dimen>

<!-- textSize-->
<dimen name="position_duration_text_size">14sp</dimen>
</resources>
9 changes: 9 additions & 0 deletions docs/pages/component/methods.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ tolerance is the max distance in milliseconds from the seconds position that's a

This function will change the volume exactly like [volume](./props#volume) property. default value and range are the same then.

### `getCurrentPosition`

<PlatformsList types={['Android', 'iOS']} />

`getCurrentPosition(): Promise<number>`

This function retrieves and returns the precise current position of the video playback, measured in seconds.
This function will throw an error if player is not initialized.

### Example Usage

```tsx
Expand Down
10 changes: 7 additions & 3 deletions docs/pages/component/props.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,17 @@ A Boolean value that indicates whether the player should automatically delay pla

Adjust the control styles. This prop is need only if `controls={true}` and is an object. See the list of prop supported below.

| Property | Type | Description |
|-------------|---------|--------------------------------------------------------------------------------------|
| hideSeekBar | boolean | The default value is `false`, allowing you to hide the seek bar for live broadcasts. |
| Property | Type | Description |
|-----------------|---------|-----------------------------------------------------------------------------------------|
| hideSeekBar | boolean | The default value is `false`, allowing you to hide the seek bar for live broadcasts. |
| seekIncrementMS | number | The default value is `10000`. You can change the value to increment forward and rewind. |

Example with default values:

```javascript
controlsStyles={{
hideSeekBar: false,
seekIncrementMS: 10000,
}}
```

Expand Down Expand Up @@ -836,6 +838,8 @@ textTracks={[
Controls whether to show media controls in the notification area.
For Android each Video component will have its own notification controls and for iOS only one notification control will be shown for the last Active Video component.

On android this will also allow for external controls, Google Assistant session and other benefits of MediaSession.

You propably want also set `playInBackground` to `true` to keep the video playing when the app is in the background or `playWhenInactive` to `true` to keep the video playing when notifications or the Control Center are in front of the video.

To customize the notification controls you can use `metadata` property in the `source` prop.
Expand Down
2 changes: 2 additions & 0 deletions docs/pages/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Then follow the instructions for your platform to link react-native-video into y
### Standard Method
Run `pod install` in the `ios` directory of your project.

⚠️ from version `6.0.0` the minimum iOS version required is `13.0`. For more information see [updating section](updating.md)

### Enable custom feature in podfile file

Samples available in sample app see [sample pod file](https://github.com/TheWidlarzGroup/react-native-video/blob/9c669a2d8a53df36773fd82ff0917280d0659bc7/examples/basic/ios/Podfile#L34)
Expand Down
Loading

0 comments on commit aa61388

Please sign in to comment.