diff --git a/node_modules/react-native-video/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/node_modules/react-native-video/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index fe95fdf..c95448a 100644 --- a/node_modules/react-native-video/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/node_modules/react-native-video/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -3,6 +3,7 @@ package com.brentvatne.exoplayer; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; +import android.graphics.Color; import android.media.AudioManager; import android.net.Uri; import android.os.Handler; @@ -14,6 +15,7 @@ import android.view.Window; import android.view.accessibility.CaptioningManager; import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; import com.brentvatne.react.R; import com.brentvatne.receiver.AudioBecomingNoisyReceiver; @@ -317,6 +319,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(!isFullscreen)); // Invoking onClick event for exoplayerView exoPlayerView.setOnClickListener(new OnClickListener() { @@ -327,7 +330,7 @@ class ReactExoplayerView extends FrameLayout implements }); //Handling the playButton click event - ImageButton playButton = playerControlView.findViewById(R.id.exo_play); + ImageButton playButton = playerControlView.findViewById(com.google.android.exoplayer2.R.id.exo_play); playButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -339,7 +342,7 @@ class ReactExoplayerView extends FrameLayout implements }); //Handling the pauseButton click event - ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause); + ImageButton pauseButton = playerControlView.findViewById(com.google.android.exoplayer2.R.id.exo_pause); pauseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -736,6 +739,7 @@ class ReactExoplayerView extends FrameLayout implements @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState="; + eventEmitter.playbackRateChange(playWhenReady && playbackState == com.google.android.exoplayer2.ExoPlayer.STATE_READY ? 1.0f : 0.0f); switch (playbackState) { case Player.STATE_IDLE: text += "idle"; @@ -1305,6 +1309,7 @@ class ReactExoplayerView extends FrameLayout implements Window window = activity.getWindow(); View decorView = window.getDecorView(); int uiOptions; + ImageView fullscreenButton = playerControlView.findViewById(R.id.exo_fullscreen_icon); if (isFullscreen) { if (Util.SDK_INT >= 19) { // 4.4+ uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION @@ -1316,11 +1321,14 @@ class ReactExoplayerView extends FrameLayout implements } eventEmitter.fullscreenWillPresent(); decorView.setSystemUiVisibility(uiOptions); + decorView.setBackgroundColor(Color.parseColor("#000000")); + fullscreenButton.setImageResource(com.google.android.exoplayer2.R.drawable.exo_controls_fullscreen_exit); eventEmitter.fullscreenDidPresent(); } else { uiOptions = View.SYSTEM_UI_FLAG_VISIBLE; eventEmitter.fullscreenWillDismiss(); decorView.setSystemUiVisibility(uiOptions); + fullscreenButton.setImageResource(com.google.android.exoplayer2.R.drawable.exo_controls_fullscreen_enter); eventEmitter.fullscreenDidDismiss(); } } diff --git a/node_modules/react-native-video/android-exoplayer/src/main/res/layout/exo_player_control_view.xml b/node_modules/react-native-video/android-exoplayer/src/main/res/layout/exo_player_control_view.xml index becee6a..5d4b2cd 100644 --- a/node_modules/react-native-video/android-exoplayer/src/main/res/layout/exo_player_control_view.xml +++ b/node_modules/react-native-video/android-exoplayer/src/main/res/layout/exo_player_control_view.xml @@ -1,5 +1,6 @@ + + + + + diff --git a/node_modules/react-native-video/ios/Video/RCTVideo.m b/node_modules/react-native-video/ios/Video/RCTVideo.m index a757c08..da428cd 100644 --- a/node_modules/react-native-video/ios/Video/RCTVideo.m +++ b/node_modules/react-native-video/ios/Video/RCTVideo.m @@ -449,6 +449,7 @@ static int const RCTVideoUnset = -1; _allowsExternalPlayback = NO; // sideload text tracks + NSLock *mixCompositionLock = [[NSLock alloc] init]; // mixComposition is not thread-safe; lock in the async blocks below AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init]; AVAssetTrack *videoAsset = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject; @@ -466,30 +467,58 @@ static int const RCTVideoUnset = -1; error:nil]; NSMutableArray* validTextTracks = [NSMutableArray array]; + NSLock *validTextTracksLock = [[NSLock alloc] init]; // validTextTracks is not thread-safe + dispatch_group_t textTracksDispatchGroup = dispatch_group_create(); + for (int i = 0; i < _textTracks.count; ++i) { AVURLAsset *textURLAsset; - NSString *textUri = [_textTracks objectAtIndex:i][@"uri"]; + NSDictionary *curTrack = _textTracks[i]; // capture the current track, because we're going to set _textTracks in an async block below + NSString *textUri = curTrack[@"uri"]; + if ([[textUri lowercaseString] hasPrefix:@"http"]) { textURLAsset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:textUri] options:assetOptions]; } else { textURLAsset = [AVURLAsset URLAssetWithURL:[self urlFilePath:textUri] options:nil]; } - AVAssetTrack *textTrackAsset = [textURLAsset tracksWithMediaType:AVMediaTypeText].firstObject; - if (!textTrackAsset) continue; // fix when there's no textTrackAsset - [validTextTracks addObject:[_textTracks objectAtIndex:i]]; - AVMutableCompositionTrack *textCompTrack = [mixComposition - addMutableTrackWithMediaType:AVMediaTypeText - preferredTrackID:kCMPersistentTrackID_Invalid]; - [textCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) - ofTrack:textTrackAsset - atTime:kCMTimeZero - error:nil]; - } - if (validTextTracks.count != _textTracks.count) { - [self setTextTracks:validTextTracks]; - } - - handler([AVPlayerItem playerItemWithAsset:mixComposition]); + + dispatch_group_enter(textTracksDispatchGroup); // completionHandler runs async + + [textURLAsset loadTracksWithMediaType:AVMediaTypeText completionHandler:^(NSArray * trackArr, NSError * error) { + if (error) { + RCTLogError(@"Error from loadTracksWithMediaType: %@", error); + dispatch_group_leave(textTracksDispatchGroup); + return; + } + + AVAssetTrack *textTrackAsset = trackArr.firstObject; + + if (!textTrackAsset) return; + + [validTextTracksLock lock]; // validTextTracks is not thread-safe + [validTextTracks addObject:curTrack]; + [validTextTracksLock unlock]; + + [mixCompositionLock lock]; // mixComposition is not thread-safe + AVMutableCompositionTrack *textCompTrack = [mixComposition + addMutableTrackWithMediaType:AVMediaTypeText + preferredTrackID:kCMPersistentTrackID_Invalid]; + [textCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) + ofTrack:textTrackAsset + atTime:kCMTimeZero + error:nil]; + [mixCompositionLock unlock]; + + dispatch_group_leave(textTracksDispatchGroup); + }]; + } + + dispatch_group_notify(textTracksDispatchGroup, dispatch_get_main_queue(), ^{ + if (validTextTracks.count > 0) { + [self setTextTracks:validTextTracks]; + } + + handler([AVPlayerItem playerItemWithAsset:mixComposition]); + }); } - (void)playerItemForSource:(NSDictionary *)source withCallback:(void(^)(AVPlayerItem *))handler