Skip to content

Commit

Permalink
Fix/fullscreen implementation for modals fix (#58)
Browse files Browse the repository at this point in the history
* allow new interface for safe playback in modals on Android

* simplify modal config
  • Loading branch information
Jmilham21 authored Aug 6, 2024
1 parent 6b0b4f2 commit fb8cd30
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 4 deletions.
2 changes: 2 additions & 0 deletions Example/app/jsx/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import DRMExample from './screens/DRMExample';
import LocalFileExample from './screens/LocalFileExample';
import SourcesExample from './screens/SourcesExample';
import YoutubeExample from './screens/YoutubeExample';
import PlayerInModal from './screens/PlayerInModal';

const Stack = createNativeStackNavigator();

Expand All @@ -23,6 +24,7 @@ export default class App extends Component {
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Single" component={SingleExample} />
<Stack.Screen name="Modal" component={PlayerInModal} />
<Stack.Screen name="List" component={ListExample} />
<Stack.Screen name="DRM" component={DRMExample} />
<Stack.Screen name="Local" component={LocalFileExample} />
Expand Down
1 change: 1 addition & 0 deletions Example/app/jsx/components/Player.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default forwardRef((props, ref) => {
config={{
license: Platform.select({ios: IOS_API_KEY, android: ANDROID_API_KEY}),
backgroundAudioEnabled: true,
fullScreenOnLandscape: false,
styling: {
colors: {},
},
Expand Down
2 changes: 1 addition & 1 deletion Example/app/jsx/screens/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {StyleSheet, View, Text, TouchableOpacity, FlatList} from 'react-native';
import Icons from 'react-native-vector-icons/FontAwesome5';
import {useNavigation} from '@react-navigation/native';

const SCREENS = ['Single', 'List', 'DRM', 'Local', 'Sources', 'Youtube'];
const SCREENS = ['Single', 'Modal', 'List', 'DRM', 'Local', 'Sources', 'Youtube'];

export default () => {
const navigation = useNavigation();
Expand Down
89 changes: 89 additions & 0 deletions Example/app/jsx/screens/PlayerInModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React, { useRef, useState } from 'react';
import Player from '../components/Player';
import PlayerContainer from '../components/PlayerContainer';
import { Alert, Modal, StyleSheet, Text, Pressable, View, StatusBar, Dimensions } from 'react-native';
export const { height } = Dimensions.get('window');


export default () => {
const playerRef = useRef([]);

const [modalVisible, setModalVisible] = useState(false);

let jwConfig = {
"title": "Basic Modal implementation",
"fullScreenOnLandscape": true,
"landscapeOnFullScreen": true,
"playerInModal": true,
"playlist": [
{
"file": "https://content.bitsontherun.com/videos/q1fx20VZ-52qL9xLP.mp4",
}
]
}

return (
<View style={styles.centeredView}>
<Modal
animationType="fade"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
setModalVisible(!modalVisible);
}}>
<View style={styles.modalView}>
<Player
ref={playerRef}
style={{ flex: 1 }}
config={{
autostart: true,
styling: {
colors: {},
},
...jwConfig
}}
/>
</View>
<Pressable
style={[styles.button, styles.buttonClose]}
onPress={() => setModalVisible(false)}>
<Text style={styles.textStyle}>Hide Modal</Text>
</Pressable>
</Modal>
<Pressable
style={[styles.button, styles.buttonOpen]}
onPress={() => setModalVisible(true)}>
<Text style={styles.textStyle}>Show Modal</Text>
</Pressable>

</View>
);
};
const styles = StyleSheet.create({
centeredView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 22,
},
modalView: {
marginTop:height / 3,
height: 300
},
button: {
borderRadius: 20,
padding: 10,
elevation: 2,
},
buttonOpen: {
backgroundColor: '#F194FF',
},
buttonClose: {
backgroundColor: '#2196F3',
},
textStyle: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
});
137 changes: 134 additions & 3 deletions android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
Expand Down Expand Up @@ -96,7 +98,12 @@
import com.jwplayer.pub.api.events.listeners.CastingEvents;
import com.jwplayer.pub.api.events.listeners.PipPluginEvents;
import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents;
import com.jwplayer.pub.api.fullscreen.ExtensibleFullscreenHandler;
import com.jwplayer.pub.api.fullscreen.FullscreenDialog;
import com.jwplayer.pub.api.fullscreen.FullscreenHandler;
import com.jwplayer.pub.api.fullscreen.delegates.DeviceOrientationDelegate;
import com.jwplayer.pub.api.fullscreen.delegates.DialogLayoutDelegate;
import com.jwplayer.pub.api.fullscreen.delegates.SystemUiDelegate;
import com.jwplayer.pub.api.license.LicenseUtil;
import com.jwplayer.pub.api.media.playlists.PlaylistItem;
import com.jwplayer.ui.views.CueMarkerSeekbar;
Expand Down Expand Up @@ -183,6 +190,7 @@ public class RNJWPlayerView extends RelativeLayout implements
Boolean fullScreenOnLandscape = false;
Boolean portraitOnExitFullScreen = false;
Boolean exitFullScreenOnPortrait = false;
Boolean playerInModal = false;

Number currentPlayingIndex;

Expand Down Expand Up @@ -427,12 +435,116 @@ public void setupPlayerView(Boolean backgroundAudioEnabled) {
EventType.PIP_OPEN
);

mPlayer.setFullscreenHandler(new fullscreenHandler());
if (playerInModal) {
mPlayer.setFullscreenHandler(createModalFullscreenHandler());
} else {
mPlayer.setFullscreenHandler(new fullscreenHandler());
}

mPlayer.allowBackgroundAudio(backgroundAudioEnabled);
}
}

/**
* Helper to build the a generic `ExtensibleFullscreenHandler` with small tweaks to play nice with Modals
* @return {@link ExtensibleFullscreenHandler}
*/
private ExtensibleFullscreenHandler createModalFullscreenHandler() {
DeviceOrientationDelegate delegate = getDeviceOrientationDelegate();
FullscreenDialog dialog = new FullscreenDialog(
mActivity,
mActivity,
android.R.style.Theme_Black_NoTitleBar_Fullscreen
);

return new ExtensibleFullscreenHandler(
new DialogLayoutDelegate(
mPlayerView,
dialog
),
delegate,
new SystemUiDelegate(
mActivity,
mActivity.getLifecycle(),
new Handler(),
dialog.getWindow().getDecorView()
)
) {
@Override
public void onFullscreenRequested() {
// if landscape is priorty we have to turn off full-screen portrait before allowing
// the default call for full-screen
mPlayer.allowFullscreenPortrait(!landscapeOnFullScreen);
super.onFullscreenRequested();
// safely set it back on UI thread after work can be finished
final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() -> {
if (mPlayer != null) {
mPlayer.allowFullscreenPortrait(true);
}
}, 100);
WritableMap eventEnterFullscreen = Arguments.createMap();
eventEnterFullscreen.putString("message", "onFullscreenRequested");
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
getId(),
"topFullScreenRequested",
eventEnterFullscreen);
}

@Override
public void onFullscreenExitRequested() {
super.onFullscreenExitRequested();

WritableMap eventExitFullscreen = Arguments.createMap();
eventExitFullscreen.putString("message", "onFullscreenExitRequested");
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
getId(),
"topFullScreenExitRequested",
eventExitFullscreen);
}
};
}

