From 91d27a6009a88e2d870f6a1cdbba04cd405bd85d Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Tue, 25 Jun 2024 08:55:32 +0200 Subject: [PATCH] feat: add plugins management (#3909) --- .../exoplayer/ReactExoplayerView.java | 6 + .../exoplayer/ReactExoplayerViewManager.java | 3 + .../java/com/brentvatne/react/RNVPlugin.kt | 22 ++ .../react/ReactNativeVideoManager.kt | 71 +++++ docs/pages/other/_meta.json | 3 +- docs/pages/other/plugin.md | 125 ++++++++ examples/basic/android/app/build.gradle | 1 + .../java/com/videoplayer/MainApplication.kt | 2 + examples/basic/android/settings.gradle | 3 + examples/basic/ios/Podfile | 1 + examples/basic/ios/Podfile.lock | 32 +- .../ios/videoplayer.xcodeproj/project.pbxproj | 274 +++++++++--------- examples/basic/tsconfig.json | 1 + .../android/build.gradle | 106 +++++++ .../android/gradle.properties | 5 + .../android/src/main/AndroidManifest.xml | 3 + .../android/src/main/AndroidManifestNew.xml | 2 + .../VideoPluginSampleModule.kt | 50 ++++ .../VideoPluginSamplePackage.kt | 19 ++ .../ios/VideoPluginSample-Bridging-Header.h | 2 + .../ios/VideoPluginSample.mm | 14 + .../ios/VideoPluginSample.swift | 67 +++++ .../package.json | 9 + .../react-native-video-plugin-sample.podspec | 42 +++ .../src/index.tsx | 22 ++ ios/Video/RCTVideo.swift | 7 + ios/Video/RNVPlugin.swift | 23 ++ ios/Video/ReactNativeVideoManager.swift | 53 ++++ 28 files changed, 828 insertions(+), 140 deletions(-) create mode 100644 android/src/main/java/com/brentvatne/react/RNVPlugin.kt create mode 100644 android/src/main/java/com/brentvatne/react/ReactNativeVideoManager.kt create mode 100644 docs/pages/other/plugin.md create mode 100644 examples/react-native-video-plugin-sample/android/build.gradle create mode 100644 examples/react-native-video-plugin-sample/android/gradle.properties create mode 100644 examples/react-native-video-plugin-sample/android/src/main/AndroidManifest.xml create mode 100644 examples/react-native-video-plugin-sample/android/src/main/AndroidManifestNew.xml create mode 100644 examples/react-native-video-plugin-sample/android/src/main/java/com/videopluginsample/VideoPluginSampleModule.kt create mode 100644 examples/react-native-video-plugin-sample/android/src/main/java/com/videopluginsample/VideoPluginSamplePackage.kt create mode 100644 examples/react-native-video-plugin-sample/ios/VideoPluginSample-Bridging-Header.h create mode 100644 examples/react-native-video-plugin-sample/ios/VideoPluginSample.mm create mode 100644 examples/react-native-video-plugin-sample/ios/VideoPluginSample.swift create mode 100644 examples/react-native-video-plugin-sample/package.json create mode 100644 examples/react-native-video-plugin-sample/react-native-video-plugin-sample.podspec create mode 100644 examples/react-native-video-plugin-sample/src/index.tsx create mode 100644 ios/Video/RNVPlugin.swift create mode 100644 ios/Video/ReactNativeVideoManager.swift diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index e0a14a71..ef6c8878 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -123,6 +123,7 @@ import com.brentvatne.common.react.VideoEventEmitter; import com.brentvatne.common.toolbox.DebugLog; import com.brentvatne.react.BuildConfig; import com.brentvatne.react.R; +import com.brentvatne.react.ReactNativeVideoManager; import com.brentvatne.receiver.AudioBecomingNoisyReceiver; import com.brentvatne.receiver.BecomingNoisyListener; import com.facebook.react.bridge.LifecycleEventListener; @@ -258,6 +259,9 @@ public class ReactExoplayerView extends FrameLayout implements private long lastDuration = -1; private boolean viewHasDropped = false; + + private String instanceId = String.valueOf(UUID.randomUUID()); + private void updateProgress() { if (player != null) { if (playerControlView != null && isPlayingAd() && controls) { @@ -756,6 +760,7 @@ public class ReactExoplayerView extends FrameLayout implements .setLoadControl(loadControl) .setMediaSourceFactory(mediaSourceFactory) .build(); + ReactNativeVideoManager.Companion.getInstance().onInstanceCreated(instanceId, player); refreshDebugState(); player.addListener(self); player.setVolume(muted ? 0.f : audioVolume * 1); @@ -1150,6 +1155,7 @@ public class ReactExoplayerView extends FrameLayout implements player.removeListener(this); trackSelector = null; + ReactNativeVideoManager.Companion.getInstance().onInstanceRemoved(instanceId, player); player = null; } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index c43349b9..f23ea548 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -20,6 +20,7 @@ import com.brentvatne.common.api.SubtitleStyle; import com.brentvatne.common.react.VideoEventEmitter; import com.brentvatne.common.toolbox.DebugLog; import com.brentvatne.common.toolbox.ReactBridgeUtils; +import com.brentvatne.react.ReactNativeVideoManager; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; @@ -97,12 +98,14 @@ public class ReactExoplayerViewManager extends ViewGroupManager = ArrayList() + private var pluginList: ArrayList = ArrayList() + + /** + * register a new ReactExoplayerViewManager in the managed list + */ + fun registerView(newInstance: ReactExoplayerViewManager): () -> Boolean = + { + if (instanceList.size > 2) { + DebugLog.d(TAG, "multiple Video displayed ?") + } + instanceList.add(newInstance) + } + + /** + * unregister existing ReactExoplayerViewManager in the managed list + */ + fun unregisterView(newInstance: ReactExoplayerViewManager): () -> Boolean = + { + instanceList.remove(newInstance) + } + + /** + * register a new plugin in the managed list + */ + fun registerPlugin(plugin: RNVPlugin) { + pluginList.add(plugin) + return + } + + /** + * unregister a plugin from the managed list + */ + fun unregisterPlugin(plugin: RNVPlugin) { + pluginList.remove(plugin) + return + } + + override fun onInstanceCreated(id: String, player: Any) { + pluginList.forEach { it.onInstanceCreated(id, player) } + } + + override fun onInstanceRemoved(id: String, player: Any) { + pluginList.forEach { it.onInstanceRemoved(id, player) } + } +} diff --git a/docs/pages/other/_meta.json b/docs/pages/other/_meta.json index 2280f458..f980cb56 100644 --- a/docs/pages/other/_meta.json +++ b/docs/pages/other/_meta.json @@ -2,5 +2,6 @@ "caching": "Caching", "misc": "Misc", "debug": "Debugging", - "new-arch": "New Architecture" + "new-arch": "New Architecture", + "plugin": "Plugin (experimental)" } \ No newline at end of file diff --git a/docs/pages/other/plugin.md b/docs/pages/other/plugin.md new file mode 100644 index 00000000..dad1cf7c --- /dev/null +++ b/docs/pages/other/plugin.md @@ -0,0 +1,125 @@ +# Plugin (experimental) + +Since Version 6.4.0, it is possible to create plugins for analytics management and maybe much more. +A sample plugin is available in the repository in: example/react-native-video-plugin-sample. (important FIXME, put sample link) + +## Concept + +Most of the analytics system which tracks player information (bitrate, errors, ...) can be integrated directly with Exoplayer or AVPlayer handles. + +This plugin system allows none intrusive integration of analytics in the react-native-package. It shall be done in native language (kotlin/swift). + +The idea behind this system is to be able to plug an analytics package to react native video without doing any code change (ideally). + +Following documentation will show on how to create a new plugin for react native video + +## Warning and consideration +This is an experiental API, it is subject to change. The api with player is very simple but should be flexible enough to implement analytics system. If you need some metadata, you should implement setter in the new package you are creating. + +As api is flexible, it makes possible to missuse the system. It is necessary to consider the player handle as read-only. If you modify player behavior, we cannot garanty the good behavior of react-native-video package. + +## General + +First you need to create a new react native package: +````shell +npx create-react-native-library@latest react-native-video-custom-analytics +```` + +Both android and iOS implementation expose an interface `RNVPlugin`. +Your `react-native-video-custom-analytics` shall implement this interface and register itself as a plugin for react native video. + +## Android +There is no special requierement for gradle file. +You need two mandatory action to be able to receive player handle + +### 1/ Create the plugin + +First you should instanciate a class which extends `RNVPlugin`. + +The proposed integration implement `RNVPlugin` directly inside the Module file (`VideoPluginSampleModule`). + +The `RNVPlugin` interface only defines 2 functions, see description here under. + +```kotlin + /** + * Function called when a new player is created + * @param id: a random string identifying the player + * @param player: the instantiated player reference + */ + fun onInstanceCreated(id: String, player: Any) + /** + * Function called when a player should be destroyed + * when this callback is called, the plugin shall free all + * resources and release all reference to Player object + * @param id: a random string identifying the player + * @param player: the player to release + */ + fun onInstanceRemoved(id: String, player: Any) + ```` + +### 2/ register the plugin + +To register this allocated class in the main react native video package you should call following function: + +```kotlin +ReactNativeVideoManager.getInstance().registerPlugin(plugin) +``` +The proposed integration register the instanciated class in `createNativeModules` entry point. + +Your native module can now track Player updates directly from Player reference and report to backend. + +## ios + +### 1/ podspec integration + +Your new module shall be able to access to react-native-video package, then we must declare it as a dependency of the new module you are creating. + +```podfile + s.dependency "react-native-video" +```` + +### 2/ Create the plugin + +First you should instanciate a class which extends `RNVPlugin`. + +The proposed integration implement `RNVPlugin` directly inside the entry point of the module file (`VideoPluginSample`). + +The `RNVPlugin` interface only defines 2 functions, see description here under. + +```swift + /** + * Function called when a new player is created + * @param player: the instantiated player reference + */ + func onInstanceCreated(player: Any) + /** + * Function called when a player should be destroyed + * when this callback is called, the plugin shall free all + * resources and release all reference to Player object + * @param player: the player to release + */ + func onInstanceRemoved(player: Any) +``` + +### 3/ Register the plugin + +To register this allocated class in the main react native video package you should register it by calling this function: + +```swift +ReactNativeVideoManager.shared.registerPlugin(plugin: plugin) +``` + +The proposed integration register the instanciated class in file `VideoPluginSample` in the init function: + +```swift +import react_native_video + +... + +override init() { + super.init() + ReactNativeVideoManager.shared.registerPlugin(plugin: self) +} +``` + +Your native module can now track Player updates directly from Player reference and report to backend. diff --git a/examples/basic/android/app/build.gradle b/examples/basic/android/app/build.gradle index bd7dbe4e..124d86d3 100644 --- a/examples/basic/android/app/build.gradle +++ b/examples/basic/android/app/build.gradle @@ -137,6 +137,7 @@ dependencies { } implementation project(':react-native-video') + implementation project(':react-native-video-plugin-sample') constraints { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") { diff --git a/examples/basic/android/app/src/main/java/com/videoplayer/MainApplication.kt b/examples/basic/android/app/src/main/java/com/videoplayer/MainApplication.kt index 4166c3d1..c1eeea47 100644 --- a/examples/basic/android/app/src/main/java/com/videoplayer/MainApplication.kt +++ b/examples/basic/android/app/src/main/java/com/videoplayer/MainApplication.kt @@ -14,6 +14,7 @@ import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.soloader.SoLoader import com.brentvatne.react.ReactVideoPackage +import com.videopluginsample.VideoPluginSamplePackage class MainApplication : Application(), ReactApplication { @@ -24,6 +25,7 @@ class MainApplication : Application(), ReactApplication { // Packages that cannot be autolinked yet can be added manually here, for example: // add(MyReactNativePackage()) add(ReactVideoPackage()) + add(VideoPluginSamplePackage()) } override fun getJSMainModuleName(): String = "src/index" diff --git a/examples/basic/android/settings.gradle b/examples/basic/android/settings.gradle index 60b7d9cb..bfb02b91 100644 --- a/examples/basic/android/settings.gradle +++ b/examples/basic/android/settings.gradle @@ -2,6 +2,9 @@ rootProject.name = 'videoplayer' apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' +include ':react-native-video-plugin-sample' +project (':react-native-video-plugin-sample').projectDir = new File(rootProject.projectDir, '../../react-native-video-plugin-sample/android') + include ':react-native-video' project (':react-native-video').projectDir = new File(rootProject.projectDir, '../../../android') diff --git a/examples/basic/ios/Podfile b/examples/basic/ios/Podfile index 08f3a493..f31e6f8a 100644 --- a/examples/basic/ios/Podfile +++ b/examples/basic/ios/Podfile @@ -46,6 +46,7 @@ target 'videoplayer' do ) pod 'react-native-video', path: '../../..' + pod 'react-native-video-plugin-sample', path: '../../react-native-video-plugin-sample' target 'videoplayerTests' do inherit! :complete diff --git a/examples/basic/ios/Podfile.lock b/examples/basic/ios/Podfile.lock index 62ca951b..f9eea80f 100644 --- a/examples/basic/ios/Podfile.lock +++ b/examples/basic/ios/Podfile.lock @@ -1016,6 +1016,28 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-video-plugin-sample (0.0.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - react-native-video + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-video/Video (6.2.0): - DoubleConversion - glog @@ -1324,6 +1346,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - react-native-video (from `../../..`) + - react-native-video-plugin-sample (from `../../react-native-video-plugin-sample`) - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) @@ -1441,6 +1464,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" react-native-video: :path: "../../.." + react-native-video-plugin-sample: + :path: "../../react-native-video-plugin-sample" React-nativeconfig: :path: "../node_modules/react-native/ReactCommon" React-NativeModulesApple: @@ -1505,12 +1530,12 @@ SPEC CHECKSUMS: ExpoModulesCore: 2731dc119f8c1400636a994df4efbc19522defbd FBLazyVector: 4bc164e5b5e6cfc288d2b5ff28643ea15fa1a589 fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 - glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 + glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f hermes-engine: 01d3e052018c2a13937aca1860fbedbccd4a41b7 libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 - RCT-Folly: 045d6ecaa59d826c5736dfba0b2f4083ff8d79df + RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47 RCTDeprecation: b03c35057846b685b3ccadc9bfe43e349989cdb2 RCTRequired: 194626909cfa8d39ca6663138c417bc6c431648c RCTTypeSafety: 552aff5b8e8341660594db00e53ac889682bc120 @@ -1535,6 +1560,7 @@ SPEC CHECKSUMS: React-logger: 29fa3e048f5f67fe396bc08af7606426d9bd7b5d React-Mapbuffer: 86703e9e4f6522053568300827b436ccc01e1101 react-native-video: 59f262a2d87c998b747ca1f031efb6eba1c156b5 + react-native-video-plugin-sample: d3a93b7ad777cad7fa2c30473de75a2635ce5feb React-nativeconfig: 5d452e509d6fbedc1522e21b566451fc673ac6b7 React-NativeModulesApple: 6560431301ffdab8df6212cc8c8eff779396d8e0 React-perflogger: 32ed45d9cee02cf6639acae34251590dccd30994 @@ -1566,6 +1592,6 @@ SPEC CHECKSUMS: SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 45564236f670899c9739b1581a12b00ead5d391f -PODFILE CHECKSUM: dfb9633fc816e568fd6d90dde654d15acd66faa9 +PODFILE CHECKSUM: a73d485df51877001f2b04a5a4379cfa5a3ba8fa COCOAPODS: 1.15.2 diff --git a/examples/basic/ios/videoplayer.xcodeproj/project.pbxproj b/examples/basic/ios/videoplayer.xcodeproj/project.pbxproj index 2c6b3b56..41daeee5 100644 --- a/examples/basic/ios/videoplayer.xcodeproj/project.pbxproj +++ b/examples/basic/ios/videoplayer.xcodeproj/project.pbxproj @@ -8,15 +8,16 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* videoplayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* videoplayerTests.m */; }; - 11C6209C7B72C624AC36CAD1 /* Pods_videoplayer_videoplayerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 594390EE3512A597F6D2CFC8 /* Pods_videoplayer_videoplayerTests.framework */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 208BB513171FFCC3277F9E0F /* Pods_videoplayer_videoplayerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5C7FBD972AC23420C9CAEF6 /* Pods_videoplayer_videoplayerTests.framework */; }; 20E2D2234B216472515590E5 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B7A50CD29E62AE55CDBAC5 /* ExpoModulesProvider.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 8564D8A0ECE6B35EF7A78EDB /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF6DF30451E50AB13568EFC /* ExpoModulesProvider.swift */; }; - A64041D5CF85945B698F6FD0 /* Pods_videoplayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4610D42AC113C0F184AAF5BC /* Pods_videoplayer.framework */; }; + C57DB7DC75FFA5378D941129 /* Pods_videoplayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 10FD7EDE51B5059CB0982AD2 /* Pods_videoplayer.framework */; }; DA6F026ACB11B4361D7006B9 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 06EB80F4634394ABC14C45DC /* PrivacyInfo.xcprivacy */; }; + EC73F7EE64DE3B7F743B618D /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -33,22 +34,22 @@ 00E356EE1AD99517003FC87E /* videoplayerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = videoplayerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* videoplayerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = videoplayerTests.m; sourceTree = ""; }; + 058979377AFD7ECE5B23DBEB /* Pods-videoplayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer.release.xcconfig"; path = "Target Support Files/Pods-videoplayer/Pods-videoplayer.release.xcconfig"; sourceTree = ""; }; 06EB80F4634394ABC14C45DC /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = videoplayer/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 10FD7EDE51B5059CB0982AD2 /* Pods_videoplayer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_videoplayer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* videoplayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = videoplayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = videoplayer/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = videoplayer/AppDelegate.mm; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = videoplayer/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = videoplayer/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = videoplayer/main.m; sourceTree = ""; }; - 327740C386721461467B91B2 /* Pods-videoplayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer.release.xcconfig"; path = "Target Support Files/Pods-videoplayer/Pods-videoplayer.release.xcconfig"; sourceTree = ""; }; - 4610D42AC113C0F184AAF5BC /* Pods_videoplayer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_videoplayer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5112808AA45F803BD0D3F411 /* Pods-videoplayer-videoplayerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer-videoplayerTests.release.xcconfig"; path = "Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests.release.xcconfig"; sourceTree = ""; }; - 594390EE3512A597F6D2CFC8 /* Pods_videoplayer_videoplayerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_videoplayer_videoplayerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6E6DE29C8B861F4D5A4BBDEB /* Pods-videoplayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer.debug.xcconfig"; path = "Target Support Files/Pods-videoplayer/Pods-videoplayer.debug.xcconfig"; sourceTree = ""; }; + 2F5C4E6DD1564FCB6C9B7B94 /* Pods-videoplayer-videoplayerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer-videoplayerTests.debug.xcconfig"; path = "Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests.debug.xcconfig"; sourceTree = ""; }; 7AF6DF30451E50AB13568EFC /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-videoplayer-videoplayerTests/ExpoModulesProvider.swift"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = videoplayer/LaunchScreen.storyboard; sourceTree = ""; }; + 9C018F4E223E0E71BA85ABC9 /* Pods-videoplayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer.debug.xcconfig"; path = "Target Support Files/Pods-videoplayer/Pods-videoplayer.debug.xcconfig"; sourceTree = ""; }; + A5C7FBD972AC23420C9CAEF6 /* Pods_videoplayer_videoplayerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_videoplayer_videoplayerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B9B7A50CD29E62AE55CDBAC5 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-videoplayer/ExpoModulesProvider.swift"; sourceTree = ""; }; - CBE734469FBE698BF938A7EF /* Pods-videoplayer-videoplayerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer-videoplayerTests.debug.xcconfig"; path = "Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests.debug.xcconfig"; sourceTree = ""; }; + CF1F0C5E1D8D8D557C4C7043 /* Pods-videoplayer-videoplayerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer-videoplayerTests.release.xcconfig"; path = "Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -57,7 +58,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 11C6209C7B72C624AC36CAD1 /* Pods_videoplayer_videoplayerTests.framework in Frameworks */, + 208BB513171FFCC3277F9E0F /* Pods_videoplayer_videoplayerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -65,7 +66,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A64041D5CF85945B698F6FD0 /* Pods_videoplayer.framework in Frameworks */, + EC73F7EE64DE3B7F743B618D /* BuildFile in Frameworks */, + C57DB7DC75FFA5378D941129 /* Pods_videoplayer.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -107,8 +109,8 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 4610D42AC113C0F184AAF5BC /* Pods_videoplayer.framework */, - 594390EE3512A597F6D2CFC8 /* Pods_videoplayer_videoplayerTests.framework */, + 10FD7EDE51B5059CB0982AD2 /* Pods_videoplayer.framework */, + A5C7FBD972AC23420C9CAEF6 /* Pods_videoplayer_videoplayerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -165,10 +167,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 6E6DE29C8B861F4D5A4BBDEB /* Pods-videoplayer.debug.xcconfig */, - 327740C386721461467B91B2 /* Pods-videoplayer.release.xcconfig */, - CBE734469FBE698BF938A7EF /* Pods-videoplayer-videoplayerTests.debug.xcconfig */, - 5112808AA45F803BD0D3F411 /* Pods-videoplayer-videoplayerTests.release.xcconfig */, + 9C018F4E223E0E71BA85ABC9 /* Pods-videoplayer.debug.xcconfig */, + 058979377AFD7ECE5B23DBEB /* Pods-videoplayer.release.xcconfig */, + 2F5C4E6DD1564FCB6C9B7B94 /* Pods-videoplayer-videoplayerTests.debug.xcconfig */, + CF1F0C5E1D8D8D557C4C7043 /* Pods-videoplayer-videoplayerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -188,13 +190,13 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "videoplayerTests" */; buildPhases = ( - C0C880E0881EE98C1792E57D /* [CP] Check Pods Manifest.lock */, + 05848CC282AE20BB2B2AA52D /* [CP] Check Pods Manifest.lock */, E0436766C647BDEAF9FD5ED3 /* [Expo] Configure project */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - E3461B2529EDAA05BCA42787 /* [CP] Embed Pods Frameworks */, - 0765D094F5C79DBE6CC6386B /* [CP] Copy Pods Resources */, + 0F69B47FEB727B4EBBAC0C93 /* [CP] Embed Pods Frameworks */, + 8DEA7E188641F8CD9B4543DD /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -210,15 +212,15 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "videoplayer" */; buildPhases = ( - 66FA7E36D064B737784DDFB7 /* [CP] Check Pods Manifest.lock */, + 4BC7B73D9362CA23BDA1E909 /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 43E82399B51FE7A2CADEE958 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 189A4605ED0B2F88CF1DCFA8 /* [CP] Embed Pods Frameworks */, - F467D0E16E9AD72917847B43 /* [CP] Copy Pods Resources */, + 5580EE7ED1DD133A3CB36FB0 /* [CP] Embed Pods Frameworks */, + 4617BBDCD64674510B35868A /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -302,82 +304,7 @@ shellPath = /bin/sh; shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n"; }; - 0765D094F5C79DBE6CC6386B /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 189A4605ED0B2F88CF1DCFA8 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 43E82399B51FE7A2CADEE958 /* [Expo] Configure project */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-videoplayer/expo-configure-project.sh\"\n"; - }; - 66FA7E36D064B737784DDFB7 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-videoplayer-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C0C880E0881EE98C1792E57D /* [CP] Check Pods Manifest.lock */ = { + 05848CC282AE20BB2B2AA52D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -399,6 +326,115 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 0F69B47FEB727B4EBBAC0C93 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 43E82399B51FE7A2CADEE958 /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-videoplayer/expo-configure-project.sh\"\n"; + }; + 4617BBDCD64674510B35868A /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 4BC7B73D9362CA23BDA1E909 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-videoplayer-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 5580EE7ED1DD133A3CB36FB0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8DEA7E188641F8CD9B4543DD /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; E0436766C647BDEAF9FD5ED3 /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -418,40 +454,6 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-videoplayer-videoplayerTests/expo-configure-project.sh\"\n"; }; - E3461B2529EDAA05BCA42787 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - F467D0E16E9AD72917847B43 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; FD10A7F022414F080027D42C /* Start Packager */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -506,7 +508,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CBE734469FBE698BF938A7EF /* Pods-videoplayer-videoplayerTests.debug.xcconfig */; + baseConfigurationReference = 2F5C4E6DD1564FCB6C9B7B94 /* Pods-videoplayer-videoplayerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -534,7 +536,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5112808AA45F803BD0D3F411 /* Pods-videoplayer-videoplayerTests.release.xcconfig */; + baseConfigurationReference = CF1F0C5E1D8D8D557C4C7043 /* Pods-videoplayer-videoplayerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; @@ -559,7 +561,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6E6DE29C8B861F4D5A4BBDEB /* Pods-videoplayer.debug.xcconfig */; + baseConfigurationReference = 9C018F4E223E0E71BA85ABC9 /* Pods-videoplayer.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -587,7 +589,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 327740C386721461467B91B2 /* Pods-videoplayer.release.xcconfig */; + baseConfigurationReference = 058979377AFD7ECE5B23DBEB /* Pods-videoplayer.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/examples/basic/tsconfig.json b/examples/basic/tsconfig.json index 0e31fe10..6ca60036 100644 --- a/examples/basic/tsconfig.json +++ b/examples/basic/tsconfig.json @@ -4,6 +4,7 @@ "compilerOptions": { "paths": { "react-native-video": ["../../src/index"], + "react-native-video-plugin-sample": ["../react-native-video-plugin-sample/src/index"], } }, "jsx": "react", diff --git a/examples/react-native-video-plugin-sample/android/build.gradle b/examples/react-native-video-plugin-sample/android/build.gradle new file mode 100644 index 00000000..796511f6 --- /dev/null +++ b/examples/react-native-video-plugin-sample/android/build.gradle @@ -0,0 +1,106 @@ +buildscript { + // Buildscript is evaluated before everything else so we can't use getExtOrDefault + def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["VideoPluginSample_kotlinVersion"] + + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:7.2.1" + // noinspection DifferentKotlinGradleVersion + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +def reactNativeArchitectures() { + def value = rootProject.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} + +def isNewArchitectureEnabled() { + return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +} + +apply plugin: "com.android.library" +apply plugin: "kotlin-android" + +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" +} + +def getExtOrDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["VideoPluginSample_" + name] +} + +def getExtOrIntegerDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["VideoPluginSample_" + name]).toInteger() +} + +def supportsNamespace() { + def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') + def major = parsed[0].toInteger() + def minor = parsed[1].toInteger() + + // Namespace support was added in 7.3.0 + return (major == 7 && minor >= 3) || major >= 8 +} + +android { + if (supportsNamespace()) { + namespace "com.videopluginsample" + + sourceSets { + main { + manifest.srcFile "src/main/AndroidManifestNew.xml" + } + } + } + + compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + + defaultConfig { + minSdkVersion getExtOrIntegerDefault("minSdkVersion") + targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + + } + + buildTypes { + release { + minifyEnabled false + } + } + + lintOptions { + disable "GradleCompatible" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +repositories { + mavenCentral() + google() +} + +def safeExtGet(prop) { + return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : project.properties["RNVideo_" + prop] +} + +def kotlin_version = getExtOrDefault("kotlinVersion") +def media3_version = safeExtGet('media3Version') + +dependencies { + // For < 0.71, this will be from the local maven repo + // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "androidx.media3:media3-exoplayer:$media3_version" + implementation project(':react-native-video') +} + diff --git a/examples/react-native-video-plugin-sample/android/gradle.properties b/examples/react-native-video-plugin-sample/android/gradle.properties new file mode 100644 index 00000000..bdab6c6b --- /dev/null +++ b/examples/react-native-video-plugin-sample/android/gradle.properties @@ -0,0 +1,5 @@ +VideoPluginSample_kotlinVersion=1.7.0 +VideoPluginSample_minSdkVersion=21 +VideoPluginSample_targetSdkVersion=31 +VideoPluginSample_compileSdkVersion=31 +VideoPluginSample_ndkversion=21.4.7075529 diff --git a/examples/react-native-video-plugin-sample/android/src/main/AndroidManifest.xml b/examples/react-native-video-plugin-sample/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..2a859369 --- /dev/null +++ b/examples/react-native-video-plugin-sample/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/examples/react-native-video-plugin-sample/android/src/main/AndroidManifestNew.xml b/examples/react-native-video-plugin-sample/android/src/main/AndroidManifestNew.xml new file mode 100644 index 00000000..a2f47b60 --- /dev/null +++ b/examples/react-native-video-plugin-sample/android/src/main/AndroidManifestNew.xml @@ -0,0 +1,2 @@ + + diff --git a/examples/react-native-video-plugin-sample/android/src/main/java/com/videopluginsample/VideoPluginSampleModule.kt b/examples/react-native-video-plugin-sample/android/src/main/java/com/videopluginsample/VideoPluginSampleModule.kt new file mode 100644 index 00000000..399deb88 --- /dev/null +++ b/examples/react-native-video-plugin-sample/android/src/main/java/com/videopluginsample/VideoPluginSampleModule.kt @@ -0,0 +1,50 @@ +package com.videopluginsample + +import androidx.media3.common.PlaybackException +import androidx.media3.common.Player +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.util.EventLogger +import com.brentvatne.common.toolbox.DebugLog +import com.brentvatne.react.RNVPlugin +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod + +class VideoPluginSampleModule(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext), RNVPlugin, Player.Listener { + + private val debugEventLogger = EventLogger("RNVPluginSample") + + override fun getName(): String { + return NAME + } + + @ReactMethod + fun setMetadata(promise: Promise) { + promise.resolve(true) + } + + companion object { + const val NAME = "VideoPluginSample" + const val TAG = "VideoPluginSampleModule" + } + + override fun onPlayerError(error: PlaybackException) { + DebugLog.e(TAG, "onPlayerError: " + error.errorCodeName) + } + + + override fun onInstanceCreated(id: String, player: Any) { + if (player is ExoPlayer) { + player.addAnalyticsListener(debugEventLogger) + player.addListener(this) + } + } + + override fun onInstanceRemoved(id: String, player: Any) { + if (player is ExoPlayer) { + player.removeAnalyticsListener(debugEventLogger) + } + } +} diff --git a/examples/react-native-video-plugin-sample/android/src/main/java/com/videopluginsample/VideoPluginSamplePackage.kt b/examples/react-native-video-plugin-sample/android/src/main/java/com/videopluginsample/VideoPluginSamplePackage.kt new file mode 100644 index 00000000..18300543 --- /dev/null +++ b/examples/react-native-video-plugin-sample/android/src/main/java/com/videopluginsample/VideoPluginSamplePackage.kt @@ -0,0 +1,19 @@ +package com.videopluginsample + +import com.brentvatne.react.ReactNativeVideoManager +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class VideoPluginSamplePackage : ReactPackage { + override fun createNativeModules(reactContext: ReactApplicationContext): List { + val plugin = VideoPluginSampleModule(reactContext) + ReactNativeVideoManager.getInstance().registerPlugin(plugin) + return listOf(plugin) + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return emptyList() + } +} diff --git a/examples/react-native-video-plugin-sample/ios/VideoPluginSample-Bridging-Header.h b/examples/react-native-video-plugin-sample/ios/VideoPluginSample-Bridging-Header.h new file mode 100644 index 00000000..dea7ff6b --- /dev/null +++ b/examples/react-native-video-plugin-sample/ios/VideoPluginSample-Bridging-Header.h @@ -0,0 +1,2 @@ +#import +#import diff --git a/examples/react-native-video-plugin-sample/ios/VideoPluginSample.mm b/examples/react-native-video-plugin-sample/ios/VideoPluginSample.mm new file mode 100644 index 00000000..1ecf4806 --- /dev/null +++ b/examples/react-native-video-plugin-sample/ios/VideoPluginSample.mm @@ -0,0 +1,14 @@ +#import + +@interface RCT_EXTERN_MODULE(VideoPluginSample, NSObject) + +RCT_EXTERN_METHOD(setMetadata: + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + ++ (BOOL)requiresMainQueueSetup +{ + return YES; +} + +@end diff --git a/examples/react-native-video-plugin-sample/ios/VideoPluginSample.swift b/examples/react-native-video-plugin-sample/ios/VideoPluginSample.swift new file mode 100644 index 00000000..87ade70d --- /dev/null +++ b/examples/react-native-video-plugin-sample/ios/VideoPluginSample.swift @@ -0,0 +1,67 @@ +import react_native_video +import AVFoundation +import AVKit + +@objc(VideoPluginSample) +class VideoPluginSample: NSObject, RNVPlugin { + private var _playerRateChangeObserver: NSKeyValueObservation? + private var _playerCurrentItemChangeObserver: NSKeyValueObservation? + private var _playerItemStatusObserver: NSKeyValueObservation? + + /** + * create an init function to register the plugin + */ + override init() { + super.init() + ReactNativeVideoManager.shared.registerPlugin(plugin: self) + } + + + @objc(withResolver:withRejecter:) + func setMetadata(resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void { + resolve(true) + } + + /* + * Handlers called on player creation and destructon + */ + func onInstanceCreated(id: String, player: Any) { + if player is AVPlayer { + let avPlayer = player as! AVPlayer + NSLog("plug onInstanceCreated") + _playerRateChangeObserver = avPlayer.observe(\.rate, options: [.old], changeHandler: handlePlaybackRateChange) + _playerCurrentItemChangeObserver = avPlayer.observe(\.currentItem, options: [.old], changeHandler: handleCurrentItemChange) + + } + } + + func onInstanceRemoved(id: String, player: Any) { + if player is AVPlayer { + let avPlayer = player as! AVPlayer + NSLog("plug onInstanceRemoved") + _playerRateChangeObserver?.invalidate() + _playerCurrentItemChangeObserver?.invalidate() + } + } + + /** + * custom functions to be able to track AVPlayer state change + */ + func handlePlaybackRateChange(player: AVPlayer, change: NSKeyValueObservedChange) { + NSLog("plugin: handlePlaybackRateChange \(change.oldValue)") + } + + func handlePlayerItemStatusChange(playerItem: AVPlayerItem, change _: NSKeyValueObservedChange) { + NSLog("plugin: handlePlayerItemStatusChange \(playerItem.status)") + } + + func handleCurrentItemChange(player: AVPlayer, change: NSKeyValueObservedChange) { + NSLog("plugin: handleCurrentItemChange \(player.currentItem)") + guard let playerItem = player.currentItem else { + _playerItemStatusObserver?.invalidate() + return + } + + _playerItemStatusObserver = playerItem.observe(\.status, options: [.new, .old], changeHandler: handlePlayerItemStatusChange) + } +} diff --git a/examples/react-native-video-plugin-sample/package.json b/examples/react-native-video-plugin-sample/package.json new file mode 100644 index 00000000..6e0b2c30 --- /dev/null +++ b/examples/react-native-video-plugin-sample/package.json @@ -0,0 +1,9 @@ +{ + "name": "react-native-video-plugin-sample", + "version": "0.0.0", + "description": "sample subpackage for react native video plugin", + "main": "src/index", + "author": " <> ()", + "license": "UNLICENSED", + "homepage": "#readme" +} diff --git a/examples/react-native-video-plugin-sample/react-native-video-plugin-sample.podspec b/examples/react-native-video-plugin-sample/react-native-video-plugin-sample.podspec new file mode 100644 index 00000000..74bd0919 --- /dev/null +++ b/examples/react-native-video-plugin-sample/react-native-video-plugin-sample.podspec @@ -0,0 +1,42 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + +Pod::Spec.new do |s| + s.name = "react-native-video-plugin-sample" + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.platforms = { :ios => min_ios_version_supported } + s.source = { :git => ".git", :tag => "#{s.version}" } + + s.source_files = "ios/**/*.{h,m,mm,swift}" + s.dependency "react-native-video" + + # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. + # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + + # Don't install the dependencies when we run `pod install` in the old architecture. + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + s.dependency "React-Codegen" + s.dependency "RCT-Folly" + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" + end + end +end diff --git a/examples/react-native-video-plugin-sample/src/index.tsx b/examples/react-native-video-plugin-sample/src/index.tsx new file mode 100644 index 00000000..b727549b --- /dev/null +++ b/examples/react-native-video-plugin-sample/src/index.tsx @@ -0,0 +1,22 @@ +import { NativeModules, Platform } from 'react-native'; + +const LINKING_ERROR = + `The package 'react-native-video-plugin-sample' doesn't seem to be linked. Make sure: \n\n` + + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + + '- You rebuilt the app after installing the package\n' + + '- You are not using Expo Go\n'; + +const VideoPluginSample = NativeModules.VideoPluginSample + ? NativeModules.VideoPluginSample + : new Proxy( + {}, + { + get() { + throw new Error(LINKING_ERROR); + }, + } + ); + +export function multiply(a: number, b: number): Promise { + return VideoPluginSample.multiply(a, b); +} diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 25bae29d..f1b53a58 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -81,6 +81,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } + private let instanceId = UUID().uuidString + private var _isBuffering = false { didSet { onVideoBuffer?(["isBuffering": _isBuffering, "target": reactTag as Any]) @@ -184,6 +186,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH init(eventDispatcher: RCTEventDispatcher!) { super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + ReactNativeVideoManager.shared.registerView(newInstance: self) #if USE_GOOGLE_IMA _imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled) #endif @@ -263,6 +266,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH #if os(iOS) _pip = nil #endif + ReactNativeVideoManager.shared.unregisterView(newInstance: self) } // MARK: - App lifecycle handlers @@ -462,6 +466,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if _player == nil { _player = AVPlayer() + ReactNativeVideoManager.shared.onInstanceCreated(id: instanceId, player: _player) + _player!.replaceCurrentItem(with: playerItem) if _showNotificationControls { @@ -1261,6 +1267,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _selectedAudioTrackCriteria = nil _presentingViewController = nil + ReactNativeVideoManager.shared.onInstanceRemoved(id: instanceId, player: _player) _player = nil _resouceLoaderDelegate = nil _playerObserver.clearPlayer() diff --git a/ios/Video/RNVPlugin.swift b/ios/Video/RNVPlugin.swift new file mode 100644 index 00000000..93016cd5 --- /dev/null +++ b/ios/Video/RNVPlugin.swift @@ -0,0 +1,23 @@ +// +// RNVPlugin.swift +// react-native-video +// + +import Foundation + +public protocol RNVPlugin { + /** + * Function called when a new player is created + * @param id: a random string identifying the player + * @param player: the instantiated player reference + */ + func onInstanceCreated(id: String, player: Any) + /** + * Function called when a player should be destroyed + * when this callback is called, the plugin shall free all + * resources and release all reference to Player object + * @param id: a random string identifying the player + * @param player: the player to release + */ + func onInstanceRemoved(id: String, player: Any) +} diff --git a/ios/Video/ReactNativeVideoManager.swift b/ios/Video/ReactNativeVideoManager.swift new file mode 100644 index 00000000..5057b393 --- /dev/null +++ b/ios/Video/ReactNativeVideoManager.swift @@ -0,0 +1,53 @@ +// +// ReactNativeVideoManager.swift +// react-native-video +// + +import Foundation + +public class ReactNativeVideoManager: RNVPlugin { + private let expectedMaxVideoCount = 10 + + // create a private initializer + private init() {} + + public static let shared: ReactNativeVideoManager = .init() + + var instanceList: [RCTVideo] = Array() + var pluginList: [RNVPlugin] = Array() + + /** + * register a new ReactExoplayerViewManager in the managed list + */ + func registerView(newInstance: RCTVideo) { + if instanceList.count > expectedMaxVideoCount { + DebugLog("multiple Video displayed ?") + } + instanceList.append(newInstance) + } + + /** + * unregister existing ReactExoplayerViewManager in the managed list + */ + func unregisterView(newInstance: RCTVideo) { + if let i = instanceList.firstIndex(of: newInstance) { + instanceList.remove(at: i) + } + } + + /** + * register a new plugin in the managed list + */ + public func registerPlugin(plugin: RNVPlugin) { + pluginList.append(plugin) + return + } + + public func onInstanceCreated(id: String, player: Any) { + pluginList.forEach { it in it.onInstanceCreated(id: id, player: player) } + } + + public func onInstanceRemoved(id: String, player: Any) { + pluginList.forEach { it in it.onInstanceRemoved(id: id, player: player) } + } +}