From 0863570322b9eaee5946fe0702012f93a41cc2f1 Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Thu, 27 Jul 2023 15:54:32 +0200 Subject: [PATCH 1/4] chore(android): add dependencies for handling system ui and update AGP --- android/gradlew | 12 ++++++++---- example/android/build.gradle | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- example/ios/Podfile.lock | 6 ++++++ example/package.json | 4 +++- example/yarn.lock | 10 ++++++++++ 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/android/gradlew b/android/gradlew index 79a61d42..fcb6fca1 100755 --- a/android/gradlew +++ b/android/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/example/android/build.gradle b/example/android/build.gradle index 6bd66447..30e78b5a 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -19,7 +19,7 @@ buildscript { mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:7.0.4") + classpath("com.android.tools.build:gradle:7.3.1") classpath("com.facebook.react:react-native-gradle-plugin") classpath("de.undercouch:gradle-download-task:4.1.2") // NOTE: Do not place your application dependencies here; they belong diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 669386b8..b1159fc5 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b8c606d6..ad4278f0 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -272,6 +272,8 @@ PODS: - React-jsinspector (0.69.6-2) - React-logger (0.69.6-2): - glog + - react-native-orientation-locker (1.5.0): + - React-Core - react-native-safe-area-context (4.3.1): - RCT-Folly - RCTRequired @@ -397,6 +399,7 @@ DEPENDENCIES: - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) @@ -478,6 +481,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsinspector" React-logger: :path: "../node_modules/react-native/ReactCommon/logger" + react-native-orientation-locker: + :path: "../node_modules/react-native-orientation-locker" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" React-perflogger: @@ -547,6 +552,7 @@ SPEC CHECKSUMS: React-jsiexecutor: c83eedc4e1f901add32d31ea9996313bc0701816 React-jsinspector: 392a5ab785fe93b2d561729feef37a72c46f0aad React-logger: 8daf6ad98634b54430fada5c3710515211c6115a + react-native-orientation-locker: 851f6510d8046ea2f14aa169b1e01fcd309a94ba react-native-safe-area-context: 6c12e3859b6f27b25de4fee8201cfb858432d8de React-perflogger: da6bbceb96facda672b35dc7362dd6bf54ff6774 React-RCTAnimation: aa95cd5bd5418b5d8569b31775e79818110ed90b diff --git a/example/package.json b/example/package.json index f128d97b..0372525c 100644 --- a/example/package.json +++ b/example/package.json @@ -16,7 +16,9 @@ "react": "18.0.0", "react-native": "npm:react-native-tvos@0.69.6-2", "react-native-modal": "13.0.1", + "react-native-orientation-locker": "1.5.0", "react-native-safe-area-context": "4.3.1", - "react-native-screens": "3.15.0" + "react-native-screens": "3.15.0", + "react-native-system-navigation-bar": "^2.6.1" } } diff --git a/example/yarn.lock b/example/yarn.lock index 535add9c..906ad499 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -3478,6 +3478,11 @@ react-native-modal@13.0.1: prop-types "^15.6.2" react-native-animatable "1.3.3" +react-native-orientation-locker@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/react-native-orientation-locker/-/react-native-orientation-locker-1.5.0.tgz#324853937eed4835ecd1c8613ab2206135d908ac" + integrity sha512-4XOCGmNN4BXg5JUFjUuXpsfhPJmbA3LkQilJO1ed+6vL97teTdG2w5IEevKiqL9/hPjeWE8YYtX/YW+yp53hkg== + react-native-safe-area-context@4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.3.1.tgz#5cf97b25b395e0d09bc1f828920cd7da0d792ade" @@ -3491,6 +3496,11 @@ react-native-screens@3.15.0: react-freeze "^1.0.0" warn-once "^0.1.0" +react-native-system-navigation-bar@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/react-native-system-navigation-bar/-/react-native-system-navigation-bar-2.6.1.tgz#cead40f7311fe92be4921808901b7b56e9c763d8" + integrity sha512-IN5n55sEbA4oAvwUqK8oCfKKc4fXJmBzcqC/Eq8bv26gTwzk6oUiPrHtzJCSJvWyCOCt6jKuc9vDWEFTLDOLog== + "react-native@npm:react-native-tvos@0.69.6-2": version "0.69.6-2" resolved "https://registry.yarnpkg.com/react-native-tvos/-/react-native-tvos-0.69.6-2.tgz#d2214283a8ba267a2f08330274f98dbdb22d333a" From 7250ed1030fe01c6b1ead3b59e1cf6e5f993ef08 Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Thu, 27 Jul 2023 16:11:55 +0200 Subject: [PATCH 2/4] feat: add landscape fullscreen example --- .../AppDelegate.mm | 5 + example/src/App.tsx | 15 ++ .../screens/LandscapeFullscreenHandling.tsx | 171 ++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 example/src/screens/LandscapeFullscreenHandling.tsx diff --git a/example/ios/BitmovinPlayerReactNativeExample/AppDelegate.mm b/example/ios/BitmovinPlayerReactNativeExample/AppDelegate.mm index 812a8ce6..390a3eb1 100644 --- a/example/ios/BitmovinPlayerReactNativeExample/AppDelegate.mm +++ b/example/ios/BitmovinPlayerReactNativeExample/AppDelegate.mm @@ -1,3 +1,4 @@ +#import "Orientation.h" #import "AppDelegate.h" #import @@ -29,6 +30,10 @@ @interface AppDelegate () @implementation AppDelegate +- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { + return [Orientation getOrientation]; +} + - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { RCTAppSetupPrepareApp(application); diff --git a/example/src/App.tsx b/example/src/App.tsx index c670e1d6..feaea6e7 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -17,6 +17,7 @@ import CustomPlayback from './screens/CustomPlayback'; import BasicPictureInPicture from './screens/BasicPictureInPicture'; import CustomHtmlUi from './screens/CustomHtmlUi'; import BasicFullscreenHandling from './screens/BasicFullscreenHandling'; +import LandscapeFullscreenHandling from './screens/LandscapeFullscreenHandling'; export type RootStackParamsList = { ExamplesList: { @@ -33,6 +34,9 @@ export type RootStackParamsList = { BasicFullscreenHandling: { navigation: NativeStackNavigationProp; }; + LandscapeFullscreenHandling: { + navigation: NativeStackNavigationProp; + }; SubtitlePlayback: undefined; CustomPlaybackForm: undefined; CustomPlayback: { @@ -98,6 +102,10 @@ export default function App() { title: 'Basic Fullscreen handling', routeName: 'BasicFullscreenHandling', }); + stackParams.data.push({ + title: 'Landscape Fullscreen handling', + routeName: 'LandscapeFullscreenHandling', + }); } return ( @@ -184,6 +192,13 @@ export default function App() { options={{ title: 'Basic Fullscreen Handling' }} /> )} + {!Platform.isTV && ( + + )} ); diff --git a/example/src/screens/LandscapeFullscreenHandling.tsx b/example/src/screens/LandscapeFullscreenHandling.tsx new file mode 100644 index 00000000..62994ce7 --- /dev/null +++ b/example/src/screens/LandscapeFullscreenHandling.tsx @@ -0,0 +1,171 @@ +import React, { useCallback, useRef, useState } from 'react'; +import { Platform, StatusBar, StyleSheet, View } from 'react-native'; +import { NativeStackScreenProps } from '@react-navigation/native-stack'; +import { useFocusEffect } from '@react-navigation/native'; +import { + AudioSession, + Event, + FullscreenHandler, + PlayerView, + SourceType, + usePlayer, +} from 'bitmovin-player-react-native'; +import { useTVGestures } from '../hooks'; +import { RootStackParamsList } from '../App'; +import Orientation from 'react-native-orientation-locker'; +import SystemNavigationBar from 'react-native-system-navigation-bar'; + +type LandscapeFullscreenHandlingProps = NativeStackScreenProps< + RootStackParamsList, + 'LandscapeFullscreenHandling' +>; + +function prettyPrint(header: string, obj: any) { + console.log(header, JSON.stringify(obj, null, 2)); +} + +class SampleFullscreenHandler implements FullscreenHandler { + isFullscreenActive: boolean = true; + onFullscreen: (fullscreenMode: boolean) => void; + + constructor( + isFullscreenActive: boolean, + onFullscreen: (fullscreenMode: boolean) => void + ) { + this.isFullscreenActive = isFullscreenActive; + this.onFullscreen = onFullscreen; + } + + enterFullscreen(): void { + this.onFullscreen(true); + this.isFullscreenActive = true; + if (Platform.OS === 'android') { + // Hides navigation and status bar on Android + SystemNavigationBar.stickyImmersive(true); + } else { + // Hides status bar on iOS + StatusBar.setHidden(true); + } + Orientation.lockToLandscape(); + console.log('enter fullscreen'); + } + + exitFullscreen(): void { + this.onFullscreen(false); + if (Platform.OS === 'android') { + // shows navigation and status bar on Android + SystemNavigationBar.stickyImmersive(false); + } else { + // shows status bar on iOS + StatusBar.setHidden(false); + } + this.isFullscreenActive = false; + Orientation.unlockAllOrientations(); + console.log('exit fullscreen'); + } +} + +export default function LandscapeFullscreenHandling({ + navigation, +}: LandscapeFullscreenHandlingProps) { + useTVGestures(); + + const player = usePlayer({ + playbackConfig: { + isAutoplayEnabled: true, + }, + }); + + const [fullscreenMode, setFullscreenMode] = useState(true); + const fullscreenHandler = useRef( + new SampleFullscreenHandler(fullscreenMode, (isFullscreen: boolean) => { + console.log('on fullscreen change'); + setFullscreenMode(isFullscreen); + navigation.setOptions({ headerShown: !isFullscreen }); + }) + ).current; + useFocusEffect( + useCallback(() => { + // iOS audio session must be set to `playback` first otherwise PiP mode won't work. + // + // Usually it's desireable to set the audio's category only once during your app's main component + // initialization. This way you can guarantee that your app's audio category is properly + // configured throughout the whole lifecycle of the application. + AudioSession.setCategory('playback').catch((error) => { + // Handle any native errors that might occur while setting the audio's category. + console.log( + "[LandscapeFullscreen] Failed to set app's audio category to `playback`:\n", + error + ); + }); + // Load desired source configuration + player.load({ + url: + Platform.OS === 'ios' + ? 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8' + : 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd', + type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH, + title: 'Art of Motion', + poster: + 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/poster.jpg', + }); + return () => { + player.destroy(); + }; + }, [player]) + ); + useFocusEffect( + useCallback(() => { + return () => { + fullscreenHandler.exitFullscreen(); + }; + }, [fullscreenHandler]) + ); + + const onEvent = useCallback((event: Event) => { + prettyPrint(`[${event.name}]`, event); + }, []); + + const onError = useCallback(() => { + setFullscreenMode(false); + }, []); + + return ( + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'white', + padding: 20, + }, + player: { + flex: 1, + backgroundColor: 'black', + }, + playerFullscreen: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: 'black', + }, +}); From c8877e6146b11e5f17c3277119bf15ceab4224a2 Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Fri, 28 Jul 2023 11:34:45 +0200 Subject: [PATCH 3/4] feat: hide all system UI componentns in the basic fullscreen example --- example/src/screens/BasicFullscreenHandling.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/example/src/screens/BasicFullscreenHandling.tsx b/example/src/screens/BasicFullscreenHandling.tsx index 6652e5bc..88cc3ecc 100644 --- a/example/src/screens/BasicFullscreenHandling.tsx +++ b/example/src/screens/BasicFullscreenHandling.tsx @@ -12,6 +12,7 @@ import { } from 'bitmovin-player-react-native'; import { useTVGestures } from '../hooks'; import { RootStackParamsList } from '../App'; +import SystemNavigationBar from 'react-native-system-navigation-bar'; type BasicFullscreenHandlingProps = NativeStackScreenProps< RootStackParamsList, @@ -36,14 +37,26 @@ class SampleFullscreenHandler implements FullscreenHandler { enterFullscreen(): void { this.isFullscreenActive = true; - StatusBar.setHidden(true); + if (Platform.OS === 'android') { + // Hides navigation and status bar on Android + SystemNavigationBar.stickyImmersive(true); + } else { + // Hides status bar on iOS + StatusBar.setHidden(true); + } console.log('enter fullscreen'); this.onFullscreen(true); } exitFullscreen(): void { this.isFullscreenActive = false; - StatusBar.setHidden(false); + if (Platform.OS === 'android') { + // shows navigation and status bar on Android + SystemNavigationBar.stickyImmersive(false); + } else { + // shows status bar on iOS + StatusBar.setHidden(false); + } console.log('exit fullscreen'); this.onFullscreen(false); } From 05216faa63a573b74b77dacc8dbf4f258615e835 Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Fri, 28 Jul 2023 11:59:17 +0200 Subject: [PATCH 4/4] chore: correct oder of statements for fullscreenhandling in the Landscape fullscreen example --- example/src/screens/LandscapeFullscreenHandling.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/example/src/screens/LandscapeFullscreenHandling.tsx b/example/src/screens/LandscapeFullscreenHandling.tsx index 62994ce7..09415ed1 100644 --- a/example/src/screens/LandscapeFullscreenHandling.tsx +++ b/example/src/screens/LandscapeFullscreenHandling.tsx @@ -37,7 +37,6 @@ class SampleFullscreenHandler implements FullscreenHandler { } enterFullscreen(): void { - this.onFullscreen(true); this.isFullscreenActive = true; if (Platform.OS === 'android') { // Hides navigation and status bar on Android @@ -48,10 +47,11 @@ class SampleFullscreenHandler implements FullscreenHandler { } Orientation.lockToLandscape(); console.log('enter fullscreen'); + this.onFullscreen(true); } exitFullscreen(): void { - this.onFullscreen(false); + this.isFullscreenActive = false; if (Platform.OS === 'android') { // shows navigation and status bar on Android SystemNavigationBar.stickyImmersive(false); @@ -59,9 +59,9 @@ class SampleFullscreenHandler implements FullscreenHandler { // shows status bar on iOS StatusBar.setHidden(false); } - this.isFullscreenActive = false; Orientation.unlockAllOrientations(); console.log('exit fullscreen'); + this.onFullscreen(false); } }