/**
* Add logic here for your custom orientation implementation
*
* @return Default {@link DeviceOrientationDelegate}
*/
private DeviceOrientationDelegate getDeviceOrientationDelegate() {
DeviceOrientationDelegate delegate = new DeviceOrientationDelegate(
mActivity,
mActivity.getLifecycle(),
new Handler()
) {
@Override
public void setFullscreen(boolean fullscreen) {
super.setFullscreen(fullscreen);
}

@Override
public void onAllowRotationChanged(boolean allowRotation) {
super.onAllowRotationChanged(allowRotation);
}

@Override
protected void doRotation(boolean fullscreen, boolean allowFullscreenPortrait) {
super.doRotation(fullscreen, allowFullscreenPortrait);
}

@Override
protected void doRotationListener() {
super.doRotationListener();
}

@Override
public void onAllowFullscreenPortrait(boolean allowFullscreenPortrait) {
super.onAllowFullscreenPortrait(allowFullscreenPortrait);
}
};
delegate.onAllowRotationChanged(true);
return delegate;
}

private class fullscreenHandler implements FullscreenHandler {
ViewGroup mPlayerViewContainer = (ViewGroup) mPlayerView.getParent();
private View mDecorView;
Expand Down Expand Up @@ -504,8 +616,15 @@ public void run() {
mPlayerViewContainer.addView(mPlayerView, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mPlayerView.layout(mPlayerViewContainer.getLeft(), mPlayerViewContainer.getTop(),
mPlayerViewContainer.getRight(), mPlayerViewContainer.getBottom());
// returning from full-screen portrait requires a different measure
if (mActivity.getResources().getConfiguration().orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
) {
mPlayerView.layout(mPlayerView.getLeft(), mPlayerViewContainer.getTop(),
mPlayerViewContainer.getMeasuredWidth(), mPlayerViewContainer.getBottom());
} else {
mPlayerView.layout(mPlayerViewContainer.getLeft(), mPlayerViewContainer.getTop(),
mPlayerViewContainer.getRight(), mPlayerViewContainer.getBottom());
}
}
});

Expand Down Expand Up @@ -761,6 +880,18 @@ private void setupPlayer(ReadableMap prop) {
mPlayerView.fullScreenOnLandscape = fullScreenOnLandscape;
}

if (prop.hasKey("landscapeOnFullScreen")) {
landscapeOnFullScreen = prop.getBoolean("landscapeOnFullScreen");
}

if (prop.hasKey("portraitOnExitFullScreen")) {
portraitOnExitFullScreen = prop.getBoolean("fullScreenOnLandscape");
}

if (prop.hasKey("playerInModal")) {
playerInModal = prop.getBoolean("playerInModal");
}

if (prop.hasKey("exitFullScreenOnPortrait")) {
exitFullScreenOnPortrait = prop.getBoolean("exitFullScreenOnPortrait");
mPlayerView.exitFullScreenOnPortrait = exitFullScreenOnPortrait;
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ declare module "@jwplayer/jwplayer-react-native" {
landscapeOnFullScreen?: boolean;
portraitOnExitFullScreen?: boolean;
exitFullScreenOnPortrait?: boolean;
playerInModal?: boolean;
playlist?: PlaylistItem[];
stretching?: string;
related?: Related;
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ export default class JWPlayer extends Component {
offlineImage: PropTypes.string,
forceFullScreenOnLandscape: PropTypes.bool,
forceLandscapeOnFullScreen: PropTypes.bool,
playerInModal: PropTypes.bool,
enableLockScreenControls: PropTypes.bool,
stretching: PropTypes.oneOf([
'uniform',
Expand Down

0 comments on commit fb8cd30

Please sign in to comment.