diff --git a/Example/ios/Podfile b/Example/ios/Podfile index 37855a77..24323885 100644 --- a/Example/ios/Podfile +++ b/Example/ios/Podfile @@ -1,9 +1,12 @@ require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' -platform :ios, '12.0' +platform :ios, '14.0' install! 'cocoapods', :deterministic_uuids => false +$RNJWPlayerUseGoogleIMA = true +$RNJWPlayerUseGoogleCast = true + target 'RNJWPlayer' do config = use_native_modules! diff --git a/Example/ios/Podfile.lock b/Example/ios/Podfile.lock index 8c78b9e8..a8e2f64a 100644 --- a/Example/ios/Podfile.lock +++ b/Example/ios/Podfile.lock @@ -73,15 +73,19 @@ PODS: - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - - google-cast-sdk (4.7.0): - - google-cast-sdk/Core (= 4.7.0) + - google-cast-sdk (4.5.3): + - google-cast-sdk/Core (= 4.5.3) + - GTMSessionFetcher/Core (~> 1.1) - Protobuf (~> 3.13) - - google-cast-sdk/Core (4.7.0): + - google-cast-sdk/Core (4.5.3): + - GTMSessionFetcher/Core (~> 1.1) - Protobuf (~> 3.13) - - JWPlayerKit (4.13.0) + - GoogleAds-IMA-iOS-SDK (3.19.2) + - GTMSessionFetcher/Core (1.7.2) + - JWPlayerKit (4.17.0) - libevent (2.1.12) - OpenSSL-Universal (1.1.1100) - - Protobuf (3.21.12) + - Protobuf (3.25.1) - RCT-Folly (2021.06.28.00-v2): - boost - DoubleConversion @@ -289,14 +293,10 @@ PODS: - React-jsinspector (0.68.1) - React-logger (0.68.1): - glog - - react-native-orientation-locker (1.4.0): + - react-native-orientation-locker (1.5.0): + - React-Core + - react-native-safe-area-context (4.7.2): - React-Core - - react-native-safe-area-context (4.2.5): - - RCT-Folly - - RCTRequired - - RCTTypeSafety - - React - - ReactCommon/turbomodule/core - React-perflogger (0.68.1) - React-RCTActionSheet (0.68.1): - React-Core/RCTActionSheetHeaders (= 0.68.1) @@ -368,18 +368,19 @@ PODS: - React-Core - RNFS (2.16.6): - React - - RNGestureHandler (2.4.2): + - RNGestureHandler (2.12.1): - React-Core - - RNJWPlayer (0.2.36): - - google-cast-sdk (~> 4.7.0) - - JWPlayerKit (~> 4.13.0) - - React - - RNScreens (3.13.1): + - RNJWPlayer (0.2.42): + - google-cast-sdk (~> 4.5.3) + - GoogleAds-IMA-iOS-SDK (~> 3.19.1) + - JWPlayerKit (~> 4.17.0) + - React-Core + - RNScreens (3.25.0): - React-Core - React-RCTImage - - RNVectorIcons (8.1.0): + - RNVectorIcons (10.0.0): - React-Core - - SocketRocket (0.6.0) + - SocketRocket (0.6.1) - Yoga (1.14.0) - YogaKit (1.18.1): - Yoga (~> 1.14) @@ -464,6 +465,8 @@ SPEC REPOS: - FlipperKit - fmt - google-cast-sdk + - GoogleAds-IMA-iOS-SDK + - GTMSessionFetcher - JWPlayerKit - libevent - OpenSSL-Universal @@ -570,11 +573,13 @@ SPEC CHECKSUMS: FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 476ee3e89abb49e07f822b48323c51c57124b572 - google-cast-sdk: 6731e9a206ce43e0f4433d15598ad8a19f0371d5 - JWPlayerKit: cfc3559afa6aed5314e046bce83f35973c2520db + google-cast-sdk: f94c5df87564f71d4342400b8487665e9bed27c6 + GoogleAds-IMA-iOS-SDK: 0e817c05ab26f1b9285c80f4a75e1350a916d50b + GTMSessionFetcher: 5595ec75acf5be50814f81e9189490412bad82ba + JWPlayerKit: b0fd3f2abf99f40f8cf8292d550864be4b5df3f9 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c - Protobuf: 120350fc38646e2dedc26f49ecba778184ea1de2 + Protobuf: d94761c33f1239c0a43a0817ca1a5f7f7c900241 RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8 RCTRequired: 00581111c53531e39e3c6346ef0d2c0cf52a5a37 RCTTypeSafety: 07e03ee7800e7dd65cba8e52ad0c2edb06c96604 @@ -588,8 +593,8 @@ SPEC CHECKSUMS: React-jsiexecutor: 4a4bae5671b064a2248a690cf75957669489d08c React-jsinspector: 218a2503198ff28a085f8e16622a8d8f507c8019 React-logger: f79dd3cc0f9b44f5611c6c7862badd891a862cf8 - react-native-orientation-locker: 2da91e5391971dace445495821c899c111dcad7a - react-native-safe-area-context: ebf8c413eb8b5f7c392a036a315eb7b46b96845f + react-native-orientation-locker: 851f6510d8046ea2f14aa169b1e01fcd309a94ba + react-native-safe-area-context: 7aa8e6d9d0f3100a820efb1a98af68aa747f9284 React-perflogger: 30ab8d6db10e175626069e742eead3ebe8f24fd5 React-RCTActionSheet: 4b45da334a175b24dabe75f856b98fed3dfd6201 React-RCTAnimation: d6237386cb04500889877845b3e9e9291146bc2e @@ -605,14 +610,14 @@ SPEC CHECKSUMS: RNBootSplash: 2830c475d5793d6cf12312d1e8f5f1decb0dd3e8 RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0 RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df - RNGestureHandler: 61628a2c859172551aa2100d3e73d1e57878392f - RNJWPlayer: cd1846f78dc1e996fe7e93462ac140e46ca417c6 - RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19 - RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4 - SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 + RNGestureHandler: c0d04458598fcb26052494ae23dda8f8f5162b13 + RNJWPlayer: 78ba87f0d9c4f4b39c8bbc03fe04d73587acd7e8 + RNScreens: 85d3880b52d34db7b8eeebe2f1a0e807c05e69fa + RNVectorIcons: 8b5bb0fa61d54cd2020af4f24a51841ce365c7e9 + SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: 17cd9a50243093b547c1e539c749928dd68152da YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 72302fbb90a843c0cefdfb43fe4bd7e42dee995b +PODFILE CHECKSUM: 0aa82ae79746645e542b17a02524e6691f65694a -COCOAPODS: 1.11.3 +COCOAPODS: 1.14.3 diff --git a/Example/ios/RNJWPlayer.xcodeproj/project.pbxproj b/Example/ios/RNJWPlayer.xcodeproj/project.pbxproj index f3c83602..db532cfc 100644 --- a/Example/ios/RNJWPlayer.xcodeproj/project.pbxproj +++ b/Example/ios/RNJWPlayer.xcodeproj/project.pbxproj @@ -546,7 +546,7 @@ "\"$(PODS_TARGET_SRCROOT)/include/\"", ); INFOPLIST_FILE = RNJWPlayer/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -620,7 +620,7 @@ "\"$(PODS_TARGET_SRCROOT)/include/\"", ); INFOPLIST_FILE = RNJWPlayer/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Example/package.json b/Example/package.json index a2cb818e..382ecef6 100644 --- a/Example/package.json +++ b/Example/package.json @@ -19,7 +19,7 @@ "react-native-fs": "2.16.6", "react-native-gesture-handler": "2.12.1", "rn-iphone-helper": "2.0.0", - "react-native-jw-media-player": "0.2.39", + "react-native-jw-media-player": "0.2.42", "react-native-orientation-locker": "1.5.0", "react-native-safe-area-context": "4.7.2", "react-native-screens": "3.25.0", diff --git a/RNJWPlayer.podspec b/RNJWPlayer.podspec index 4846805c..df0990f8 100644 --- a/RNJWPlayer.podspec +++ b/RNJWPlayer.podspec @@ -11,9 +11,21 @@ Pod::Spec.new do |s| s.homepage = package['homepage'] s.platform = :ios, "14.0" s.source = { :git => "https://github.com/chaimPaneth/react-native-jw-media-player.git", :tag => "v#{s.version}" } - s.source_files = "ios/RNJWPlayer/*.{h,m}" + s.source_files = "ios/RNJWPlayer/*.{h,m,swift}" s.dependency 'JWPlayerKit', '~> 4.17.0' - s.dependency 'React' + s.dependency 'React-Core' + s.static_framework = true + s.info_plist = { + 'NSBluetoothAlwaysUsageDescription' => 'We will use your Bluetooth for media casting.', + 'NSBluetoothPeripheralUsageDescription' => 'We will use your Bluetooth for media casting.', + 'NSLocalNetworkUsageDescription' => 'We will use the local network to discover Cast-enabled devices on your WiFi network.', + 'Privacy - Local Network Usage Description' => 'We will use the local network to discover Cast-enabled devices on your WiFi network.', + 'NSMicrophoneUsageDescription' => 'We will use your Microphone for media casting.' + } + s.xcconfig = { + 'OTHER_LDFLAGS': '-ObjC', + } + if defined?($RNJWPlayerUseGoogleCast) Pod::UI.puts "RNJWPlayer: enable Google Cast" s.dependency 'google-cast-sdk', '~> 4.5.3' @@ -28,13 +40,5 @@ Pod::Spec.new do |s| 'OTHER_SWIFT_FLAGS' => '$(inherited) -D USE_GOOGLE_IMA' } end - s.static_framework = true - s.info_plist = { - 'NSBluetoothAlwaysUsageDescription' => 'We will use your Bluetooth for media casting.', - 'NSBluetoothPeripheralUsageDescription' => 'We will use your Bluetooth for media casting.', - 'NSLocalNetworkUsageDescription' => 'We will use the local network to discover Cast-enabled devices on your WiFi network.', - 'Privacy - Local Network Usage Description' => 'We will use the local network to discover Cast-enabled devices on your WiFi network.', - 'NSMicrophoneUsageDescription' => 'We will use your Microphone for media casting.' - } end diff --git a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java index 45ec30ab..aadc49ff 100755 --- a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java +++ b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java @@ -311,6 +311,7 @@ public void destroyPlayer() { EventType.FULLSCREEN, EventType.SEEK, EventType.SEEKED, + EventType.PLAYBACK_RATE_CHANGED, EventType.CAPTIONS_LIST, EventType.CAPTIONS_CHANGED, EventType.META, @@ -390,6 +391,7 @@ public void setupPlayerView(Boolean backgroundAudioEnabled) { EventType.FULLSCREEN, EventType.SEEK, EventType.SEEKED, + EventType.PLAYBACK_RATE_CHANGED, EventType.CAPTIONS_LIST, EventType.CAPTIONS_CHANGED, EventType.META, @@ -1336,7 +1338,7 @@ public void onSeeked(SeekedEvent seekedEvent) { @Override public void onPlaybackRateChanged(PlaybackRateChangedEvent playbackRateChangedEvent) { WritableMap event = Arguments.createMap(); - event.putString("message", "onRateChange"); + event.putString("message", "onRateChanged"); event.putDouble("rate", playbackRateChangedEvent.getPlaybackRate()); getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topRateChanged", event); } diff --git a/index.d.ts b/index.d.ts index cb6f8291..42f020b6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -232,39 +232,79 @@ declare module "react-native-jw-media-player" { enableLockScreenControls: boolean; pipEnabled: boolean; } - type NativeError = (event: { nativeEvent: { code: string, error: string } }) => void; - type NativeWarning = (event: { nativeEvent: { code: string, warning: string } }) => void; + interface BaseEvent { + nativeEvent: T; + } + interface SeekEventProps { + position: number; + offset: number; + } + interface SeekedEventProps { + position: number; + } + interface RateChangedEventProps { + rate: number; + at: number; + } + interface TimeEventProps { + position: number; + duration: number; + } + interface ControlBarVisibleEventProps { + visible: boolean; + } + interface PlaylistEventProps { + playlist: PlaylistItem[] + } + interface PlaylistItemEventProps { + playlistItem: PlaylistItem + } + interface PlayerErrorEventProps { + code: string; + error: string; + } + interface PlayerWarningEventProps { + code: string; + warning: string; + } + interface AdEventProps { + client?: string; + reason?: string; + type: number; + } + type NativeError = (event: BaseEvent) => void; + type NativeWarning = (event: BaseEvent) => void; interface PropsType { config: Config; style?: ViewStyle; controls?: boolean; - onPlayerReady?: (event: any) => void; - onPlaylist?: (playlist: PlaylistItem[]) => void; - onBeforePlay?: (event: any) => void; - onBeforeComplete?: (event: any) => void; - onPlay?: (event: any) => void; - onPause?: (event: any) => void; + onPlayerReady?: () => void; + onPlaylist?: (event: BaseEvent) => void; + onBeforePlay?: () => void; + onBeforeComplete?: () => void; + onComplete?: () => void; + onPlay?: () => void; + onPause?: () => void; + onSeek?: (event: BaseEvent) => void; + onSeeked?: (event?: BaseEvent) => void; + onRateChanged?: (event?: BaseEvent) => void; onSetupPlayerError?: NativeError; onPlayerError?: NativeError; onPlayerWarning?: NativeWarning; onPlayerAdError?: NativeError; onPlayerAdWarning?: NativeWarning; - onAdEvent?: (event: {client: string?, reason: string?, type: number}) => void; - onAdTime?: (event: {position: number, duration: number}) => void; - onBuffer?: (event: any) => void; - onTime?: (event: any) => void; - onComplete?: (event: any) => void; - onFullScreenRequested?: (event: any) => void; - onFullScreen?: (event: any) => void; - onFullScreenExitRequested?: (event: any) => void; - onFullScreenExit?: (event: any) => void; - onSeek?: (seek: { position: number; offset: number }) => void; - onSeeked?: (seeked?: { position: number }) => void; - onRateChanged?: (changed?: { rate: number, at: number }) => void; - onPlaylistItem?: (playlistItem: PlaylistItem) => void; - onControlBarVisible?: (event: any) => void; - onPlaylistComplete?: (event: any) => void; - onAudioTracks?: (event: any) => void; + onAdEvent?: (event: BaseEvent) => void; + onAdTime?: (event: BaseEvent) => void; + onBuffer?: () => void; + onTime?: (event: BaseEvent) => void; + onFullScreenRequested?: () => void; + onFullScreen?: () => void; + onFullScreenExitRequested?: () => void; + onFullScreenExit?: () => void; + onControlBarVisible?: (event: BaseEvent) => void; + onPlaylistComplete?: () => void; + onPlaylistItem?: (event: BaseEvent) => void; + onAudioTracks?: () => void; shouldComponentUpdate?: (nextProps: any, nextState: any) => boolean; } diff --git a/index.js b/index.js index fcd89779..99a02c78 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,7 @@ const RNJWPlayerManager = let playerId = 0; const RCT_RNJWPLAYER_REF = 'RNJWPlayerKey'; -const RNJWPlayer = requireNativeComponent('RNJWPlayerView', null); +const RNJWPlayer = requireNativeComponent('RNJWPlayerView'); const JWPlayerStateIOS = { JWPlayerStateUnknown: 0, diff --git a/ios/RNJWPlayer.xcodeproj/project.pbxproj b/ios/RNJWPlayer.xcodeproj/project.pbxproj index 7d3430c0..4a28d982 100644 --- a/ios/RNJWPlayer.xcodeproj/project.pbxproj +++ b/ios/RNJWPlayer.xcodeproj/project.pbxproj @@ -7,13 +7,16 @@ objects = { /* Begin PBXBuildFile section */ - 2A5DC7C0272B5DB6003BF3E4 /* RCTConvert+RNJWPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A5DC7BF272B5DB6003BF3E4 /* RCTConvert+RNJWPlayer.m */; }; 2A9166FF21064ECE00152DD3 /* JWPlayer_iOS_SDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A9166FD21064DE100152DD3 /* JWPlayer_iOS_SDK.framework */; }; 2AA52BE726C144B200AD26AE /* RNJWPlayerViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */; }; - 2AA52BE826C144B200AD26AE /* RNJWPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA52BE426C144B200AD26AE /* RNJWPlayerView.m */; }; 3BC75FCC1E43B1DB0011FBAA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC75FCB1E43B1DB0011FBAA /* UIKit.framework */; }; 3BC75FD11E43B3090011FBAA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC75FD01E43B3090011FBAA /* Foundation.framework */; }; - 86182A342B31877B0040739A /* RNJWPlayerAds.m in Sources */ = {isa = PBXBuildFile; fileRef = 86182A332B31877B0040739A /* RNJWPlayerAds.m */; }; + 860F49FA2B38AD1D00D0FCC4 /* RNJWPlayerAds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 860F49F92B38AD1D00D0FCC4 /* RNJWPlayerAds.swift */; }; + 86182A312B2AFA170040739A /* RNJWPlayerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86182A302B2AFA170040739A /* RNJWPlayerModels.swift */; }; + 8650D60D2B1CD21000DD1C7E /* RNJWPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8650D60C2B1CD21000DD1C7E /* RNJWPlayerView.swift */; }; + 8650D61E2B1D1E1E00DD1C7E /* RNJWPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8650D6192B1D1E1E00DD1C7E /* RNJWPlayerViewController.swift */; }; + 8650D61F2B1D1E1E00DD1C7E /* RCTConvert+RNJWPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8650D61A2B1D1E1E00DD1C7E /* RCTConvert+RNJWPlayer.swift */; }; + 8650D6202B1D1E1E00DD1C7E /* RNJWPlayerViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8650D61B2B1D1E1E00DD1C7E /* RNJWPlayerViewManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -30,17 +33,17 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libRNJWPlayer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNJWPlayer.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 2A5DC7BE272B5DB6003BF3E4 /* RCTConvert+RNJWPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RCTConvert+RNJWPlayer.h"; path = "RNJWPlayer/RCTConvert+RNJWPlayer.h"; sourceTree = ""; }; - 2A5DC7BF272B5DB6003BF3E4 /* RCTConvert+RNJWPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RCTConvert+RNJWPlayer.m"; path = "RNJWPlayer/RCTConvert+RNJWPlayer.m"; sourceTree = ""; }; 2A9166FD21064DE100152DD3 /* JWPlayer_iOS_SDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JWPlayer_iOS_SDK.framework; path = "../../../ios/Pods/JWPlayer-SDK/JWPlayer_iOS_SDK.framework"; sourceTree = ""; }; - 2AA52BE126C144B200AD26AE /* RNJWPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNJWPlayerView.h; path = RNJWPlayer/RNJWPlayerView.h; sourceTree = ""; }; 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNJWPlayerViewManager.m; path = RNJWPlayer/RNJWPlayerViewManager.m; sourceTree = ""; }; - 2AA52BE326C144B200AD26AE /* RNJWPlayerViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNJWPlayerViewManager.h; path = RNJWPlayer/RNJWPlayerViewManager.h; sourceTree = ""; }; - 2AA52BE426C144B200AD26AE /* RNJWPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNJWPlayerView.m; path = RNJWPlayer/RNJWPlayerView.m; sourceTree = ""; }; 3BC75FCB1E43B1DB0011FBAA /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 3BC75FD01E43B3090011FBAA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 86182A332B31877B0040739A /* RNJWPlayerAds.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNJWPlayerAds.m; path = RNJWPlayer/RNJWPlayerAds.m; sourceTree = ""; }; - 86182A352B3187A80040739A /* RNJWPlayerAds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNJWPlayerAds.h; path = RNJWPlayer/RNJWPlayerAds.h; sourceTree = ""; }; + 860F49F92B38AD1D00D0FCC4 /* RNJWPlayerAds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RNJWPlayerAds.swift; path = RNJWPlayer/RNJWPlayerAds.swift; sourceTree = ""; }; + 86182A302B2AFA170040739A /* RNJWPlayerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RNJWPlayerModels.swift; path = RNJWPlayer/RNJWPlayerModels.swift; sourceTree = ""; }; + 86182A322B2AFA660040739A /* RNJWPlayer-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RNJWPlayer-Bridging-Header.h"; path = "RNJWPlayer/RNJWPlayer-Bridging-Header.h"; sourceTree = ""; }; + 8650D60C2B1CD21000DD1C7E /* RNJWPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNJWPlayerView.swift; path = RNJWPlayer/RNJWPlayerView.swift; sourceTree = ""; }; + 8650D6192B1D1E1E00DD1C7E /* RNJWPlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNJWPlayerViewController.swift; path = RNJWPlayer/RNJWPlayerViewController.swift; sourceTree = ""; }; + 8650D61A2B1D1E1E00DD1C7E /* RCTConvert+RNJWPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "RCTConvert+RNJWPlayer.swift"; path = "RNJWPlayer/RCTConvert+RNJWPlayer.swift"; sourceTree = ""; }; + 8650D61B2B1D1E1E00DD1C7E /* RNJWPlayerViewManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNJWPlayerViewManager.swift; path = RNJWPlayer/RNJWPlayerViewManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -78,14 +81,14 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 2AA52BE126C144B200AD26AE /* RNJWPlayerView.h */, - 2AA52BE426C144B200AD26AE /* RNJWPlayerView.m */, - 2AA52BE326C144B200AD26AE /* RNJWPlayerViewManager.h */, + 860F49F92B38AD1D00D0FCC4 /* RNJWPlayerAds.swift */, + 86182A322B2AFA660040739A /* RNJWPlayer-Bridging-Header.h */, 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */, - 86182A352B3187A80040739A /* RNJWPlayerAds.h */, - 86182A332B31877B0040739A /* RNJWPlayerAds.m */, - 2A5DC7BE272B5DB6003BF3E4 /* RCTConvert+RNJWPlayer.h */, - 2A5DC7BF272B5DB6003BF3E4 /* RCTConvert+RNJWPlayer.m */, + 8650D61B2B1D1E1E00DD1C7E /* RNJWPlayerViewManager.swift */, + 86182A302B2AFA170040739A /* RNJWPlayerModels.swift */, + 8650D60C2B1CD21000DD1C7E /* RNJWPlayerView.swift */, + 8650D6192B1D1E1E00DD1C7E /* RNJWPlayerViewController.swift */, + 8650D61A2B1D1E1E00DD1C7E /* RCTConvert+RNJWPlayer.swift */, 134814211AA4EA7D00B7C361 /* Products */, 3BC75FCA1E43B1DB0011FBAA /* Frameworks */, ); @@ -122,6 +125,7 @@ TargetAttributes = { 58B511DA1A9E6C8500147676 = { CreatedOnToolsVersion = 6.1.1; + LastSwiftMigration = 1410; }; }; }; @@ -148,10 +152,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 86182A342B31877B0040739A /* RNJWPlayerAds.m in Sources */, - 2AA52BE826C144B200AD26AE /* RNJWPlayerView.m in Sources */, - 2A5DC7C0272B5DB6003BF3E4 /* RCTConvert+RNJWPlayer.m in Sources */, + 8650D6202B1D1E1E00DD1C7E /* RNJWPlayerViewManager.swift in Sources */, + 8650D61F2B1D1E1E00DD1C7E /* RCTConvert+RNJWPlayer.swift in Sources */, + 8650D61E2B1D1E1E00DD1C7E /* RNJWPlayerViewController.swift in Sources */, + 8650D60D2B1CD21000DD1C7E /* RNJWPlayerView.swift in Sources */, 2AA52BE726C144B200AD26AE /* RNJWPlayerViewManager.m in Sources */, + 860F49FA2B38AD1D00D0FCC4 /* RNJWPlayerAds.swift in Sources */, + 86182A312B2AFA170040739A /* RNJWPlayerModels.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -235,6 +242,7 @@ 58B511F01A9E6C8500147676 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../../../ios/**", @@ -246,6 +254,7 @@ "$(SRCROOT)/../../react-native/React/**", "$(SRCROOT)/../../react-native/React", ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = ""; OTHER_LDFLAGS = ( "-ObjC", @@ -253,12 +262,16 @@ ); PRODUCT_NAME = RNJWPlayer; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; 58B511F11A9E6C8500147676 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../../../ios/**", @@ -270,6 +283,7 @@ "$(SRCROOT)/../../react-native/React/**", "$(SRCROOT)/../../react-native/React", ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = ""; OTHER_LDFLAGS = ( "-ObjC", @@ -277,6 +291,8 @@ ); PRODUCT_NAME = RNJWPlayer; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer-Bridging-Header.h"; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/ios/RNJWPlayer/RCTConvert+RNJWPlayer.h b/ios/RNJWPlayer/RCTConvert+RNJWPlayer.h deleted file mode 100644 index 6392a076..00000000 --- a/ios/RNJWPlayer/RCTConvert+RNJWPlayer.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// RCTConvert+RNJWPlayer.m -// RNJWPlayer -// -// Created by Chaim Paneth on 10/29/21. -// - -#import -#import - -@interface RCTConvert (RNJWPlayer) - -+ (JWAdClient)JWAdClient:(id)json; - -+ (JWInterfaceBehavior)JWInterfaceBehavior:(id)json; - -+ (JWCaptionEdgeStyle)JWCaptionEdgeStyle:(id)json; - -+ (JWPreload)JWPreload:(id)json; - -+ (JWRelatedOnClick)JWRelatedOnClick:(id)json; - -+ (JWRelatedOnComplete)JWRelatedOnComplete:(id)json; - -+ (JWControlType)JWControlType:(id)json; - -@end diff --git a/ios/RNJWPlayer/RCTConvert+RNJWPlayer.m b/ios/RNJWPlayer/RCTConvert+RNJWPlayer.m deleted file mode 100644 index c177c220..00000000 --- a/ios/RNJWPlayer/RCTConvert+RNJWPlayer.m +++ /dev/null @@ -1,61 +0,0 @@ -// -// RCTConvert+RNJWPlayer.m -// RNJWPlayer -// -// Created by Chaim Paneth on 10/29/21. -// - -#import "RCTConvert+RNJWPlayer.h" - -@implementation RCTConvert (RNJWPlayer) - -RCT_ENUM_CONVERTER(JWAdClient, (@{ - @"vast": @(JWAdClientJWPlayer), - @"ima": @(JWAdClientGoogleIMA), - @"ima_dai": @(JWAdClientGoogleIMADAI), -}), JWAdClientUnknown, integerValue) - -RCT_ENUM_CONVERTER(JWInterfaceBehavior, (@{ - @"normal": @(JWInterfaceBehaviorNormal), - @"hidden": @(JWInterfaceBehaviorHidden), - @"onscreen": @(JWInterfaceBehaviorAlwaysOnScreen), -}), JWInterfaceBehaviorNormal, integerValue) - -RCT_ENUM_CONVERTER(JWCaptionEdgeStyle, (@{ - @"none": @(JWCaptionEdgeStyleNone), - @"dropshadow": @(JWCaptionEdgeStyleDropshadow), - @"raised": @(JWCaptionEdgeStyleRaised), - @"depressed": @(JWCaptionEdgeStyleDepressed), - @"uniform": @(JWCaptionEdgeStyleUniform), -}), JWCaptionEdgeStyleUndefined, integerValue) - -RCT_ENUM_CONVERTER(JWPreload, (@{ - @"auto": @(JWPreloadAuto), - @"none": @(JWPreloadNone), -}), JWPreloadNone, integerValue) - -RCT_ENUM_CONVERTER(JWRelatedOnClick, (@{ - @"play": @(JWRelatedOnClickPlay), - @"link": @(JWRelatedOnClickLink), -}), JWRelatedOnClickPlay, integerValue) - -RCT_ENUM_CONVERTER(JWRelatedOnComplete, (@{ - @"show": @(JWRelatedOnCompleteShow), - @"hide": @(JWRelatedOnCompleteHide), - @"autoplay": @(JWRelatedOnCompleteAutoplay), -}), JWRelatedOnCompleteShow, integerValue) - -RCT_ENUM_CONVERTER(JWControlType, (@{ - @"forward": @(JWControlTypeFastForwardButton), - @"rewind": @(JWControlTypeRewindButton), - @"pip": @(JWControlTypePictureInPictureButton), - @"airplay": @(JWControlTypeAirplayButton), - @"chromecast": @(JWControlTypeChromecastButton), - @"next": @(JWControlTypeNextButton), - @"previous": @(JWControlTypePreviousButton), - @"settings": @(JWControlTypeSettingsButton), - @"languages": @(JWControlTypeLanguagesButton), - @"fullscreen": @(JWControlTypeFullscreenButton), -}), JWControlTypeFullscreenButton, integerValue) - -@end diff --git a/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift b/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift new file mode 100644 index 00000000..5ae920b4 --- /dev/null +++ b/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift @@ -0,0 +1,119 @@ +// +// RNJWPlayerViewController.m +// RNJWPlayer +// +// Created by Chaim Paneth on 3/30/22. +// + +import Foundation +import React +import JWPlayerKit + +extension RCTConvert { + + static func JWAdClient(_ value: String) -> JWAdClient { + switch value { + case "vast": + return .JWPlayer + case "ima": + return .GoogleIMA + case "ima_dai": + return .GoogleIMADAI + default: + return .unknown + } + } + + static func JWInterfaceBehavior(_ value: String) -> JWInterfaceBehavior { + switch value { + case "normal": + return .normal + case "hidden": + return .hidden + case "onscreen": + return .alwaysOnScreen + default: + return .normal + } + } + + static func JWCaptionEdgeStyle(_ value: String) -> JWCaptionEdgeStyle { + switch value { + case "none": + return .none + case "dropshadow": + return .dropshadow + case "raised": + return .raised + case "depressed": + return .depressed + case "uniform": + return .uniform + default: + return .undefined + } + } + + static func JWPreload(_ value: String) -> JWPreload { + switch value { + case "auto": + return .auto + case "none": + return .none + default: + return .none + } + } + + static func JWRelatedOnClick(_ value: String) -> JWRelatedOnClick { + switch value { + case "play": + return .play + case "link": + return .link + default: + return .play + } + } + + static func JWRelatedOnComplete(_ value: String) -> JWRelatedOnComplete { + switch value { + case "show": + return .show + case "hide": + return .hide + case "autoplay": + return .autoplay + default: + return .show + } + } + + static func JWControlType(_ value: String) -> JWControlType { + switch value { + case "forward": + return .fastForwardButton + case "rewind": + return .rewindButton + case "pip": + return .pictureInPictureButton + case "airplay": + return .airplayButton + case "chromecast": + return .chromecastButton + case "next": + return .nextButton + case "previous": + return .previousButton + case "settings": + return .settingsButton + case "languages": + return .languagesButton + case "fullscreen": + return .fullscreenButton + default: + return .fullscreenButton + } + } + +} diff --git a/ios/RNJWPlayer/RNJWPlayerViewManager.h b/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h similarity index 67% rename from ios/RNJWPlayer/RNJWPlayerViewManager.h rename to ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h index bd3866ef..78c8d306 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.h +++ b/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h @@ -3,7 +3,3 @@ #else #import "RCTViewManager.h" #endif - -@interface RNJWPlayerViewManager: RCTViewManager - -@end diff --git a/ios/RNJWPlayer/RNJWPlayerAds.h b/ios/RNJWPlayer/RNJWPlayerAds.h deleted file mode 100644 index 114a9ac2..00000000 --- a/ios/RNJWPlayer/RNJWPlayerAds.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// RNJWPlayerAds.m -// RNJWPlayer -// -// Created by Chaim Paneth on 10/29/21. -// - -#import -#import - -@interface RNJWPlayerAds: NSObject - -+ (JWAdvertisingConfig *)configureVASTWithAds:(NSDictionary *)ads error:(JWError **)error; - -+ (JWAdvertisingConfig *)configureIMAWithAds:(NSDictionary *)ads error:(JWError **)error; - -+ (JWAdvertisingConfig *)configureIMADAIWithAds:(NSDictionary *)ads error:(JWError **)error; - -+ (NSArray *)getAdSchedule:(NSDictionary *)ads error:(JWError **)error; - -+ (JWAdRules *)getAdRules:(NSDictionary *)adRulesDict error:(JWError **)error; - -+ (JWAdShownOnSeek)mapStringToJWAdShownOnSeek:(NSString *)seekString; - -+ (JWImaSettings *)getIMASettings:(NSDictionary *)imaSettingsDict; - -+ (JWGoogleDAIStream *)getGoogleDAIStream:(NSDictionary *)googleDAIStreamDict error:(JWError **)error; - -@end diff --git a/ios/RNJWPlayer/RNJWPlayerAds.m b/ios/RNJWPlayer/RNJWPlayerAds.m deleted file mode 100644 index 345cc26f..00000000 --- a/ios/RNJWPlayer/RNJWPlayerAds.m +++ /dev/null @@ -1,279 +0,0 @@ -// -// RNJWPlayerAds.m -// RNJWPlayer -// -// Created by Chaim Paneth on 19/12/2023. -// - -#import "RNJWPlayerAds.h" - -@implementation RNJWPlayerAds - -+ (JWAdvertisingConfig *)configureVASTWithAds:(NSDictionary *)ads error:(JWError **)error { - JWAdsAdvertisingConfigBuilder* adConfigBuilder = [[JWAdsAdvertisingConfigBuilder alloc] init]; - - // Configure VAST specific settings here - // Example: setting ad schedule, tag, etc. - - NSArray* scheduleArray = [self getAdSchedule:ads error:error]; - if (scheduleArray.count > 0) { - [adConfigBuilder schedule:scheduleArray]; - } - - id tag = ads[@"tag"]; - if (tag != nil && (tag != (id)[NSNull null])) { - NSString* encodedString = [tag stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; - NSURL* tagUrl = [NSURL URLWithString:encodedString]; - if (tagUrl != nil) { - [adConfigBuilder tag:tagUrl]; - } - } - - id adVmap = ads[@"adVmap"]; - if (adVmap != nil && (adVmap != (id)[NSNull null])) { - NSString* encodedString = [adVmap stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; - NSURL* adVmapUrl = [NSURL URLWithString:encodedString]; - if (adVmapUrl != nil) { - [adConfigBuilder vmapURL:adVmapUrl]; - } - } - - id openBrowserOnAdClick = ads[@"openBrowserOnAdClick"]; - if (openBrowserOnAdClick != nil && (openBrowserOnAdClick != (id)[NSNull null])) { - [adConfigBuilder openBrowserOnAdClick:openBrowserOnAdClick]; - } - - NSDictionary *adRulesDict = ads[@"adRules"]; - if (adRulesDict) { - JWAdRules* adRules = [self getAdRules:adRulesDict error:error]; - [adConfigBuilder adRules:adRules]; - } - - NSDictionary *adSettingsDict = ads[@"adSettings"]; - if (adSettingsDict) { - JWAdSettings* adSettings = [self getAdSettings:adSettingsDict]; - [adConfigBuilder adSettings:adSettings]; - } - - return [adConfigBuilder buildAndReturnError:error]; -} - -+ (JWAdvertisingConfig *)configureIMAWithAds:(NSDictionary *)ads error:(JWError **)error { -#if !USE_GOOGLE_IMA - assert("Error: GoogleAds-IMA-iOS-SDK is not installed. Add $RNJWPlayerUseGoogleIMA = true; to your podfile"); -#endif - - JWImaAdvertisingConfigBuilder* adConfigBuilder = [[JWImaAdvertisingConfigBuilder alloc] init]; - - // Configure Google IMA specific settings here - // Example: setting ad schedule, tag, IMA settings, etc. - - NSArray* scheduleArray = [self getAdSchedule:ads error:error]; - if (scheduleArray.count > 0) { - [adConfigBuilder schedule:scheduleArray]; - } - - id tag = ads[@"tag"]; - if (tag != nil && (tag != (id)[NSNull null])) { - NSString* encodedString = [tag stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; - NSURL* tagUrl = [NSURL URLWithString:encodedString]; - if (tagUrl != nil) { - [adConfigBuilder tag:tagUrl]; - } - } - - id adVmap = ads[@"adVmap"]; - if (adVmap != nil && (adVmap != (id)[NSNull null])) { - NSString* encodedString = [adVmap stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; - NSURL* adVmapUrl = [NSURL URLWithString:encodedString]; - if (adVmapUrl != nil) { - [adConfigBuilder vmapURL:adVmapUrl]; - } - } - - NSDictionary *adRulesDict = ads[@"adRules"]; - if (adRulesDict) { - JWAdRules* adRules = [self getAdRules:adRulesDict error:error]; - [adConfigBuilder adRules:adRules]; - } - - NSDictionary *imaSettingsDict = ads[@"imaSettings"]; - if (imaSettingsDict) { - JWImaSettings* imaSettings = [self getIMASettings:imaSettingsDict]; - [adConfigBuilder imaSettings:imaSettings]; - } - - return [adConfigBuilder buildAndReturnError:error]; -} - -+ (JWAdvertisingConfig *)configureIMADAIWithAds:(NSDictionary *)ads error:(JWError **)error { -#if !USE_GOOGLE_IMA - assert("Error: GoogleAds-IMA-iOS-SDK is not installed. Add $RNJWPlayerUseGoogleIMA = true; to your podfile"); -#endif - - JWImaDaiAdvertisingConfigBuilder* adConfigBuilder = [[JWImaDaiAdvertisingConfigBuilder alloc] init]; - - // Configure Google IMA DAI specific settings here - // Example: setting stream configuration, friendly obstructions, etc. - - NSDictionary *imaSettingsDict = ads[@"imaSettings"]; - if (imaSettingsDict) { - JWImaSettings* imaSettings = [self getIMASettings:imaSettingsDict]; - [adConfigBuilder imaSettings:imaSettings]; - } - - NSDictionary *googleDAIStreamDict = ads[@"googleDAIStream"]; - if (googleDAIStreamDict) { - JWGoogleDAIStream* googleDAIStream = [self getGoogleDAIStream:googleDAIStreamDict error:error]; - [adConfigBuilder googleDAIStream:googleDAIStream]; - } - - return [adConfigBuilder buildAndReturnError:error]; -} - -+ (NSArray *)getAdSchedule:(NSDictionary *)ads error:(JWError **)error { - id schedule = ads[@"adSchedule"]; - if(schedule != nil && (schedule != (id)[NSNull null])) { - NSArray* scheduleAr = (NSArray*)schedule; - if (scheduleAr.count > 0) { - NSMutableArray * scheduleArray = [[NSMutableArray alloc] init]; - - for (id item in scheduleAr) { - NSString *offsetString = [item objectForKey:@"offset"]; - NSString *tag = [item objectForKey:@"tag"]; - NSString* encodedString = [tag stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; - NSURL* tagUrl = [NSURL URLWithString:encodedString]; - - if (tagUrl != nil) { - JWAdBreakBuilder* adBreakBuilder = [[JWAdBreakBuilder alloc] init]; - JWAdOffset* offset = [JWAdOffset fromString:offsetString]; - - [adBreakBuilder offset:offset]; - [adBreakBuilder tags:@[tagUrl]]; - - JWAdBreak *adBreak = [adBreakBuilder buildAndReturnError:error]; - - [scheduleArray addObject:adBreak]; - } - } - - return scheduleArray; - } - } - - return @[]; -} - -+ (JWAdRules *)getAdRules:(NSDictionary *)adRulesDict error:(JWError **)error { - JWAdRulesBuilder *builder = [[JWAdRulesBuilder alloc] init]; - - NSUInteger startOn = [adRulesDict[@"startOn"] unsignedIntegerValue]; - NSUInteger frequency = [adRulesDict[@"frequency"] unsignedIntegerValue]; - NSUInteger timeBetweenAds = [adRulesDict[@"timeBetweenAds"] unsignedIntegerValue]; - JWAdShownOnSeek startOnSeek = [self mapStringToJWAdShownOnSeek:adRulesDict[@"startOnSeek"]]; - - [builder jwRulesWithStartOn:startOn frequency:frequency timeBetweenAds:timeBetweenAds startOnSeek:startOnSeek]; - - return [builder buildAndReturnError:error]; -} - -+ (JWAdShownOnSeek)mapStringToJWAdShownOnSeek:(NSString *)seekString { - if ([seekString isEqualToString:@"pre"]) { - return JWAdShownOnSeekPre; - } - return JWAdShownOnSeekNone; -} - -+ (JWAdSettings *)getAdSettings:(NSDictionary *)settingsDict { - JWAdSettingsBuilder *builder = [[JWAdSettingsBuilder alloc] init]; - - if (settingsDict[@"allowsBackgroundPlayback"]) { - BOOL allowsBackgroundPlayback = [settingsDict[@"allowsBackgroundPlayback"] boolValue]; - [builder allowsBackgroundPlayback:allowsBackgroundPlayback]; - } - - // Add other settings as needed - - return [builder build]; -} - -+ (JWImaSettings *)getIMASettings:(NSDictionary *)imaSettingsDict { - JWImaSettingsBuilder *builder = [[JWImaSettingsBuilder alloc] init]; - if (imaSettingsDict[@"locale"]) { - [builder locale:imaSettingsDict[@"locale"]]; - } - if (imaSettingsDict[@"ppid"]) { - [builder ppid:imaSettingsDict[@"ppid"]]; - } - if (imaSettingsDict[@"maxRedirects"]) { - [builder maxRedirects:[imaSettingsDict[@"maxRedirects"] unsignedIntegerValue]]; - } - if (imaSettingsDict[@"sessionID"]) { - [builder sessionID:imaSettingsDict[@"sessionID"]]; - } - if (imaSettingsDict[@"debugMode"]) { - [builder debugMode:[imaSettingsDict[@"debugMode"] boolValue]]; - } - return [builder build]; -} - -+ (JWGoogleDAIStream *)getGoogleDAIStream:(NSDictionary *)googleDAIStreamDict error:(JWError **)error { - JWGoogleDAIStreamBuilder *builder = [[JWGoogleDAIStreamBuilder alloc] init]; - if (googleDAIStreamDict[@"videoID"] && googleDAIStreamDict[@"cmsID"]) { - [builder vodStreamInfoWithVideoID:googleDAIStreamDict[@"videoID"] cmsID:googleDAIStreamDict[@"cmsID"]]; - } else if (googleDAIStreamDict[@"assetKey"]) { - [builder liveStreamInfoWithAssetKey:googleDAIStreamDict[@"assetKey"]]; - } - if (googleDAIStreamDict[@"apiKey"]) { - [builder apiKey:googleDAIStreamDict[@"apiKey"]]; - } - if (googleDAIStreamDict[@"adTagParameters"]) { - [builder adTagParameters:googleDAIStreamDict[@"adTagParameters"]]; - } - return [builder buildAndReturnError:error]; -} - -//+ (UIView *)findViewWithId:(NSString *)viewId { -// // needs implementation -//} - -//+ (NSArray *)createFriendlyObstructionsFromArray:(NSArray *)obstructionsArray { -// NSMutableArray *obstructions = [NSMutableArray array]; -// -// for (NSDictionary *obstructionDict in obstructionsArray) { -// NSString *viewId = obstructionDict[@"viewId"]; -// UIView *view = [self findViewWithId:viewId]; // Implement this method to find the view by ID -// JWFriendlyObstructionPurpose purpose = [self mapStringToJWFriendlyObstructionPurpose:obstructionDict[@"purpose"]]; -// NSString *reason = obstructionDict[@"reason"]; -// -// JWFriendlyObstruction *obstruction = [[JWFriendlyObstruction alloc] initWithView:view purpose:purpose reason:reason]; -// [obstructions addObject:obstruction]; -// } -// -// return obstructions; -//} - -//+ (JWFriendlyObstructionPurpose)mapStringToJWFriendlyObstructionPurpose:(NSString *)purposeString { -// if ([purposeString isEqualToString:@"mediaControls"]) { -// return JWFriendlyObstructionPurposeMediaControls; -// } else if ([purposeString isEqualToString:@"closeAd"]) { -// return JWFriendlyObstructionPurposeCloseAd; -// } else if ([purposeString isEqualToString:@"notVisible"]) { -// return JWFriendlyObstructionPurposeNotVisible; -// } -// return JWFriendlyObstructionPurposeOther; -//} - -//+ (JWCompanionAdSlot *)createCompanionAdSlotFromDictionary:(NSDictionary *)companionAdSlotDict { -// UIView *view = [self findViewWithId:companionAdSlotDict[@"viewId"]]; // Implement this method to find the UIView -// CGSize size = CGSizeZero; -// if (companionAdSlotDict[@"size"]) { -// size = CGSizeMake([companionAdSlotDict[@"size"][@"width"] floatValue], -// [companionAdSlotDict[@"size"][@"height"] floatValue]); -// } -// JWCompanionAdSlot *slot = [[JWCompanionAdSlot alloc] initWithView:view size:size]; -// return slot; -//} - -@end - diff --git a/ios/RNJWPlayer/RNJWPlayerAds.swift b/ios/RNJWPlayer/RNJWPlayerAds.swift new file mode 100644 index 00000000..c9687be4 --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerAds.swift @@ -0,0 +1,260 @@ +// +// RNJWPlayerAds.swift +// RNJWPlayer +// +// Created by Chaim Paneth on 24/12/2023. +// + +import Foundation +import JWPlayerKit + +class RNJWPlayerAds { + + // Convert configureVASTWithAds function + static func configureVAST(with ads: [String: Any]) -> JWAdvertisingConfig? { + let adConfigBuilder = JWAdsAdvertisingConfigBuilder() + + // Configure VAST specific settings here + // Example: setting ad schedule, tag, etc. + + if let scheduleArray = getAdSchedule(from: ads), !scheduleArray.isEmpty { + adConfigBuilder.schedule(scheduleArray) + } + + if let tag = ads["tag"] as? String, let encodedString = tag.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed), let tagUrl = URL(string: encodedString) { + adConfigBuilder.tag(tagUrl) + } + + if let adVmap = ads["adVmap"] as? String, let encodedString = adVmap.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed), let adVmapUrl = URL(string: encodedString) { + adConfigBuilder.vmapURL(adVmapUrl) + } + + if let openBrowserOnAdClick = ads["openBrowserOnAdClick"] as? Bool { + adConfigBuilder.openBrowserOnAdClick(openBrowserOnAdClick) + } + + if let adRulesDict = ads["adRules"] as? [String: Any], let adRules = getAdRules(from: adRulesDict) { + adConfigBuilder.adRules(adRules) + } + + if let adSettingsDict = ads["adSettings"] as? [String: Any] { + if let adSettings = getAdSettings(from: adSettingsDict) { + adConfigBuilder.adSettings(adSettings) + } + } + + return try? adConfigBuilder.build() + } + + // Convert configureIMAWithAds function + static func configureIMA(with ads: [String: Any]) -> JWAdvertisingConfig? { + // Ensure Google IMA SDK is available +#if !USE_GOOGLE_IMA + assertionFailure("Error: GoogleAds-IMA-iOS-SDK is not installed. Add $RNJWPlayerUseGoogleIMA = true; to your podfile") +#endif + + let adConfigBuilder = JWImaAdvertisingConfigBuilder() + + // Configure Google IMA specific settings here + // Example: setting ad schedule, tag, IMA settings, etc. + + if let scheduleArray = getAdSchedule(from: ads), !scheduleArray.isEmpty { + adConfigBuilder.schedule(scheduleArray) + } + + if let tag = ads["tag"] as? String, let encodedString = tag.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed), let tagUrl = URL(string: encodedString) { + adConfigBuilder.tag(tagUrl) + } + + if let adVmap = ads["adVmap"] as? String, let encodedString = adVmap.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed), let adVmapUrl = URL(string: encodedString) { + adConfigBuilder.vmapURL(adVmapUrl) + } + + if let adRulesDict = ads["adRules"] as? [String: Any], let adRules = getAdRules(from: adRulesDict) { + adConfigBuilder.adRules(adRules) + } + + if let imaSettingsDict = ads["imaSettings"] as? [String: Any] { + if let imaSettings = getIMASettings(from: imaSettingsDict) { + adConfigBuilder.imaSettings(imaSettings) + } + } + + return try? adConfigBuilder.build() + } + + // Convert configureIMADAIWithAds function + static func configureIMADAI(with ads: [String: Any]) -> JWAdvertisingConfig? { + // Ensure Google IMA SDK is available +#if !USE_GOOGLE_IMA + assertionFailure("Error: GoogleAds-IMA-iOS-SDK is not installed. Add $RNJWPlayerUseGoogleIMA = true; to your podfile") +#endif + + let adConfigBuilder = JWImaDaiAdvertisingConfigBuilder() + + // Configure Google IMA DAI specific settings here + // Example: setting stream configuration, friendly obstructions, etc. + + if let imaSettingsDict = ads["imaSettings"] as? [String: Any] { + if let imaSettings = getIMASettings(from: imaSettingsDict) { + adConfigBuilder.imaSettings(imaSettings) + } + } + + if let googleDAIStreamDict = ads["googleDAIStream"] as? [String: Any], let googleDAIStream = getGoogleDAIStream(from: googleDAIStreamDict) { + adConfigBuilder.googleDAIStream(googleDAIStream) + } + + return try? adConfigBuilder.build() + } + + // Convert getAdSchedule function + static func getAdSchedule(from ads: [String: Any]) -> [JWAdBreak]? { + guard let schedule = ads["adSchedule"] as? [[String: Any]], !schedule.isEmpty else { return nil } + + var scheduleArray: [JWAdBreak] = [] + + for item in schedule { + if let offsetString = item["offset"] as? String, let tag = item["tag"] as? String, let encodedString = tag.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed), let tagUrl = URL(string: encodedString) { + + let adBreakBuilder = JWAdBreakBuilder() + if let offset = JWAdOffset.from(string: offsetString) { + adBreakBuilder.offset(offset) + adBreakBuilder.tags([tagUrl]) + + if let adBreak = try? adBreakBuilder.build() { + scheduleArray.append(adBreak) + } + } + } + } + + return scheduleArray.isEmpty ? nil : scheduleArray + } + + // Convert getAdRules function + static func getAdRules(from adRulesDict: [String: Any]) -> JWAdRules? { + let builder = JWAdRulesBuilder() + + if let startOn = adRulesDict["startOn"] as? UInt, let frequency = adRulesDict["frequency"] as? UInt, let timeBetweenAds = adRulesDict["timeBetweenAds"] as? UInt { + let startOnSeek = mapStringToJWAdShownOnSeek(adRulesDict["startOnSeek"] as? String) + builder.jwRules(startOn: startOn, frequency: frequency, timeBetweenAds: timeBetweenAds, startOnSeek: startOnSeek) + } + + return try? builder.build() + } + + // Convert mapStringToJWAdShownOnSeek function + static func mapStringToJWAdShownOnSeek(_ seekString: String?) -> JWAdShownOnSeek { + guard let seekString = seekString else { return .none } + switch seekString { + case "pre": + return .pre + default: + return .none + } + } + + // Convert getAdSettings function + static func getAdSettings(from settingsDict: [String: Any]) -> JWAdSettings? { + let builder = JWAdSettingsBuilder() + + if let allowsBackgroundPlayback = settingsDict["allowsBackgroundPlayback"] as? Bool { + builder.allowsBackgroundPlayback(allowsBackgroundPlayback) + } + + // Add other settings as needed + + return builder.build() + } + + // Convert getIMASettings function + static func getIMASettings(from imaSettingsDict: [String: Any]) -> JWImaSettings? { + let builder = JWImaSettingsBuilder() + if let locale = imaSettingsDict["locale"] as? String { + builder.locale(locale) + } + if let ppid = imaSettingsDict["ppid"] as? String { + builder.ppid(ppid) + } + if let maxRedirects = imaSettingsDict["maxRedirects"] as? UInt { + builder.maxRedirects(maxRedirects) + } + if let sessionID = imaSettingsDict["sessionID"] as? String { + builder.sessionID(sessionID) + } + if let debugMode = imaSettingsDict["debugMode"] as? Bool { + builder.debugMode(debugMode) + } + return builder.build() + } + + // Convert getGoogleDAIStream function + static func getGoogleDAIStream(from googleDAIStreamDict: [String: Any]) -> JWGoogleDAIStream? { + let builder = JWGoogleDAIStreamBuilder() + if let videoID = googleDAIStreamDict["videoID"] as? String, let cmsID = googleDAIStreamDict["cmsID"] as? String { + builder.vodStreamInfo(videoID: videoID, cmsID: cmsID) + } else if let assetKey = googleDAIStreamDict["assetKey"] as? String { + builder.liveStreamInfo(assetKey: assetKey) + } + if let apiKey = googleDAIStreamDict["apiKey"] as? String { + builder.apiKey(apiKey) + } + if let adTagParameters = googleDAIStreamDict["adTagParameters"] as? [String: Any] { + let stringParams = adTagParameters.compactMapValues { $0 as? String } + builder.adTagParameters(stringParams) + } + return try? builder.build() + } + + // Placeholder for findViewWithId function - Needs implementation +// static func findView(withId viewId: String) -> UIView? { +// // Implementation needed to find and return the view with the given id +// return nil +// } +// +// static func createFriendlyObstructions(fromArray obstructionsArray: [[String: Any]]) -> [JWFriendlyObstruction] { +// var obstructions: [JWFriendlyObstruction] = [] +// +// for obstructionDict in obstructionsArray { +// if let viewId = obstructionDict["viewId"] as? String, +// let view = findView(withId: viewId), +// let purposeString = obstructionDict["purpose"] as? String, +// let reason = obstructionDict["reason"] as? String { +// let purpose = mapStringToJWFriendlyObstructionPurpose(purposeString) +// let obstruction = JWFriendlyObstruction(view: view, purpose: purpose, reason: reason) +// obstructions.append(obstruction) +// } +// } +// +// return obstructions +// } +// +// static func mapStringToJWFriendlyObstructionPurpose(_ purposeString: String) -> JWFriendlyObstructionPurpose { +// switch purposeString { +// case "mediaControls": +// return .mediaControls +// case "closeAd": +// return .closeAd +// case "notVisible": +// return .notVisible +// default: +// return .other +// } +// } +// +// static func createCompanionAdSlot(fromDictionary companionAdSlotDict: [String: Any]) -> JWCompanionAdSlot? { +// guard let viewId = companionAdSlotDict["viewId"] as? String, +// let view = findView(withId: viewId), +// let sizeDict = companionAdSlotDict["size"] as? [String: Float], +// let width = sizeDict["width"], +// let height = sizeDict["height"] else { +// return nil +// } +// +// let size = CGSize(width: CGFloat(width), height: CGFloat(height)) +// let slot = JWCompanionAdSlot(view: view, size: size) +// return slot +// } +} + diff --git a/ios/RNJWPlayer/RNJWPlayerModels.swift b/ios/RNJWPlayer/RNJWPlayerModels.swift new file mode 100644 index 00000000..af18edfb --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerModels.swift @@ -0,0 +1,149 @@ +// This file was generated from JSON Schema using quicktype, do not modify it directly. +// To parse the JSON, add this file to your project and do: +// +// let jWConfig = try? JSONDecoder().decode(JWConfig.self, from: jsonData) + +import Foundation + +// MARK: - JWConfig +struct JWConfig: Codable { + let config: Config +} + +// MARK: - Config +struct Config: Codable { + let license: String + let advertising: Advertising + let autostart, controls, configRepeat: Bool + let nextUpStyle: NextUpStyle + let styling: Styling + let backgroundAudioEnabled: Bool + let category: String + let categoryOptions: [String] + let mode: String + let fullScreenOnLandscape, landscapeOnFullScreen, portraitOnExitFullScreen, exitFullScreenOnPortrait: Bool + let playlist: [Playlist] + let stretching: String + let related: Related + let preload, interfaceBehavior: String + let interfaceFadeDelay: Int + let hideUIGroups: [String] + let processSpcURL, fairplayCERTURL, contentUUID: String + let viewOnly, enableLockScreenControls, pipEnabled: Bool + + enum CodingKeys: String, CodingKey { + case license, advertising, autostart, controls + case configRepeat = "repeat" + case nextUpStyle, styling, backgroundAudioEnabled, category, categoryOptions, mode, fullScreenOnLandscape, landscapeOnFullScreen, portraitOnExitFullScreen, exitFullScreenOnPortrait, playlist, stretching, related, preload, interfaceBehavior, interfaceFadeDelay, hideUIGroups + case processSpcURL = "processSpcUrl" + case fairplayCERTURL = "fairplayCertUrl" + case contentUUID, viewOnly, enableLockScreenControls, pipEnabled + } +} + +// MARK: - Advertising +struct Advertising: Codable { + let adSchedule: [AdSchedule] + let adVmap, tag: String + let openBrowserOnAdClick: Bool + let adClient: String +} + +// MARK: - AdSchedule +struct AdSchedule: Codable { + let tag, offset: String +} + +// MARK: - NextUpStyle +struct NextUpStyle: Codable { + let offsetSeconds, offsetPercentage: Int +} + +// MARK: - Playlist +struct Playlist: Codable { + let file: String + let sources: [Source] + let image, title, description, mediaID: String + let adSchedule: [AdSchedule] + let adVmap: String + let tracks: [Source] + let recommendations, startTime: String + let autostart: Bool + + enum CodingKeys: String, CodingKey { + case file, sources, image, title, description + case mediaID = "mediaId" + case adSchedule, adVmap, tracks, recommendations, startTime, autostart + } +} + +// MARK: - Source +struct Source: Codable { + let file, label: String + let sourceDefault: Bool + + enum CodingKeys: String, CodingKey { + case file, label + case sourceDefault = "default" + } +} + +// MARK: - Related +struct Related: Codable { + let onClick, onComplete, heading, url: String + let autoplayMessage: String + let autoplayTimer: Int +} + +// MARK: - Styling +struct Styling: Codable { + let colors: Colors + let font: Font + let displayTitle, displayDescription: Bool + let captionsStyle: CaptionsStyle + let menuStyle: MenuStyle +} + +// MARK: - CaptionsStyle +struct CaptionsStyle: Codable { + let font: Font + let fontColor, backgroundColor, highlightColor, edgeStyle: String +} + +// MARK: - Font +struct Font: Codable { + let name: String + let size: Int +} + +// MARK: - Colors +struct Colors: Codable { + let buttons, backgroundColor, fontColor: String + let timeslider: Timeslider +} + +// MARK: - Timeslider +struct Timeslider: Codable { + let progress, rail, thumb: String +} + +// MARK: - MenuStyle +struct MenuStyle: Codable { + let font: Font + let fontColor, backgroundColor: String +} + +// MARK: - Extensions +extension Decodable { + init(_ dict: [Key: Value]) throws where Key: Codable, Value: Codable { + let data = try JSONEncoder().encode(dict) + self = try JSONDecoder().decode(Self.self, from: data) + } +} + +extension Decodable { + init(_ dict: [Key: Any]) throws { + let data = try JSONSerialization.data(withJSONObject: dict, options: []) + self = try JSONDecoder().decode(Self.self, from: data) + } +} diff --git a/ios/RNJWPlayer/RNJWPlayerView.h b/ios/RNJWPlayer/RNJWPlayerView.h deleted file mode 100644 index a702cb3d..00000000 --- a/ios/RNJWPlayer/RNJWPlayerView.h +++ /dev/null @@ -1,127 +0,0 @@ -#if __has_include("React/RCTViewManager.h") -#import "React/RCTViewManager.h" -#else -#import "RCTViewManager.h" -#endif - -#import -#import -#import -#import "RNJWPlayerViewController.h" -#import "RNJWPlayerAds.h" - -#if USE_GOOGLE_CAST - #import -#endif - - -@class RNJWPlayerViewController; - -@interface RNJWPlayerView : UIView - -@property(nonatomic, strong)RNJWPlayerViewController* playerViewController; -@property(nonatomic, strong)JWPlayerView *playerView; - -@property(nonatomic, strong)AVAudioSession *audioSession; - -@property(nonatomic)BOOL pipEnabled; -@property(nonatomic)BOOL backgroundAudioEnabled; - -@property(nonatomic)BOOL userPaused; -@property(nonatomic)BOOL wasInterrupted; - -@property(nonatomic)JWInterfaceBehavior interfaceBehavior; - -/* DRM props */ -@property(nonatomic)NSString *fairplayCertUrl; -@property(nonatomic)NSString *processSpcUrl; -@property(nonatomic)NSString *contentUUID; - -/* Config helpers */ -@property(nonatomic)NSString *audioCategory; -@property(nonatomic)NSString *audioMode; -@property(nonatomic, strong)NSArray* audioCategoryOptions; -@property(nonatomic)BOOL settingConfig; -@property(nonatomic)BOOL pendingConfig; -@property(nonatomic)NSDictionary* currentConfig; - -/* casting objects */ -@property(nonatomic, strong)JWCastController *castController; -@property(nonatomic)BOOL isCasting; -@property(nonatomic, strong)NSArray *availableDevices; - -/* player state events */ -@property(nonatomic, copy)RCTBubblingEventBlock onBuffer; -@property(nonatomic, copy)RCTBubblingEventBlock onUpdateBuffer; -@property(nonatomic, copy)RCTBubblingEventBlock onPlay; -@property(nonatomic, copy)RCTBubblingEventBlock onBeforePlay; -@property(nonatomic, copy)RCTBubblingEventBlock onAttemptPlay; -@property(nonatomic, copy)RCTBubblingEventBlock onPause; -@property(nonatomic, copy)RCTBubblingEventBlock onIdle; -@property(nonatomic, copy)RCTBubblingEventBlock onPlaylistItem; -@property(nonatomic, copy)RCTBubblingEventBlock onLoaded; -@property(nonatomic, copy)RCTBubblingEventBlock onVisible; -@property(nonatomic, copy)RCTBubblingEventBlock onTime; -@property(nonatomic, copy)RCTBubblingEventBlock onSeek; -@property(nonatomic, copy)RCTBubblingEventBlock onSeeked; -@property(nonatomic, copy)RCTBubblingEventBlock onRateChanged; -@property(nonatomic, copy)RCTBubblingEventBlock onPlaylist; -@property(nonatomic, copy)RCTBubblingEventBlock onPlaylistComplete; -@property(nonatomic, copy)RCTBubblingEventBlock onBeforeComplete; -@property(nonatomic, copy)RCTBubblingEventBlock onComplete; - -/* av events */ -@property(nonatomic, copy)RCTBubblingEventBlock onAudioTracks; - -/* player events */ -@property(nonatomic, copy)RCTBubblingEventBlock onPlayerReady; -@property(nonatomic, copy)RCTBubblingEventBlock onSetupPlayerError; -@property(nonatomic, copy)RCTBubblingEventBlock onPlayerError; -@property(nonatomic, copy)RCTBubblingEventBlock onPlayerWarning; - -/* ad events */ -@property(nonatomic, copy)RCTBubblingEventBlock onPlayerAdWarning; -@property(nonatomic, copy)RCTBubblingEventBlock onPlayerAdError; -@property(nonatomic, copy)RCTBubblingEventBlock onAdEvent; -@property(nonatomic, copy)RCTBubblingEventBlock onAdTime; - -/* player view controller events */ -@property(nonatomic, copy)RCTBubblingEventBlock onScreenTapped; -@property(nonatomic, copy)RCTBubblingEventBlock onControlBarVisible; -@property(nonatomic, copy)RCTBubblingEventBlock onFullScreen; -@property(nonatomic, copy)RCTBubblingEventBlock onFullScreenRequested; -@property(nonatomic, copy)RCTBubblingEventBlock onFullScreenExit; -@property(nonatomic, copy)RCTBubblingEventBlock onFullScreenExitRequested; - -/* player view events */ -@property(nonatomic, copy)RCTBubblingEventBlock onPlayerSizeChange; - -/* casting events */ -@property(nonatomic, copy)RCTBubblingEventBlock onCastingDevicesAvailable; -@property(nonatomic, copy)RCTBubblingEventBlock onConnectedToCastingDevice; -@property(nonatomic, copy)RCTBubblingEventBlock onDisconnectedFromCastingDevice; -@property(nonatomic, copy)RCTBubblingEventBlock onConnectionTemporarilySuspended; -@property(nonatomic, copy)RCTBubblingEventBlock onConnectionRecovered; -@property(nonatomic, copy)RCTBubblingEventBlock onConnectionFailed; -@property(nonatomic, copy)RCTBubblingEventBlock onCasting; -@property(nonatomic, copy)RCTBubblingEventBlock onCastingEnded; -@property(nonatomic, copy)RCTBubblingEventBlock onCastingFailed; - -/* casting methods */ -- (void)setUpCastController; -- (void)presentCastDialog; -#if USE_GOOGLE_CAST -- (GCKCastState)castState; -#endif -- (JWCastingDevice*)connectedDevice; -- (NSArray *)availableDevices; - -/* Methods */ --(void)setLicense:(id)license; --(void)toggleUIGroup:(UIView*)view :(NSString*)name :(NSString*)ofSubview :(BOOL)show; --(void)startDeinitProcess; --(JWPlayerItem*)getPlayerItem:item; -- (void)setVisibility:(BOOL)isVisible forControls:(NSArray* _Nonnull)controls; -- (void)setConfig:(NSDictionary*)config; - -@end diff --git a/ios/RNJWPlayer/RNJWPlayerView.m b/ios/RNJWPlayer/RNJWPlayerView.m deleted file mode 100644 index 4f228447..00000000 --- a/ios/RNJWPlayer/RNJWPlayerView.m +++ /dev/null @@ -1,1797 +0,0 @@ -#import "RNJWPlayerView.h" -#import -#import -#import -#import "RCTConvert+RNJWPlayer.h" - -@implementation RNJWPlayerView - -#pragma mark - RNJWPlayer allocation - -- (instancetype)init -{ - self = [super init]; - if (self) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rotated:) name:UIDeviceOrientationDidChangeNotification object:nil]; - } - return self; -} - -- (void)removeFromSuperview { - [self startDeinitProcess]; -} - --(void)startDeinitProcess -{ - @try { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; - } @catch(id anException) { - - } - - [self reset]; - [super removeFromSuperview]; -} - --(void)reset -{ - @try { -// [[NSNotificationCenter defaultCenter] removeObserver:self]; - - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionMediaServicesWereResetNotification object:_audioSession]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:_audioSession]; - - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil]; - - if (_playerViewController || _playerView) { - [[_playerViewController.player currentItem] removeObserver:self forKeyPath:@"playbackLikelyToKeepUp" context:nil]; - if (_playerView) { - [[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:@"isPictureInPicturePossible" context:NULL]; - } - } - } @catch(id anException) { - - } - - [self removePlayerView]; - [self dismissPlayerViewController]; - - [self deinitAudioSession]; - -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - if (self.playerView != nil) { - self.playerView.frame = self.frame; - } - - if (self.playerViewController != nil) { - self.playerViewController.view.frame = self.frame; - } -} - -- (void)rotated:(NSNotification *)notification { - if (UIDeviceOrientationIsLandscape(UIDevice.currentDevice.orientation)) { - NSLog(@"Landscape"); - } - - if (UIDeviceOrientationIsPortrait(UIDevice.currentDevice.orientation)) { - NSLog(@"Portrait"); - } - - [self layoutSubviews]; -} - --(BOOL)shouldAutorotate { - return NO; -} - -#pragma mark - RNJWPlayer props - --(void)setLicense:(id)license -{ - if ((license != nil) && (license != (id)[NSNull null])) { - [JWPlayerKitLicense setLicenseKey:license]; - } else { - NSLog(@"JW SDK License key not set."); - } -} - -- (NSArray *)keysForDifferingValuesInDict1:(NSDictionary *)dict1 andDict2:(NSDictionary *)dict2 { - NSMutableArray *diffKeys = [NSMutableArray new]; - for (NSString *key in dict1) { - if (![dict1[key] isEqual:dict2[key]]) { - [diffKeys addObject:key]; - } - } - return [diffKeys copy]; -} - -- (void)setConfig:(NSDictionary*)config -{ - // Create mutable copies of the dictionaries - NSMutableDictionary *configCopy = [config mutableCopy]; - NSMutableDictionary *currentConfigCopy = [_currentConfig mutableCopy]; - - // Remove the playlist key - [configCopy removeObjectForKey:@"playlist"]; - [currentConfigCopy removeObjectForKey:@"playlist"]; - - // Compare dictionaries without playlist key - if (![configCopy isEqualToDictionary:currentConfigCopy]) { - NSLog(@"There are differences other than the 'playlist' key."); - - NSArray *diffKeys = [self keysForDifferingValuesInDict1:configCopy andDict2:currentConfigCopy]; - NSLog(@"There are differences in these keys: %@", diffKeys); - - [self setNewConfig:config]; - } else { - // Compare original dictionaries - if(![_currentConfig isEqualToDictionary:config]) { - NSLog(@"The only difference is the 'playlist' key."); - - if (_playerViewController || _playerView) { - NSMutableArray *playlistArray = [[NSMutableArray alloc] init]; - - for (id item in config[@"playlist"]) { - JWPlayerItem *playerItem = [self getPlayerItem:item]; - [playlistArray addObject:playerItem]; - } - - if (_playerViewController) { - [_playerViewController.player loadPlaylistWithItems:playlistArray]; - } else { // if (_playerView) - [_playerView.player loadPlaylistWithItems:playlistArray]; - } - } else { - [self setNewConfig:config]; - } - } else { - NSLog(@"There are no differences."); - } - } -} - --(void)setNewConfig:(NSDictionary*)config -{ - _currentConfig = config; - - if (!_settingConfig) { - _pendingConfig = NO; - _settingConfig = YES; - - id license = config[@"license"]; - [self setLicense:license]; - - _backgroundAudioEnabled = [config[@"backgroundAudioEnabled"] boolValue]; - _pipEnabled = [config[@"pipEnabled"] boolValue]; - if (_backgroundAudioEnabled || _pipEnabled) { - id category = config[@"category"]; - id categoryOptions = config[@"categoryOptions"]; - id mode = config[@"mode"]; - - [self initAudioSession:category :categoryOptions :mode]; - } else { - [self deinitAudioSession]; - } - - id viewOnly = config[@"viewOnly"]; - if ((viewOnly != nil) && (viewOnly != (id)[NSNull null])) { - [self setupPlayerView:config :[self getPlayerConfiguration:config]]; - } else { - [self setupPlayerViewController:config :[self getPlayerConfiguration:config]]; - } - - _processSpcUrl = config[@"processSpcUrl"]; - _fairplayCertUrl = config[@"fairplayCertUrl"]; - _contentUUID = config[@"contentUUID"]; - } else { - _pendingConfig = YES; - } -} - --(void)setControls:(BOOL)controls -{ - [self toggleUIGroup:_playerViewController.view :@"JWPlayerKit.InterfaceView" :nil :controls]; -} - -#pragma mark - RNJWPlayer styling - --(UIColor*)colorWithHexString:(NSString*)hex -{ - NSString *cString = [[hex stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; - - // String should be 6 or 8 characters - if ([cString length] < 6) return [UIColor grayColor]; - - // strip 0X if it appears - if ([cString hasPrefix:@"0X"]) cString = [cString substringFromIndex:2]; - - if ([cString length] != 6) return [UIColor grayColor]; - - // Separate into r, g, b substrings - NSRange range; - range.location = 0; - range.length = 2; - NSString *rString = [cString substringWithRange:range]; - - range.location = 2; - NSString *gString = [cString substringWithRange:range]; - - range.location = 4; - NSString *bString = [cString substringWithRange:range]; - - // Scan values - unsigned int r, g, b; - [[NSScanner scannerWithString:rString] scanHexInt:&r]; - [[NSScanner scannerWithString:gString] scanHexInt:&g]; - [[NSScanner scannerWithString:bString] scanHexInt:&b]; - - return [UIColor colorWithRed:((float) r / 255.0f) - green:((float) g / 255.0f) - blue:((float) b / 255.0f) - alpha:1.0f]; -} - --(void)setStyling:styling -{ - JWError* error = nil; - - if (styling != nil && (styling != (id)[NSNull null])) { - JWPlayerSkinBuilder* skinStylingBuilder = [[JWPlayerSkinBuilder alloc] init]; - - id colors = styling[@"colors"]; - if (colors != nil && (colors != (id)[NSNull null])) { - id timeSlider = colors[@"timeslider"]; - if (timeSlider != nil && (timeSlider != (id)[NSNull null])) { - JWTimeSliderStyleBuilder* timeSliderStyleBuilder = [[JWTimeSliderStyleBuilder alloc] init]; - - id progress = timeSlider[@"progress"]; - if (progress != nil && (progress != (id)[NSNull null])) { - [timeSliderStyleBuilder minimumTrackColor:[self colorWithHexString:progress]]; - } - - id rail = timeSlider[@"rail"]; - if (rail != nil && (rail != (id)[NSNull null])) { - [timeSliderStyleBuilder maximumTrackColor:[self colorWithHexString:rail]]; - } - - id thumb = timeSlider[@"thumb"]; - if (thumb != nil && (thumb != (id)[NSNull null])) { - [timeSliderStyleBuilder thumbColor:[self colorWithHexString:thumb]]; - } - - JWTimeSliderStyle* timeSliderStyle = [timeSliderStyleBuilder buildAndReturnError:&error]; - - [skinStylingBuilder timeSliderStyle:timeSliderStyle]; - } - - id buttons = colors[@"buttons"]; - if (buttons != nil && (buttons != (id)[NSNull null])) { - [skinStylingBuilder buttonsColor:[self colorWithHexString:buttons]]; - } - - id backgroundColor = colors[@"backgroundColor"]; - if (backgroundColor != nil && (backgroundColor != (id)[NSNull null])) { - [skinStylingBuilder backgroundColor:[self colorWithHexString:backgroundColor]]; - } - - id fontColor = colors[@"fontColor"]; - if (fontColor != nil && (fontColor != (id)[NSNull null])) { - [skinStylingBuilder fontColor:[self colorWithHexString:fontColor]]; - } - } - - id font = styling[@"font"]; - if (font != nil && (font != (id)[NSNull null])) { - id name = font[@"name"]; - id size = font[@"size"]; - - if (name != nil && (name != (id)[NSNull null]) && size != nil && (size != (id)[NSNull null])) { - [skinStylingBuilder font:[UIFont fontWithName:name size:[size floatValue]]]; - } - } - - id showTitle = styling[@"displayTitle"]; - if (showTitle != nil && (showTitle != (id)[NSNull null])) { - [skinStylingBuilder titleIsVisible:showTitle]; - } - - id showDesc = styling[@"displayDescription"]; - if (showDesc != nil && (showDesc != (id)[NSNull null])) { - [skinStylingBuilder descriptionIsVisible:showDesc]; - } - - id capStyle = styling[@"captionsStyle"]; - if (capStyle != nil && (capStyle != (id)[NSNull null])) { - JWCaptionStyleBuilder* capStyleBuilder = [[JWCaptionStyleBuilder alloc] init]; - - id font = capStyle[@"font"]; - if (font != nil && (font != (id)[NSNull null])) { - id name = font[@"name"]; - id size = font[@"size"]; - if (name != nil && (name != (id)[NSNull null]) && size != nil && (size != (id)[NSNull null])) { - [capStyleBuilder font:[UIFont fontWithName:name size:[size floatValue]]]; - } - } - - id fontColor = capStyle[@"fontColor"]; - if (fontColor != nil && (fontColor != (id)[NSNull null])) { - [capStyleBuilder fontColor:[self colorWithHexString:fontColor]]; - } - - id backgroundColor = capStyle[@"backgroundColor"]; - if (backgroundColor != nil && (backgroundColor != (id)[NSNull null])) { - [capStyleBuilder backgroundColor:[self colorWithHexString:backgroundColor]]; - } - - id highlightColor = capStyle[@"highlightColor"]; - if (highlightColor != nil && (highlightColor != (id)[NSNull null])) { - [capStyleBuilder highlightColor:[self colorWithHexString:highlightColor]]; - } - - id edgeStyle = capStyle[@"edgeStyle"]; - if (edgeStyle != nil && (edgeStyle != (id)[NSNull null])) { - [capStyleBuilder edgeStyle:[RCTConvert JWCaptionEdgeStyle:edgeStyle]]; - } - - JWCaptionStyle* captionStyle = [capStyleBuilder buildAndReturnError:&error]; - - [skinStylingBuilder captionStyle:captionStyle]; - } - - - id menuStyle = styling[@"menuStyle"]; - if (menuStyle != nil && (menuStyle != (id)[NSNull null])) { - JWMenuStyleBuilder* menuStyleBuilder = [[JWMenuStyleBuilder alloc] init]; - - id font = capStyle[@"font"]; - if (font != nil && (font != (id)[NSNull null])) { - id name = font[@"name"]; - id size = font[@"size"]; - if (name != nil && (name != (id)[NSNull null]) && size != nil && (size != (id)[NSNull null])) { - [menuStyle font:[UIFont fontWithName:name size:[size floatValue]]]; - } - } - - id fontColor = capStyle[@"fontColor"]; - if (fontColor != nil && (fontColor != (id)[NSNull null])) { - [menuStyle fontColor:[self colorWithHexString:fontColor]]; - } - - id backgroundColor = capStyle[@"backgroundColor"]; - if (backgroundColor != nil && (backgroundColor != (id)[NSNull null])) { - [menuStyle backgroundColor:[self colorWithHexString:backgroundColor]]; - } - - JWMenuStyle* jwMenuStyle = [menuStyleBuilder buildAndReturnError:&error]; - - [skinStylingBuilder menuStyle:jwMenuStyle]; - } - - JWPlayerSkin *skinStyling = [skinStylingBuilder buildAndReturnError:&error]; - - dispatch_async(dispatch_get_main_queue(), ^{ - self->_playerViewController.styling = skinStyling; - }); - } -} - -#pragma mark - RNJWPlayer config helpers - --(JWPlayerItem*)getPlayerItem:item -{ - JWPlayerItemBuilder* itemBuilder = [[JWPlayerItemBuilder alloc] init]; - JWError* error = nil; - - NSString* newFile = [item objectForKey:@"file"]; - NSURL* url = [NSURL URLWithString:newFile]; - - if (url && url.scheme && url.host) { - [itemBuilder file:url]; - } else if (newFile != nil) { - NSString* encodedString = [newFile stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; - NSURL* encodedUrl = [NSURL URLWithString:encodedString]; - [itemBuilder file:encodedUrl]; - } - - id itemSources = item[@"sources"]; - if(itemSources != nil && (itemSources != (id)[NSNull null])) { - NSArray* itemSourcesArray = (NSArray*)itemSources; - if (itemSourcesArray.count > 0) { - NSMutableArray *sourcesArray = [[NSMutableArray alloc] init]; - - for (id source in itemSourcesArray) { - NSString* file = [source objectForKey:@"file"]; - NSURL* fileUrl = [NSURL URLWithString:file]; - NSString* label = [source objectForKey:@"label"]; - bool isDefault = [source objectForKey:@"default"]; - - JWVideoSourceBuilder* sourceBuilder = [[JWVideoSourceBuilder alloc] init]; - - [sourceBuilder file:fileUrl]; - [sourceBuilder label:label]; - [sourceBuilder defaultVideo:isDefault]; - - [sourcesArray addObject:[sourceBuilder buildAndReturnError:&error]]; - } - - [itemBuilder videoSources:sourcesArray]; - } - } - - id mediaId = item[@"mediaId"]; - if ((mediaId != nil) && (mediaId != (id)[NSNull null])) { - [itemBuilder mediaId:mediaId]; - } - - id title = item[@"title"]; - if ((title != nil) && (title != (id)[NSNull null])) { - [itemBuilder title:title]; - } - - id desc = item[@"description"]; - if ((desc != nil) && (desc != (id)[NSNull null])) { - [itemBuilder description:desc]; - } - - id image = item[@"image"]; - if ((image != nil) && (image != (id)[NSNull null])) { - NSURL* imageUrl = [NSURL URLWithString:image]; - [itemBuilder posterImage:imageUrl]; - } - - id startTime = item[@"startTime"]; - if ((startTime != nil) && (startTime != (id)[NSNull null])) { - [itemBuilder startTime:[startTime floatValue]]; - } - - id recommendations = item[@"recommendations"]; - if ((recommendations != nil) && (recommendations != (id)[NSNull null])) { - NSURL* recUrl = [NSURL URLWithString:recommendations]; - [itemBuilder recommendations:recUrl]; - } - - id tracksItem = item[@"tracks"]; - if(tracksItem != nil && (tracksItem != (id)[NSNull null])) { - NSArray* tracksItemArray = (NSArray*)tracksItem; - if (tracksItemArray.count > 0) { - NSMutableArray *tracksArray = [[NSMutableArray alloc] init]; - - for (id item in tracksItemArray) { - NSString *file = [item objectForKey:@"file"]; - NSURL *fileUrl = [NSURL URLWithString:file]; - NSString *label = [item objectForKey:@"label"]; - bool isDefault = [item objectForKey:@"default"]; - - JWCaptionTrackBuilder* trackBuilder = [[JWCaptionTrackBuilder alloc] init]; - - [trackBuilder file:fileUrl]; - [trackBuilder label:label]; - [trackBuilder defaultTrack:isDefault]; - - JWMediaTrack *trackItem = [trackBuilder buildAndReturnError:&error]; - - [tracksArray addObject:trackItem]; - } - - [itemBuilder mediaTracks:tracksArray]; - } - } - - id ads = item[@"adSchedule"]; - if(ads != nil && (ads != (id)[NSNull null])) { - NSArray* adsAr = (NSArray*)ads; - if (adsAr.count > 0) { - NSMutableArray * adsArray = [[NSMutableArray alloc] init]; - - for (id item in adsAr) { - NSString *offsetString = [item objectForKey:@"offset"]; - NSString *tag = [item objectForKey:@"tag"]; - NSString* encodedString = [tag stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; - NSURL* tagUrl = [NSURL URLWithString:encodedString]; - - if (tagUrl != nil) { - JWAdBreakBuilder* adBreakBuilder = [[JWAdBreakBuilder alloc] init]; - JWAdOffset* offset = [JWAdOffset fromString:offsetString]; - - [adBreakBuilder offset:offset]; - [adBreakBuilder tags:@[tagUrl]]; - - JWAdBreak *adBreak = [adBreakBuilder buildAndReturnError:&error]; - - [adsArray addObject:adBreak]; - } - } - - if (adsArray.count > 0) { - [itemBuilder adScheduleWithBreaks:adsArray]; - } - } - } - - id adVmap = item[@"adVmap"]; - if (adVmap != nil && (adVmap != (id)[NSNull null])) { - NSString* encodedString = [adVmap stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; - NSURL* adVmapUrl = [NSURL URLWithString:encodedString]; - - if (adVmapUrl != nil) { - [itemBuilder adScheduleWithVmapURL:adVmapUrl]; - } - } - - return [itemBuilder buildAndReturnError:&error]; -} - --(JWPlayerConfiguration*)getPlayerConfiguration:config -{ - JWPlayerConfigurationBuilder *configBuilder = [[JWPlayerConfigurationBuilder alloc] init]; - - NSMutableArray *playlistArray = [[NSMutableArray alloc] init]; - if (config[@"playlist"] != nil && (config[@"playlist"] != (id)[NSNull null])) { - NSArray* playlist = config[@"playlist"]; - for (id item in playlist) { - JWPlayerItem *playerItem = [self getPlayerItem:item]; - [playlistArray addObject:playerItem]; - } - - [configBuilder playlistWithItems:playlistArray]; - } - - id autostart = config[@"autostart"]; - if ([autostart boolValue]) { - [configBuilder autostart:autostart]; - } - - id repeatContent = config[@"repeat"]; - if (repeatContent != nil && (repeatContent != (id)[NSNull null])) { - if ([repeatContent boolValue]) { - [configBuilder repeatContent:repeatContent]; - } - } - - id preload = config[@"preload"]; - if (preload != nil && (preload != (id)[NSNull null])) { - [configBuilder preload:[RCTConvert JWPreload:preload]]; - } - - id related = config[@"related"]; - if ((related != nil) && (related != (id)[NSNull null])) { - JWRelatedContentConfigurationBuilder* relatedBuilder = [[JWRelatedContentConfigurationBuilder alloc] init]; - - id onClick = related[@"onClick"]; - if ((onClick != nil) && (onClick != (id)[NSNull null])) { - [relatedBuilder onClick:[RCTConvert JWRelatedOnClick:onClick]]; - } - - id onComplete = related[@"onComplete"]; - if ((onComplete != nil) && (onComplete != (id)[NSNull null])) { - - [relatedBuilder onComplete:[RCTConvert JWRelatedOnComplete:onComplete]]; - } - - id heading = related[@"heading"]; - if ((heading != nil) && (heading != (id)[NSNull null])) { - [relatedBuilder heading:heading]; - } - - id urlStr = related[@"url"]; - if ((urlStr != nil) && (urlStr != (id)[NSNull null])) { - NSURL* url = [NSURL URLWithString:urlStr]; - [relatedBuilder url:url]; - } - - id autoplayMessage = related[@"autoplayMessage"]; - if ((autoplayMessage != nil) && (autoplayMessage != (id)[NSNull null])) { - [relatedBuilder autoplayMessage:autoplayMessage]; - } - - id autoplayTimer = related[@"autoplayTimer"]; - if ((autoplayTimer != nil) && (autoplayTimer != (id)[NSNull null])) { - [relatedBuilder autoplayTimer:[autoplayTimer intValue]]; - } - - JWRelatedContentConfiguration* related = [relatedBuilder build]; - - [configBuilder related:related]; - } - -// JWJSONParser -// JWLockScreenManager - - JWError* error = nil; - - id ads = config[@"advertising"]; - if (ads != nil && (ads != (id)[NSNull null])) { - JWAdvertisingConfig* advertisingConfig = nil; - - JWAdClient jwAdClient = JWAdClientUnknown; - NSString* adClientString = ads[@"adClient"]; - if (adClientString) { - jwAdClient = [RCTConvert JWAdClient:adClientString]; - } - - switch (jwAdClient) { - case JWAdClientJWPlayer: - advertisingConfig = [RNJWPlayerAds configureVASTWithAds:ads error:&error]; - break; - case JWAdClientGoogleIMA: - advertisingConfig = [RNJWPlayerAds configureIMAWithAds:ads error:&error]; - break; - case JWAdClientGoogleIMADAI: - advertisingConfig = [RNJWPlayerAds configureIMADAIWithAds:ads error:&error]; - break; - default: - // Handle unknown or unsupported ad client - break; - } - - // Handle error if any - if (error) { - NSLog(@"Error configuring ads: %@", error); - } - - [configBuilder advertising:advertisingConfig]; - } - - JWPlayerConfiguration* playerConfig = [configBuilder buildAndReturnError:&error]; - return playerConfig; -} - -#pragma mark - JWPlayer View Controller helpers - --(void)setupPlayerViewController:config :(JWPlayerConfiguration*)playerConfig -{ - if (_playerViewController == nil) { - _playerViewController = [RNJWPlayerViewController new]; - _playerViewController.parentView = self; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.reactViewController) { - [self.reactViewController addChildViewController:self.playerViewController]; - [self.playerViewController didMoveToParentViewController:self.reactViewController]; - } else { - [self reactAddControllerToClosestParent:self.playerViewController]; - } - }); - _playerViewController.view.frame = self.frame; - [self addSubview:_playerViewController.view]; - [_playerViewController setDelegates]; - } - - id interfaceBehavior = config[@"interfaceBehavior"]; - if ((interfaceBehavior != nil) && (interfaceBehavior != (id)[NSNull null])) { - _interfaceBehavior = [RCTConvert JWInterfaceBehavior:interfaceBehavior]; - } - - id interfaceFadeDelay = config[@"interfaceFadeDelay"]; - if ((interfaceFadeDelay != nil) && (interfaceFadeDelay != (id)[NSNull null])) { - _playerViewController.interfaceFadeDelay = [interfaceFadeDelay doubleValue]; - } - - id forceFullScreenOnLandscape = config[@"fullScreenOnLandscape"]; - if (forceFullScreenOnLandscape != nil && forceFullScreenOnLandscape != (id)[NSNull null]) { - _playerViewController.forceFullScreenOnLandscape = forceFullScreenOnLandscape; - } - - id forceLandscapeOnFullScreen = config[@"landscapeOnFullScreen"]; - if (forceLandscapeOnFullScreen != nil && forceLandscapeOnFullScreen != (id)[NSNull null]) { - _playerViewController.forceLandscapeOnFullScreen = forceLandscapeOnFullScreen; - } - - id enableLockScreenControls = config[@"enableLockScreenControls"]; - if ((enableLockScreenControls != nil && enableLockScreenControls != (id)[NSNull null]) || _backgroundAudioEnabled) { - _playerViewController.enableLockScreenControls = YES; - } - - id allowsPictureInPicturePlayback = config[@"allowsPictureInPicturePlayback"]; - if ((allowsPictureInPicturePlayback != nil && allowsPictureInPicturePlayback != (id)[NSNull null])) { - _playerViewController.allowsPictureInPicturePlayback = allowsPictureInPicturePlayback; - } - - id styling = config[@"styling"]; - [self setStyling:styling]; - - JWError* error = nil; - - id nextUpStyle = config[@"nextUpStyle"]; - if (nextUpStyle != nil && nextUpStyle != (id)[NSNull null]) { - JWNextUpStyleBuilder* nextUpBuilder = [[JWNextUpStyleBuilder alloc] init]; - - id offsetSeconds = nextUpStyle[@"offsetSeconds"]; - id offsetPercentage = nextUpStyle[@"offsetPercentage"]; - - [nextUpBuilder timeOffsetWithSeconds:[offsetSeconds doubleValue]]; - [nextUpBuilder timeOffsetWithPercentage:[offsetPercentage doubleValue]]; - _playerViewController.nextUpStyle = [nextUpBuilder buildAndReturnError:&error]; - } - -// _playerViewController.adInterfaceStyle -// _playerViewController.logo -// _playerView.videoGravity = 0; -// _playerView.captionStyle - - id offlineMsg = config[@"offlineMessage"]; - if (offlineMsg != nil && offlineMsg != (id)[NSNull null]) { - _playerViewController.offlineMessage = offlineMsg; - } - - id offlineImg = config[@"offlineImage"]; - if (offlineImg != nil && offlineImg != (id)[NSNull null]) { - NSURL* imageUrl = [NSURL URLWithString:offlineImg]; - if ([imageUrl isFileURL]) { - UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageUrl]]; - _playerViewController.offlinePosterImage = image; - } - } - - [self presentPlayerViewController:playerConfig]; -} - --(void)dismissPlayerViewController -{ - if (_playerViewController) { - [_playerViewController.player pause]; // hack for stop not always stopping on unmount - [_playerViewController.player stop]; - _playerViewController.enableLockScreenControls = NO; - - // hack for stop not always stopping on unmount - JWPlayerConfigurationBuilder *configBuilder = [[JWPlayerConfigurationBuilder alloc] init]; - [configBuilder playlistWithItems:@[]]; - NSError* error = nil; - [_playerViewController.player configurePlayerWith:[configBuilder buildAndReturnError:&error]]; - - _playerViewController.parentView = nil; - [_playerViewController setVisibility:NO forControls:@[@(JWControlTypePictureInPictureButton)]]; - [_playerViewController.view removeFromSuperview]; - [_playerViewController removeFromParentViewController]; - [_playerViewController willMoveToParentViewController:nil]; - [_playerViewController removeDelegates]; - _playerViewController = nil; - } -} - --(void)presentPlayerViewController:(JWPlayerConfiguration*)configuration -{ - if (configuration != nil) { - [_playerViewController.player configurePlayerWith:configuration]; - - if (_interfaceBehavior) { - _playerViewController.interfaceBehavior = JWInterfaceBehaviorHidden; - } - } -} - -#pragma mark - JWPlayer View helpers - --(void)setupPlayerView:config :(JWPlayerConfiguration*)playerConfig -{ - _playerView = [[JWPlayerView new] initWithFrame:self.superview.frame]; - - _playerView.delegate = self; - _playerView.player.delegate = self; - _playerView.player.playbackStateDelegate = self; - _playerView.player.adDelegate = self; - _playerView.player.avDelegate = self; - _playerView.player.contentKeyDataSource = self; - - [_playerView.player configurePlayerWith:playerConfig]; - - if (_pipEnabled) { - AVPictureInPictureController* pipController = _playerView.pictureInPictureController; - pipController.delegate = self; - - [pipController addObserver:self forKeyPath:@"isPictureInPicturePossible" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial context:NULL]; - } - - [self addSubview:self.playerView]; - - id autostart = config[@"autostart"]; - if ([autostart boolValue]) { - [_playerView.player play]; - } - - // Time observers - __weak RNJWPlayerView *weakSelf = self; - _playerView.player.adTimeObserver = ^(JWTimeData * time) { - if (weakSelf.onAdTime) { - weakSelf.onAdTime(@{@"position": @(time.position), @"duration": @(time.duration)}); - } - }; - - _playerView.player.mediaTimeObserver = ^(JWTimeData * time) { - if (weakSelf.onTime) { - weakSelf.onTime(@{@"position": @(time.position), @"duration": @(time.duration)}); - } - }; -} - --(void)removePlayerView -{ - if (_playerView) { - [_playerView.player stop]; - [_playerView removeFromSuperview]; - _playerView = nil; - } -} - --(void)toggleUIGroup:(UIView*)view :(NSString*)name :(NSString*)ofSubview :(BOOL)show -{ - NSArray *subviews = [view subviews]; - - for (UIView *subview in subviews) { - if ([NSStringFromClass(subview.class) isEqualToString:name] && (ofSubview == nil || [NSStringFromClass(subview.superview.class) isEqualToString:name])) { - [subview setHidden:!show]; - } else { - [self toggleUIGroup:subview :name :ofSubview :show]; - } - } -} - -- (void)setVisibility:(BOOL)isVisible forControls:(NSArray* _Nonnull)controls -{ - NSMutableArray * _controls = [[NSMutableArray alloc] init]; - - for (id control in controls) { - JWControlType type = [RCTConvert JWControlType:control]; - if (type) { - [_controls addObject:@(type)]; - } - } - - if ([_controls count]) { - [_playerViewController setVisibility:isVisible forControls:_controls]; - } -} - -#pragma mark - JWPlayer Delegate - -- (void)jwplayerIsReady:(id)player -{ - _settingConfig = NO; - if (self.onPlayerReady) { - self.onPlayerReady(@{}); - } - - if (_pendingConfig && _currentConfig) { - [self setConfig:_currentConfig]; - } -} - -- (void)jwplayer:(id)player failedWithError:(NSUInteger)code message:(NSString *)message -{ - if (self.onPlayerError) { - self.onPlayerError(@{@"code": [NSNumber numberWithInteger:code], @"error": message}); - } -} - -- (void)jwplayer:(id)player failedWithSetupError:(NSUInteger)code message:(NSString *)message -{ - if (self.onSetupPlayerError) { - self.onSetupPlayerError(@{@"code": [NSNumber numberWithInteger:code], @"error": message}); - } -} - -- (void)jwplayer:(id)player encounteredWarning:(NSUInteger)code message:(NSString *)message -{ - if (self.onPlayerWarning) { - self.onPlayerWarning(@{@"code": [NSNumber numberWithInteger:code], @"warning": message}); - } -} - -- (void)jwplayer:(id _Nonnull)player encounteredAdError:(NSUInteger)code message:(NSString * _Nonnull)message { - if (self.onPlayerAdError) { - self.onPlayerAdError(@{@"code": [NSNumber numberWithInteger:code], @"error": message}); - } -} - - -- (void)jwplayer:(id _Nonnull)player encounteredAdWarning:(NSUInteger)code message:(NSString * _Nonnull)message { - if (self.onPlayerAdWarning) { - self.onPlayerAdWarning(@{@"code": [NSNumber numberWithInteger:code], @"warning": message}); - } -} - - -#pragma mark - JWPlayer View Delegate - -- (void)playerView:(JWPlayerView *)view sizeChangedFrom:(CGSize)oldSize to:(CGSize)newSize -{ - if (self.onPlayerSizeChange) { - NSMutableDictionary* oldSizeDict = [[NSMutableDictionary alloc] init]; - [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.width] forKey:@"width"]; - [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.height] forKey:@"height"]; - - NSMutableDictionary* newSizeDict = [[NSMutableDictionary alloc] init]; - [newSizeDict setObject:[NSNumber numberWithFloat: newSize.width] forKey:@"width"]; - [newSizeDict setObject:[NSNumber numberWithFloat: newSize.height] forKey:@"height"]; - - NSMutableDictionary* sizesDict = [[NSMutableDictionary alloc] init]; - [sizesDict setObject:oldSizeDict forKey:@"oldSize"]; - [sizesDict setObject:newSizeDict forKey:@"newSize"]; - - NSError* error = nil; - NSData* data = [NSJSONSerialization dataWithJSONObject:sizesDict options:NSJSONWritingPrettyPrinted error: &error]; - self.onPlayerSizeChange(@{@"sizes": data}); - } -} - -#pragma mark - JWPlayer View Controller Delegate - -- (void)playerViewController:(JWPlayerViewController *)controller sizeChangedFrom:(CGSize)oldSize to:(CGSize)newSize -{ - if (self.onPlayerSizeChange) { - NSMutableDictionary* oldSizeDict = [[NSMutableDictionary alloc] init]; - [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.width] forKey:@"width"]; - [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.height] forKey:@"height"]; - - NSMutableDictionary* newSizeDict = [[NSMutableDictionary alloc] init]; - [newSizeDict setObject:[NSNumber numberWithFloat: newSize.width] forKey:@"width"]; - [newSizeDict setObject:[NSNumber numberWithFloat: newSize.height] forKey:@"height"]; - - NSMutableDictionary* sizesDict = [[NSMutableDictionary alloc] init]; - [sizesDict setObject:oldSizeDict forKey:@"oldSize"]; - [sizesDict setObject:newSizeDict forKey:@"newSize"]; - - NSError* error = nil; - NSData* data = [NSJSONSerialization dataWithJSONObject:sizesDict options:NSJSONWritingPrettyPrinted error: &error]; - self.onPlayerSizeChange(@{@"sizes": data}); - } -} - -- (void)playerViewController:(JWPlayerViewController *)controller screenTappedAt:(CGPoint)position -{ - if (self.onScreenTapped) { - self.onScreenTapped(@{@"x": @(position.x), @"y": @(position.y)}); - } -} - -- (void)playerViewController:(JWPlayerViewController *)controller controlBarVisibilityChanged:(BOOL)isVisible frame:(CGRect)frame -{ - if (self.onControlBarVisible) { - self.onControlBarVisible(@{@"visible": @(isVisible)}); - } -} - -- (JWFullScreenViewController * _Nullable)playerViewControllerWillGoFullScreen:(JWPlayerViewController * _Nonnull)controller { - if (self.onFullScreenRequested) { - self.onFullScreenRequested(@{}); - } - return nil; -} - -- (void)playerViewControllerDidGoFullScreen:(JWPlayerViewController *)controller -{ - if (self.onFullScreen) { - self.onFullScreen(@{}); - } -} - -- (void)playerViewControllerWillDismissFullScreen:(JWPlayerViewController *)controller -{ - if (self.onFullScreenExitRequested) { - self.onFullScreenExitRequested(@{}); - } -} - -- (void)playerViewControllerDidDismissFullScreen:(JWPlayerViewController *)controller -{ - if (self.onFullScreenExit) { - self.onFullScreenExit(@{}); - } -} - -- (void)playerViewController:(JWPlayerViewController *)controller relatedMenuClosedWithMethod:(enum JWRelatedInteraction)method -{ - -} - -- (void)playerViewController:(JWPlayerViewController *)controller relatedMenuOpenedWithItems:(NSArray *)items withMethod:(enum JWRelatedInteraction)method -{ - -} - -- (void)playerViewController:(JWPlayerViewController *)controller relatedItemBeganPlaying:(JWPlayerItem *)item atIndex:(NSInteger)index withMethod:(enum JWRelatedMethod)method -{ - -} - -#pragma mark - DRM Delegate - -- (void)contentIdentifierForURL:(NSURL * _Nonnull)url completionHandler:(void (^ _Nonnull)(NSData * _Nullable))handler { - NSData *data = [url.host dataUsingEncoding:NSUTF8StringEncoding]; - handler(data); -} - -- (void)appIdentifierForURL:(NSURL * _Nonnull)url completionHandler:(void (^ _Nonnull)(NSData * _Nullable))handler { - if (_fairplayCertUrl == nil) { - return; - } - NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:_fairplayCertUrl]]; - NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (error) { - NSLog(@"DRM cert request error - %@", error.localizedDescription); - } - handler(data); - }]; - [task resume]; - -} - -- (void)contentKeyWithSPCData:(NSData * _Nonnull)spcData completionHandler:(void (^ _Nonnull)(NSData * _Nullable, NSDate * _Nullable, NSString * _Nullable))handler { - if (_processSpcUrl == nil) { - return; - } - NSMutableURLRequest *ckcRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:_processSpcUrl]]; - [ckcRequest setHTTPMethod:@"POST"]; - [ckcRequest setHTTPBody:spcData]; - [ckcRequest addValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"]; - [[[NSURLSession sharedSession] dataTaskWithRequest:ckcRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - if (error != nil || (httpResponse != nil && httpResponse.statusCode != 200)) { - NSLog(@"DRM ckc request error - %@", error.localizedDescription); - handler(nil, nil, nil); - return; - } - handler(data, nil, @"application/octet-stream"); - }] resume]; -} - -#pragma mark - AV Picture In Picture Delegate - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if (_playerView || _playerViewController) { - if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { - if (_playerView) { - [_playerView.player play]; - } else if (_playerViewController) { - [_playerViewController.player play]; - } - } else if (_playerView && [object isEqual:_playerView.pictureInPictureController] && [keyPath isEqualToString:@"isPictureInPicturePossible"]) { - - } - } -} - -- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController -{ - -} - -- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController -{ - -} - -- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController -{ - -} - -- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error -{ - -} - -- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController -{ - -} - -- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler -{ - -} - -#pragma mark - JWPlayer State Delegate - -- (void)jwplayerContentIsBuffering:(id)player -{ - if (self.onBuffer) { - self.onBuffer(@{}); - } -} - -- (void)jwplayer:(id)player isBufferingWithReason:(enum JWBufferReason)reason -{ - if (self.onBuffer) { - self.onBuffer(@{}); - } -} - -- (void)jwplayer:(id)player updatedBuffer:(double)percent position:(JWTimeData *)time -{ - if (self.onUpdateBuffer) { - self.onUpdateBuffer(@{@"percent": @(percent), @"position": time}); - } -} - -- (void)jwplayer:(id)player didFinishLoadingWithTime:(NSTimeInterval)loadTime -{ - if (self.onLoaded) { - self.onLoaded(@{}); - } -} - -- (void)jwplayer:(id)player isAttemptingToPlay:(JWPlayerItem *)playlistItem reason:(enum JWPlayReason)reason -{ - if (self.onAttemptPlay) { - self.onAttemptPlay(@{}); - } -} - -- (void)jwplayer:(id)player isPlayingWithReason:(enum JWPlayReason)reason -{ - if (self.onPlay) { - self.onPlay(@{}); - } - - _userPaused = NO; - _wasInterrupted = NO; -} - -- (void)jwplayer:(id)player willPlayWithReason:(enum JWPlayReason)reason -{ - if (self.onBeforePlay) { - self.onBeforePlay(@{}); - } -} - -- (void)jwplayer:(id)player didPauseWithReason:(enum JWPauseReason)reason -{ - if (self.onPause) { - self.onPause(@{}); - } - - if (!_wasInterrupted) { - _userPaused = YES; - } -} - -- (void)jwplayer:(id)player didBecomeIdleWithReason:(enum JWIdleReason)reason -{ - if (self.onIdle) { - self.onIdle(@{}); - } -} - -- (void)jwplayer:(id)player isVisible:(BOOL)isVisible -{ - if (self.onVisible) { - self.onVisible(@{@"visible": @(isVisible)}); - } -} - -- (void)jwplayerContentWillComplete:(id)player -{ - if (self.onBeforeComplete) { - self.onBeforeComplete(@{}); - } -} - -- (void)jwplayerContentDidComplete:(id)player -{ - if (self.onComplete) { - self.onComplete(@{}); - } -} - -- (void)jwplayer:(id)player didLoadPlaylistItem:(JWPlayerItem *)item at:(NSUInteger)index -{ - if (self.onPlaylistItem) { - NSMutableDictionary* sourceDict = [[NSMutableDictionary alloc] init]; - NSString *file; - - for (JWVideoSource* source in item.videoSources) { - [sourceDict setObject:source.file forKey:@"file"]; - [sourceDict setObject:source.label forKey:@"label"]; - [sourceDict setObject:@(source.defaultVideo) forKey:@"default"]; - - if (source.defaultVideo) { - file = [source.file absoluteString]; - } - } - - NSMutableDictionary* schedDict = [[NSMutableDictionary alloc] init]; - for (JWAdBreak* sched in item.adSchedule) { - [schedDict setObject:sched.offset forKey:@"offset"]; - [schedDict setObject:sched.tags forKey:@"tags"]; - [schedDict setObject:@(sched.type) forKey:@"type"]; - } - - NSMutableDictionary* trackDict = [[NSMutableDictionary alloc] init]; - for (JWMediaTrack* track in item.mediaTracks) { - [trackDict setObject:track.file forKey:@"file"]; - [trackDict setObject:track.label forKey:@"label"]; - [trackDict setObject:@(track.defaultTrack) forKey:@"default"]; - } - - NSDictionary* itemDict = [NSDictionary dictionaryWithObjectsAndKeys: - file, @"file", - item.mediaId, @"mediaId", - item.title, @"title", - item.description, @"description", - item.posterImage.absoluteString, @"image", - @(item.startTime), @"startTime", - item.vmapURL.absoluteString, @"adVmap", - item.recommendations.absoluteString, @"recommendations", - sourceDict, @"sources", - schedDict, @"adSchedule", - trackDict, @"tracks", - nil]; - - NSError *error; - NSData *data = [NSJSONSerialization dataWithJSONObject:itemDict options:NSJSONWritingPrettyPrinted error: &error]; - - self.onPlaylistItem(@{@"playlistItem": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], @"index": [NSNumber numberWithInteger:index]}); - } - - [item addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil]; -} - -- (void)jwplayer:(id)player didLoadPlaylist:(NSArray *)playlist -{ - if (self.onPlaylist) { - NSMutableArray* playlistArray = [[NSMutableArray alloc] init]; - - for (JWPlayerItem* item in playlist) { - NSString *file; - - NSMutableDictionary* sourceDict = [[NSMutableDictionary alloc] init]; - for (JWVideoSource* source in item.videoSources) { - [sourceDict setObject:source.file forKey:@"file"]; - [sourceDict setObject:source.label forKey:@"label"]; - [sourceDict setObject:@(source.defaultVideo) forKey:@"default"]; - - if (source.defaultVideo) { - file = [source.file absoluteString]; - } - } - - NSMutableDictionary* schedDict = [[NSMutableDictionary alloc] init]; - for (JWAdBreak* sched in item.adSchedule) { - [schedDict setObject:sched.offset forKey:@"offset"]; - [schedDict setObject:sched.tags forKey:@"tags"]; - [schedDict setObject:@(sched.type) forKey:@"type"]; - } - - NSMutableDictionary* trackDict = [[NSMutableDictionary alloc] init]; - for (JWMediaTrack* track in item.mediaTracks) { - [trackDict setObject:track.file forKey:@"file"]; - [trackDict setObject:track.label forKey:@"label"]; - [trackDict setObject:@(track.defaultTrack) forKey:@"default"]; - } - - NSDictionary* itemDict = [NSDictionary dictionaryWithObjectsAndKeys: - file, @"file", - item.mediaId, @"mediaId", - item.title, @"title", - item.description, @"description", - item.posterImage.absoluteString, @"image", - @(item.startTime), @"startTime", - item.vmapURL.absoluteString, @"adVmap", - item.recommendations.absoluteString, @"recommendations", - sourceDict, @"sources", - schedDict, @"adSchedule", - trackDict, @"tracks", - nil]; - - [playlistArray addObject:itemDict]; - } - - NSError *error; - NSData* data = [NSJSONSerialization dataWithJSONObject:playlistArray options:NSJSONWritingPrettyPrinted error: &error]; - - self.onPlaylist(@{@"playlist": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}); - } -} - -- (void)jwplayerPlaylistHasCompleted:(id)player -{ - if (self.onPlaylistComplete) { - self.onPlaylistComplete(@{}); - } -} - -- (void)jwplayer:(id)player usesMediaType:(enum JWMediaType)type -{ - -} - -- (void)jwplayer:(id)player seekedFrom:(NSTimeInterval)oldPosition to:(NSTimeInterval)newPosition -{ - if (self.onSeek) { - self.onSeek(@{@"from": @(oldPosition), @"to": @(newPosition)}); - } -} - -- (void)jwplayerHasSeeked:(id)player -{ - if (self.onSeeked) { - self.onSeeked(@{}); - } -} - -- (void)jwplayer:(id)player playbackRateChangedTo:(double)rate at:(NSTimeInterval)time -{ - if (self.onRateChanged) { - self.onRateChanged(@{@"rate": [NSNumber numberWithDouble:rate], @"at": [NSNumber numberWithDouble:time]}); - } -} - -- (void)jwplayer:(id)player updatedCues:(NSArray * _Nonnull)cues -{ -// if (_playerViewController) { -// [_playerViewController jwplayer:player updatedCues:cues]; -// } -} - -#pragma mark - JWPlayer Ad Delegate - -- (void)jwplayer:(id)player adEvent:(JWAdEvent *)event { - if (self.onAdEvent) { - self.onAdEvent(@{@"client": @(event.client), @"type": @(event.type)}); - } -} - -#pragma Mark - Casting methods - --(void)setUpCastController -{ - if (_playerView && _playerView.player && !_castController) { - _castController = [[JWCastController alloc] initWithPlayer:_playerView.player]; - _castController.delegate = self; - } - - [self scanForDevices]; -} - -- (void)scanForDevices -{ - if (_castController != nil) { - [_castController startDiscovery]; - } -} - -- (void)stopScanForDevices -{ - if (_castController != nil) { - [_castController stopDiscovery]; - } -} - -#if USE_GOOGLE_CAST -- (void)presentCastDialog -{ - [GCKCastContext.sharedInstance presentCastDialog]; -} - -- (void)startDiscovery -{ - [[GCKCastContext.sharedInstance discoveryManager] startDiscovery]; -} - -- (void)stopDiscovery -{ - [[GCKCastContext.sharedInstance discoveryManager] stopDiscovery]; -} - -- (BOOL)discoveryActive -{ - return [[GCKCastContext.sharedInstance discoveryManager] discoveryActive]; -} - -- (BOOL)hasDiscoveredDevices -{ - return [[GCKCastContext.sharedInstance discoveryManager] hasDiscoveredDevices]; -} - -- (GCKDiscoveryState)discoveryState -{ - return [[GCKCastContext.sharedInstance discoveryManager] discoveryState]; -} - -- (void)setPassiveScan:(BOOL)passive -{ - [[GCKCastContext.sharedInstance discoveryManager] setPassiveScan:passive]; -} - -- (GCKCastState)castState -{ - return [GCKCastContext.sharedInstance castState]; -} - -- (NSUInteger)deviceCount -{ - return [[GCKCastContext.sharedInstance discoveryManager] deviceCount]; -} -#endif - -- (NSArray *)availableDevices -{ - return _castController.availableDevices; -} - -- (JWCastingDevice*)connectedDevice -{ - return _castController.connectedDevice; -} - -- (void)connectToDevice:(JWCastingDevice*)device -{ - return [_castController connectToDevice:device]; -} - -- (void)cast -{ - return [_castController cast]; -} - -- (void)stopCasting -{ - return [_castController stopCasting]; -} - -#pragma mark - JWPlayer Cast Delegate - -- (void)castController:(JWCastController * _Nonnull)controller castingBeganWithDevice:(JWCastingDevice * _Nonnull)device { - if (self.onCasting) { - self.onCasting(@{}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller castingEndedWithError:(NSError * _Nullable)error { - if (self.onCastingEnded) { - self.onCastingEnded(@{@"error": error}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller castingFailedWithError:(NSError * _Nonnull)error { - if (self.onCastingFailed) { - self.onCastingFailed(@{@"error": error}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller connectedTo:(JWCastingDevice * _Nonnull)device { - if (self.onConnectedToCastingDevice) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - - [dict setObject:device.name forKey:@"name"]; - [dict setObject:device.identifier forKey:@"identifier"]; - - NSError *error; - NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error: &error]; - - self.onConnectedToCastingDevice(@{@"device": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller connectionFailedWithError:(NSError * _Nonnull)error { - if (self.onConnectionFailed) { - self.onConnectionFailed(@{@"error": error}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller connectionRecoveredWithDevice:(JWCastingDevice * _Nonnull)device { - if (self.onConnectionRecovered) { - self.onConnectionRecovered(@{}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller connectionSuspendedWithDevice:(JWCastingDevice * _Nonnull)device { - if (self.onConnectionTemporarilySuspended) { - self.onConnectionTemporarilySuspended(@{}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller devicesAvailable:(NSArray * _Nonnull)devices { - self.availableDevices = devices; - - if (self.onCastingDevicesAvailable) { - NSMutableArray *devicesInfo = [[NSMutableArray alloc] init]; - - for (JWCastingDevice *device in devices) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - - [dict setObject:device.name forKey:@"name"]; - [dict setObject:device.identifier forKey:@"identifier"]; - - [devicesInfo addObject:dict]; - } - - NSError *error; - NSData *data = [NSJSONSerialization dataWithJSONObject:devicesInfo options:NSJSONWritingPrettyPrinted error: &error]; - - self.onCastingDevicesAvailable(@{@"devices": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller disconnectedWithError:(NSError * _Nullable)error { - if (self.onDisconnectedFromCastingDevice) { - self.onDisconnectedFromCastingDevice(@{@"error": error}); - } -} - -#pragma mark - JWPlayer AV Delegate - -- (void)jwplayer:(id _Nonnull)player audioTracksUpdated:(NSArray * _Nonnull)levels { - if (self.onAudioTracks) { - self.onAudioTracks(@{}); - } -} - -- (void)jwplayer:(id _Nonnull)player audioTrackChanged:(NSInteger)currentLevel { - -} - -- (void)jwplayer:(id _Nonnull)player captionPresented:(NSArray * _Nonnull)caption at:(JWTimeData * _Nonnull)time { - -} - -- (void)jwplayer:(id _Nonnull)player captionTrackChanged:(NSInteger)index { - -} - -- (void)jwplayer:(id _Nonnull)player qualityLevelChanged:(NSInteger)currentLevel { - -} - -- (void)jwplayer:(id _Nonnull)player qualityLevelsUpdated:(NSArray * _Nonnull)levels { - -} - -- (void)jwplayer:(id _Nonnull)player updatedCaptionList:(NSArray * _Nonnull)options { - -} - -#pragma mark - JWPlayer audio session && interruption handling - -- (void)initAudioSession:(NSString*)category :(NSArray*)categoryOptions :(NSString*)mode -{ - [self setObservers]; - - BOOL somethingChanged = NO; - - if (![category isEqualToString:_audioCategory] || ![categoryOptions isEqualToArray:_audioCategoryOptions]) { - somethingChanged = YES; - _audioCategory = category; - _audioCategoryOptions = categoryOptions; - [self setCategory:category categoryOptions:categoryOptions]; - } - - if (![mode isEqualToString:_audioMode]) { - somethingChanged = YES; - _audioMode = mode; - [self setMode:mode]; - } - - if (somethingChanged) { - NSError* activationError = nil; - BOOL success = [_audioSession setActive:YES error:&activationError]; - NSLog(@"setActive - success: @%@, error: @%@", @(success), activationError); - } -} - -- (void)deinitAudioSession -{ - NSError* activationError = nil; - BOOL success = [_audioSession setActive:NO withOptions: AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&activationError]; - NSLog(@"setUnactive - success: @%@, error: @%@", @(success), activationError); - _audioSession = nil; - [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = @{}.mutableCopy; - [[UIApplication sharedApplication] endReceivingRemoteControlEvents]; -} - --(void)setObservers -{ - if (_audioSession == nil) { - _audioSession = [AVAudioSession sharedInstance]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleMediaServicesReset) - name:AVAudioSessionMediaServicesWereResetNotification - object:_audioSession]; - - [[NSNotificationCenter defaultCenter] addObserver: self - selector: @selector(audioSessionInterrupted:) - name: AVAudioSessionInterruptionNotification - object: _audioSession]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillResignActive:) - name:UIApplicationWillResignActiveNotification object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidEnterBackground:) - name:UIApplicationDidEnterBackgroundNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillEnterForeground:) - name:UIApplicationWillEnterForegroundNotification object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(audioRouteChanged:) - name:AVAudioSessionRouteChangeNotification - object:nil]; - } -} - --(void)setCategory:(NSString *)categoryName categoryOptions :(NSArray *)categoryOptions -{ - if (!_audioSession) { - _audioSession = [AVAudioSession sharedInstance]; - } - - NSString* category = nil; - - if ([categoryName isEqual:@"Ambient"]) { - category = AVAudioSessionCategoryAmbient; - } else if ([categoryName isEqual:@"SoloAmbient"]) { - category = AVAudioSessionCategorySoloAmbient; - } else if ([categoryName isEqual:@"Playback"]) { - category = AVAudioSessionCategoryPlayback; - } else if ([categoryName isEqual:@"Record"]) { - category = AVAudioSessionCategoryRecord; - } else if ([categoryName isEqual:@"PlayAndRecord"]) { - category = AVAudioSessionCategoryPlayAndRecord; - } else if ([categoryName isEqual:@"MultiRoute"]) { - category = AVAudioSessionCategoryMultiRoute; - } else { - category = AVAudioSessionCategoryPlayback; - } - - int options = 0; - - if ([categoryOptions containsObject:@"MixWithOthers"]) { - options |= AVAudioSessionCategoryOptionMixWithOthers; - } else if ([categoryOptions containsObject:@"DuckOthers"]) { - options |= AVAudioSessionCategoryOptionDuckOthers; - } else if ([categoryOptions containsObject:@"AllowBluetooth"]) { - options |= AVAudioSessionCategoryOptionAllowBluetooth; - } else if ([categoryOptions containsObject:@"InterruptSpokenAudioAndMix"]) { - options |= AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers; - } else if ([categoryOptions containsObject:@"AllowBluetoothA2DP"]) { - options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; - } else if ([categoryOptions containsObject:@"AllowAirPlay"]) { - options |= AVAudioSessionCategoryOptionAllowAirPlay; - } else if ([categoryOptions containsObject:@"OverrideMutedMicrophone"]) { - if (@available(iOS 14.5, *)) { - options |= AVAudioSessionCategoryOptionOverrideMutedMicrophoneInterruption; - } else { - // Fallback on earlier versions - } - } - - NSError* categoryError = nil; - BOOL success = [_audioSession setCategory:category withOptions:options error:&categoryError]; - NSLog(@"setCategory - success: @%@, error: @%@", @(success), categoryError); -} - --(void)setMode:(NSString *)modeName -{ - if (!_audioSession) { - _audioSession = [AVAudioSession sharedInstance]; - } - - NSString* mode = nil; - - if ([modeName isEqual:@"Default"]) { - mode = AVAudioSessionModeDefault; - } else if ([modeName isEqual:@"VoiceChat"]) { - mode = AVAudioSessionModeVoiceChat; - } else if ([modeName isEqual:@"VideoChat"]) { - mode = AVAudioSessionModeVideoChat; - } else if ([modeName isEqual:@"GameChat"]) { - mode = AVAudioSessionModeGameChat; - } else if ([modeName isEqual:@"VideoRecording"]) { - mode = AVAudioSessionModeVideoRecording; - } else if ([modeName isEqual:@"Measurement"]) { - mode = AVAudioSessionModeMeasurement; - } else if ([modeName isEqual:@"MoviePlayback"]) { - mode = AVAudioSessionModeMoviePlayback; - } else if ([modeName isEqual:@"SpokenAudio"]) { - mode = AVAudioSessionModeSpokenAudio; - } else if ([modeName isEqual:@"VoicePrompt"]) { - if (@available(iOS 12.0, *)) { - mode = AVAudioSessionModeVoicePrompt; - } else { - // Fallback on earlier versions - } - } - - if (mode) { - NSError* modeError = nil; - BOOL success = [_audioSession setMode:mode error:&modeError]; - NSLog(@"setMode - success: @%@, error: @%@", @(success), modeError); - } -} - -// Interupted --(void)audioSessionInterrupted:(NSNotification*)note -{ - NSNumber *interruptionType = [[note userInfo] objectForKey:AVAudioSessionInterruptionTypeKey]; - NSNumber *interruptionOption = [[note userInfo] objectForKey:AVAudioSessionInterruptionOptionKey]; - - switch (interruptionType.unsignedIntegerValue) { - case AVAudioSessionInterruptionTypeBegan: { - _wasInterrupted = YES; - - if (_playerView) { - [_playerView.player pause]; - } else if (_playerViewController) { - [_playerViewController.player pause]; - } - } break; - case AVAudioSessionInterruptionTypeEnded: { - if (interruptionOption.unsignedIntegerValue == AVAudioSessionInterruptionOptionShouldResume || (!_userPaused && _backgroundAudioEnabled)) { - if (_playerView) { - [self->_playerView.player play]; - } else if (_playerViewController) { - [self->_playerViewController.player play]; - } - } - } break; - default: - break; - } -} - -// Service reset --(void)handleMediaServicesReset -{ - // • Handle this notification by fully reconfiguring audio -} - -// Inactive -// Hack for ios 14 stopping audio when going to background --(void)applicationWillResignActive:(NSNotification *)notification { - if (!_userPaused && _backgroundAudioEnabled) { - if (_playerView && [_playerView.player getState] == JWPlayerStatePlaying) { - [_playerView.player play]; - } else if (_playerViewController && [_playerViewController.player getState] == JWPlayerStatePlaying) { - [_playerViewController.player play]; - } - } -} - -// Background -- (void)applicationDidEnterBackground:(NSNotification *)notification -{ - -} - - -// Active --(void)applicationWillEnterForeground:(NSNotification *)notification{ - if (!_userPaused && _backgroundAudioEnabled) { - if (_playerView && [_playerView.player getState] == JWPlayerStatePlaying) { - [_playerView.player play]; - } else if (_playerViewController && [_playerViewController.player getState] == JWPlayerStatePlaying) { - [_playerViewController.player play]; - } - } -} - -// Route change -- (void)audioRouteChanged:(NSNotification *)notification -{ - NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; -// NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; - if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { - if (_playerView) { - [_playerView.player pause]; - } else if (_playerViewController) { - [_playerViewController.player pause]; - } - } -} - -@end diff --git a/ios/RNJWPlayer/RNJWPlayerView.swift b/ios/RNJWPlayer/RNJWPlayerView.swift new file mode 100644 index 00000000..4ece536d --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerView.swift @@ -0,0 +1,1806 @@ +// +// RNJWPlayerViewController.m +// RNJWPlayer +// +// Created by Chaim Paneth on 3/30/22. +// + +import UIKit +import AVFoundation +import AVKit +import MediaPlayer +import React +import GoogleCast +import JWPlayerKit + +class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDelegate, JWCastDelegate, JWAVDelegate, JWPlayerViewDelegate, JWPlayerViewControllerDelegate, JWDRMContentKeyDataSource, AVPictureInPictureControllerDelegate { + + // MARK: - RNJWPlayer allocation + + var playerViewController:RNJWPlayerViewController! + var playerView: JWPlayerView! + var audioSession: AVAudioSession! + var pipEnabled: Bool = true + var backgroundAudioEnabled: Bool = true + var userPaused: Bool = false + var wasInterrupted: Bool = false + var interfaceBehavior: JWInterfaceBehavior! + var fairplayCertUrl: String! + var processSpcUrl: String! + var contentUUID: String! + var audioCategory: String! + var audioMode: String! + var audioCategoryOptions: [String]! + var settingConfig: Bool = false + var pendingConfig: Bool = false + var currentConfig: [String : Any]! + var castController: JWCastController! + var isCasting: Bool = false + var availableDevices: [AnyObject]! + + @objc var onBuffer:RCTDirectEventBlock? + @objc var onUpdateBuffer:RCTDirectEventBlock? + @objc var onPlay:RCTDirectEventBlock? + @objc var onBeforePlay:RCTDirectEventBlock? + @objc var onAttemptPlay:RCTDirectEventBlock? + @objc var onPause:RCTDirectEventBlock? + @objc var onIdle:RCTDirectEventBlock? + @objc var onPlaylistItem:RCTDirectEventBlock? + @objc var onLoaded:RCTDirectEventBlock? + @objc var onVisible:RCTDirectEventBlock? + @objc var onTime:RCTDirectEventBlock? + @objc var onSeek:RCTDirectEventBlock? + @objc var onSeeked:RCTDirectEventBlock? + @objc var onRateChanged:RCTDirectEventBlock? + @objc var onPlaylist:RCTDirectEventBlock? + @objc var onPlaylistComplete:RCTDirectEventBlock? + @objc var onBeforeComplete:RCTDirectEventBlock? + @objc var onComplete:RCTDirectEventBlock? + @objc var onAudioTracks:RCTDirectEventBlock? + @objc var onPlayerReady:RCTDirectEventBlock? + @objc var onSetupPlayerError:RCTDirectEventBlock? + @objc var onPlayerError:RCTDirectEventBlock? + @objc var onPlayerWarning:RCTDirectEventBlock? + @objc var onPlayerAdWarning:RCTDirectEventBlock? + @objc var onPlayerAdError:RCTDirectEventBlock? + @objc var onAdEvent:RCTDirectEventBlock? + @objc var onAdTime:RCTDirectEventBlock? + @objc var onScreenTapped:RCTDirectEventBlock? + @objc var onControlBarVisible:RCTDirectEventBlock? + @objc var onFullScreen:RCTDirectEventBlock? + @objc var onFullScreenRequested:RCTDirectEventBlock? + @objc var onFullScreenExit:RCTDirectEventBlock? + @objc var onFullScreenExitRequested:RCTDirectEventBlock? + @objc var onPlayerSizeChange:RCTDirectEventBlock? + @objc var onCastingDevicesAvailable:RCTDirectEventBlock? + @objc var onConnectedToCastingDevice:RCTDirectEventBlock? + @objc var onDisconnectedFromCastingDevice:RCTDirectEventBlock? + @objc var onConnectionTemporarilySuspended:RCTDirectEventBlock? + @objc var onConnectionRecovered:RCTDirectEventBlock? + @objc var onConnectionFailed:RCTDirectEventBlock? + @objc var onCasting:RCTDirectEventBlock? + @objc var onCastingEnded:RCTDirectEventBlock? + @objc var onCastingFailed:RCTDirectEventBlock? + + init() { + super.init(frame: CGRect(x: 20, y: 0, width: UIScreen.main.bounds.width - 40, height: 300)) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil) + } + + deinit { + self.startDeinitProcess() + } + + override func removeFromSuperview() { + self.startDeinitProcess() + } + + func startDeinitProcess() { + NotificationCenter.default.removeObserver(self, name:UIDevice.orientationDidChangeNotification, object:nil) + + self.reset() + super.removeFromSuperview() + } + + func reset() { + NotificationCenter.default.removeObserver(self, name:AVAudioSession.mediaServicesWereResetNotification, object:audioSession) + NotificationCenter.default.removeObserver(self, name:AVAudioSession.interruptionNotification, object:audioSession) + + NotificationCenter.default.removeObserver(self, name:UIApplication.willResignActiveNotification, object:nil) + NotificationCenter.default.removeObserver(self, name:UIApplication.didEnterBackgroundNotification, object:nil) + NotificationCenter.default.removeObserver(self, name:UIApplication.willEnterForegroundNotification, object:nil) + NotificationCenter.default.removeObserver(self, name:AVAudioSession.routeChangeNotification, object:nil) + + if (playerViewController != nil) || (playerView != nil) { +// playerViewController.player.currentItem!.removeObserver(self, forKeyPath:"playbackLikelyToKeepUp", context:nil) + if (playerView != nil) { + NotificationCenter.default.removeObserver(self, forKeyPath:"isPictureInPicturePossible", context:nil) + } + } + + self.removePlayerView() + self.dismissPlayerViewController() + + self.deinitAudioSession() + + } + + override func layoutSubviews() { + super.layoutSubviews() + + if self.playerView != nil { + self.playerView.frame = self.frame + } + + if self.playerViewController != nil { + self.playerViewController.view.frame = self.frame + } + } + + @objc func rotated(notification: Notification) { + if UIDevice.current.orientation.isLandscape { + print("Landscape") + } + + if UIDevice.current.orientation.isPortrait { + print("Portrait") + } + + self.layoutSubviews() + } + + func shouldAutorotate() -> Bool { + return false + } + + // MARK: - RNJWPlayer props + + func setLicense(license:String?) { + if (license != nil) { + JWPlayerKitLicense.setLicenseKey(license!) + } else { + print("JW SDK License key not set.") + } + } + + func keysForDifferingValues(in dict1: [String: Any], and dict2: [String: Any]) -> [String] { + var diffKeys = [String]() + + for key in dict1.keys { + if let value1 = dict1[key], let value2 = dict2[key] { + if !areEqual(value1, value2) { + diffKeys.append(key) + } + } else { + // One of the dictionaries does not contain the key + diffKeys.append(key) + } + } + + return diffKeys + } + + func areEqual(_ value1: Any, _ value2: Any) -> Bool { + let mirror1 = Mirror(reflecting: value1) + let mirror2 = Mirror(reflecting: value2) + + if mirror1.displayStyle != mirror2.displayStyle { + // Different types + return false + } + + switch (value1, value2) { + case let (dict1 as [String: Any], dict2 as [String: Any]): + // Both are dictionaries, recursively compare + return keysForDifferingValues(in: dict1, and: dict2).isEmpty + case let (array1 as [Any], array2 as [Any]): + // Both are arrays, compare elements + return array1.count == array2.count && zip(array1, array2).allSatisfy(areEqual) + default: + // Use default String description for comparison + return String(describing: value1) == String(describing: value2) + } + } + + + func dictionariesAreEqual(_ dict1: [String: Any]?, _ dict2: [String: Any]?) -> Bool { + return NSDictionary(dictionary: dict1 ?? [:]).isEqual(to: dict2 ?? [:]) + } + + @objc func setJsonConfig(_ config: JSONObject) { + do { + var configuration = try JWPlayerConfigurationBuilder().configuration(json: config).build() + playerViewController.player.configurePlayer(with: configuration) + } catch { + print(error) + } + } + + @objc func setConfig(_ config: [String: Any]) { + // Create mutable copies of the dictionaries + var configCopy = config + var currentConfigCopy = currentConfig + + // Remove the playlist key + configCopy.removeValue(forKey: "playlist") + currentConfigCopy?.removeValue(forKey: "playlist") + + // Compare dictionaries without the playlist key + if (currentConfigCopy == nil) || !dictionariesAreEqual(configCopy, currentConfigCopy!) { + print("There are differences other than the 'playlist' key.") + + if (currentConfigCopy != nil) { + let diffKeys = keysForDifferingValues(in: configCopy, and: currentConfigCopy!) + print("There are differences in these keys: \(diffKeys)") + } else { + print("It's a new config") + } + + setNewConfig(config: config) + } else { + // Compare original dictionaries + if !dictionariesAreEqual(currentConfig, config) { + print("The only difference is the 'playlist' key.") + + var playlistArray = [JWPlayerItem]() + + if let playlist = config["playlist"] as? [AnyObject] { + for item in playlist { + if let playerItem = try? getPlayerItem(item: item as! [String: Any]) { + playlistArray.append(playerItem) + } + } + } + + if let playerViewController = playerViewController { + playerViewController.player.loadPlaylist(items: playlistArray) + } else if let playerView = playerView { + playerView.player.loadPlaylist(items: playlistArray) + } else { + setNewConfig(config: config) + } + } else { + print("There are no differences.") + } + } + } + + func setNewConfig(config: [String : Any]) { +// let data:Data! = try? JSONSerialization.data(withJSONObject: config, options:.prettyPrinted) +// let c = try? JSONDecoder().decode(Config.self, from: data) +// +// let jwConfig = try? Config(config) + + currentConfig = config + + if !settingConfig { + pendingConfig = false + settingConfig = true + + let license = config["license"] as? String + self.setLicense(license: license) + + if let bae = config["backgroundAudioEnabled"] as? Bool, let pe = config["pipEnabled"] as? Bool { + backgroundAudioEnabled = bae + pipEnabled = pe + } + + if backgroundAudioEnabled || pipEnabled { + let category = config["category"] as? String + let categoryOptions = config["categoryOptions"] as? [String] + let mode = config["mode"] as? String + + self.initAudioSession(category: category, categoryOptions: categoryOptions, mode: mode) + } else { + self.deinitAudioSession() + } + + do { + let viewOnly = config["viewOnly"] as? Bool + if viewOnly == true { + self.setupPlayerView(config: config, playerConfig: try self.getPlayerConfiguration(config: config)) + } else { + self.setupPlayerViewController(config: config, playerConfig: try self.getPlayerConfiguration(config: config)) + } + } catch { + print(error) + } + + processSpcUrl = config["processSpcUrl"] as? String + fairplayCertUrl = config["fairplayCertUrl"] as? String + contentUUID = config["contentUUID"] as? String + } else { + pendingConfig = true + } + } + + @objc func setControls(controls:Bool) { + self.toggleUIGroup(view: playerViewController.view, name: "JWPlayerKit.InterfaceView", ofSubview: nil, show: controls) + } + + // MARK: - RNJWPlayer styling + + func colorWithHexString(hex:String!) -> UIColor! { + var cString = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + // String should be 6 or 8 characters + if cString.count < 6 { + return UIColor.gray + } + + // Strip 0X if it appears + if cString.hasPrefix("0X") { + cString = String(cString.dropFirst(2)) + } + + if cString.count != 6 { + return UIColor.gray + } + + // Separate into r, g, b substrings + let startIndex = cString.startIndex + let endIndex = cString.index(startIndex, offsetBy: 2) + let rString = String(cString[startIndex.. JWPlayerItem { + let itemBuilder = JWPlayerItemBuilder() + + if let newFile = item["file"] as? String, + let url = URL(string: newFile) { + + if url.scheme != nil && url.host != nil { + itemBuilder.file(url) + } else { + if let encodedString = newFile.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed), + let encodedUrl = URL(string: encodedString) { + itemBuilder.file(encodedUrl) + } + } + } + + // Process sources + if let itemSources = item["sources"] as? [AnyObject], !itemSources.isEmpty { + var sourcesArray = [JWVideoSource]() + + for source in itemSources.compactMap({ $0 as? [String: Any] }) { + if let file = source["file"] as? String, + let fileURL = URL(string: file), + let label = source["label"] as? String, + let isDefault = source["default"] as? Bool { + + let sourceBuilder = JWVideoSourceBuilder() + sourceBuilder.file(fileURL) + sourceBuilder.label(label) + sourceBuilder.defaultVideo(isDefault) + + if let sourceItem = try? sourceBuilder.build() { + sourcesArray.append(sourceItem) + } + } + } + + itemBuilder.videoSources(sourcesArray) + } + + // Process other properties + if let mediaId = item["mediaId"] as? String { + itemBuilder.mediaId(mediaId) + } + + if let title = item["title"] as? String { + itemBuilder.title(title) + } + + if let description = item["description"] as? String { + itemBuilder.description(description) + } + + if let image = item["image"] as? String, let imageURL = URL(string: image) { + itemBuilder.posterImage(imageURL) + } + + if let startTime = item["startTime"] as? Double { + itemBuilder.startTime(startTime) + } + + if let recommendations = item["recommendations"] as? String, let recURL = URL(string: recommendations) { + itemBuilder.recommendations(recURL) + } + + // Process tracks + if let tracksItem = item["tracks"] as? [AnyObject], !tracksItem.isEmpty { + var tracksArray = [JWMediaTrack]() + + for trackItem in tracksItem.compactMap({ $0 as? [String: Any] }) { + if let file = trackItem["file"] as? String, + let fileURL = URL(string: file), + let label = trackItem["label"] as? String, + let isDefault = trackItem["default"] as? Bool { + + let trackBuilder = JWCaptionTrackBuilder() + trackBuilder.file(fileURL) + trackBuilder.label(label) + trackBuilder.defaultTrack(isDefault) + + if let track = try? trackBuilder.build() { + tracksArray.append(track) + } + } + } + + itemBuilder.mediaTracks(tracksArray) + } + + // Process adSchedule + if let adsItem = item["adSchedule"] as? [AnyObject], !adsItem.isEmpty { + var adsArray = [JWAdBreak]() + + for adItem in adsItem.compactMap({ $0 as? [String: Any] }) { + if let offsetString = adItem["offset"] as? String, + let tag = adItem["tag"] as? String, + let encodedString = tag.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed), + let tagURL = URL(string: encodedString), + let offset = JWAdOffset.from(string: offsetString) { + let adBreakBuilder = JWAdBreakBuilder() + adBreakBuilder.offset(offset) + adBreakBuilder.tags([tagURL]) + + if let adBreak = try? adBreakBuilder.build() { + adsArray.append(adBreak) + } + } + } + + if !adsArray.isEmpty { + itemBuilder.adSchedule(breaks: adsArray) + } + } + + // Process adVmap + if let adVmap = item["adVmap"] as? String, let adVmapURL = URL(string: adVmap) { + itemBuilder.adSchedule(vmapURL: adVmapURL) + } + + let playerItem = try itemBuilder.build() + + return playerItem + + } + + func getPlayerConfiguration(config: [String: Any]) throws -> JWPlayerConfiguration { + let configBuilder:JWPlayerConfigurationBuilder! = JWPlayerConfigurationBuilder() + + var playlistArray = [JWPlayerItem]() + + if let playlist = config["playlist"] as? [[String: Any]] { + for item in playlist { + if let playerItem = try? self.getPlayerItem(item: item) { + playlistArray.append(playerItem) + } + } + configBuilder.playlist(items: playlistArray) + } + + if let autostart = config["autostart"] as? Bool { + configBuilder.autostart(autostart) + } + + if let repeatContent = config["repeat"] as? Bool, repeatContent { + configBuilder.repeatContent(repeatContent) + } + + if let preload = config["preload"] as? String { + configBuilder.preload(RCTConvert.JWPreload(preload)) + } + + if let related = config["related"] as? [String: Any] { + let relatedBuilder = JWRelatedContentConfigurationBuilder() + + relatedBuilder.onClick(RCTConvert.JWRelatedOnClick(related["onClick"] as! String)) + relatedBuilder.onComplete(RCTConvert.JWRelatedOnComplete(related["onComplete"] as! String)) + relatedBuilder.heading(related["heading"] as! String) + relatedBuilder.url(URL(string: related["url"] as? String ?? "")!) + relatedBuilder.autoplayMessage(related["autoplayMessage"] as! String) + relatedBuilder.autoplayTimer(related["autoplayTimer"] as? Int ?? 0) + + let relatedContent = relatedBuilder.build() + configBuilder.related(relatedContent) + } + + var error: Error? + + if let ads = config["advertising"] as? [String: Any] { + var advertisingConfig: JWAdvertisingConfig? + + var jwAdClient = JWAdClient.unknown + if let adClientString = ads["adClient"] as? String { + jwAdClient = RCTConvert.JWAdClient(adClientString) + } + + switch jwAdClient { + case .JWPlayer: + advertisingConfig = RNJWPlayerAds.configureVAST(with: ads) + case .GoogleIMA: + advertisingConfig = RNJWPlayerAds.configureIMA(with: ads) + case .GoogleIMADAI: + advertisingConfig = RNJWPlayerAds.configureIMADAI(with: ads) + default: + advertisingConfig = RNJWPlayerAds.configureVAST(with: ads) + break + } + + // Handle error if any + if let error = error { + print("Error configuring ads: \(error.localizedDescription)") + } + + if (advertisingConfig != nil) { + configBuilder.advertising(advertisingConfig!) + } + } + + let playerConfig = try configBuilder.build() + + return playerConfig + } + + // MARK: - JWPlayer View Controller helpers + + func setupPlayerViewController(config: [String: Any], playerConfig: JWPlayerConfiguration) { + if playerViewController == nil { + playerViewController = RNJWPlayerViewController() + playerViewController.parentView = self + + DispatchQueue.main.async { [self] in + if self.reactViewController() != nil { + self.reactViewController()!.addChild(self.playerViewController) + self.playerViewController.didMove(toParent: self.reactViewController()) + } else { + self.reactAddController(toClosestParent: self.playerViewController) + } + } + + playerViewController.view.frame = self.frame + self.addSubview(playerViewController.view) + playerViewController.setDelegates() + } + + if let ib = config["interfaceBehavior"] as? String { + interfaceBehavior = RCTConvert.JWInterfaceBehavior(ib) + } + + if let interfaceFadeDelay = config["interfaceFadeDelay"] as? NSNumber { + playerViewController.interfaceFadeDelay = interfaceFadeDelay.doubleValue + } + + if let forceFullScreenOnLandscape = config["fullScreenOnLandscape"] as? Bool { + playerViewController.forceFullScreenOnLandscape = forceFullScreenOnLandscape + } + + if let forceLandscapeOnFullScreen = config["landscapeOnFullScreen"] as? Bool { + playerViewController.forceLandscapeOnFullScreen = forceLandscapeOnFullScreen + } + + if let enableLockScreenControls = config["enableLockScreenControls"] as? Bool { + playerViewController.enableLockScreenControls = enableLockScreenControls && backgroundAudioEnabled + } + + if let allowsPictureInPicturePlayback = config["allowsPictureInPicturePlayback"] as? Bool { + playerViewController.allowsPictureInPicturePlayback = allowsPictureInPicturePlayback + } + + if let styling = config["styling"] as? [String: Any] { + self.setStyling(styling: styling) + } + + if let nextUpStyle = config["nextUpStyle"] as? [String: Any] { + let nextUpBuilder = JWNextUpStyleBuilder() + + if let offsetSeconds = nextUpStyle["offsetSeconds"] as? Double { + nextUpBuilder.timeOffset(seconds: offsetSeconds) + } + + if let offsetPercentage = nextUpStyle["offsetPercentage"] as? Double { + nextUpBuilder.timeOffset(seconds: offsetPercentage) + } + + do { + playerViewController.nextUpStyle = try nextUpBuilder.build() + } catch { + print(error) + } + } + + // playerViewController.adInterfaceStyle + // playerViewController.logo + // playerView.videoGravity = 0; + // playerView.captionStyle + + if let offlineMsg = config["offlineMessage"] as? String { + playerViewController.offlineMessage = offlineMsg + } + + if let offlineImg = config["offlineImage"] as? String { + if let imageUrl = URL(string: offlineImg), imageUrl.isFileURL { + if let imageData = try? Data(contentsOf: imageUrl), + let image = UIImage(data: imageData) { + playerViewController.offlinePosterImage = image + } + } + } + + self.presentPlayerViewController(configuration: playerConfig) + } + + func dismissPlayerViewController() { + if (playerViewController != nil) { + playerViewController.player.pause() // hack for stop not always stopping on unmount + playerViewController.player.stop() + playerViewController.enableLockScreenControls = false + + // hack for stop not always stopping on unmount + let configBuilder:JWPlayerConfigurationBuilder! = JWPlayerConfigurationBuilder() + configBuilder.playlist(items: []) + + do { + let configuration: JWPlayerConfiguration = try configBuilder.build() + playerViewController.player.configurePlayer(with: configuration) + } catch { + print(error) + } + + + playerViewController.parentView = nil + playerViewController.setVisibility(.hidden, for:[.pictureInPictureButton]) + playerViewController.view.removeFromSuperview() + playerViewController.removeFromParent() + playerViewController.willMove(toParent: nil) + playerViewController.removeDelegates() + playerViewController = nil + } + } + + func presentPlayerViewController(configuration: JWPlayerConfiguration!) { + if configuration != nil { + playerViewController.player.configurePlayer(with: configuration) + if (interfaceBehavior != nil) { + playerViewController.interfaceBehavior = interfaceBehavior + } + } + } + + // MARK: - JWPlayer View helpers + + func setupPlayerView(config: [String: Any], playerConfig: JWPlayerConfiguration) { + playerView = JWPlayerView(frame:self.superview!.frame) + + playerView.delegate = self + playerView.player.delegate = self + playerView.player.playbackStateDelegate = self + playerView.player.adDelegate = self + playerView.player.avDelegate = self + playerView.player.contentKeyDataSource = self + + playerView.player.configurePlayer(with: playerConfig) + + if pipEnabled { + let pipController:AVPictureInPictureController! = playerView.pictureInPictureController + pipController.delegate = self + + pipController.addObserver(self, forKeyPath:"isPictureInPicturePossible", options:[.new, .initial], context:nil) + } + + self.addSubview(self.playerView) + + if let autostart = config["autostart"] as? Bool, autostart { + playerView.player.play() + } + + // Time observers + weak var weakSelf:RNJWPlayerView! = self + playerView.player.adTimeObserver = { (time:JWTimeData!) in + weakSelf.onAdTime?(["position": time.position, "duration": time.duration]) + } + + playerView.player.mediaTimeObserver = { (time:JWTimeData!) in + weakSelf.onTime?(["position": time.position, "duration": time.duration]) + } + } + + func removePlayerView() { + if (playerView != nil) { + playerView.player.stop() + playerView.removeFromSuperview() + playerView = nil + } + } + + func toggleUIGroup(view: UIView, name: String, ofSubview: String?, show: Bool) { + let subviews = view.subviews + + for subview in subviews { + if NSStringFromClass(subview.classForCoder) == name && (ofSubview == nil || NSStringFromClass(subview.superview!.classForCoder) == name) { + subview.isHidden = !show + } else { + toggleUIGroup(view: subview, name: name, ofSubview: ofSubview, show: show) + } + } + } + + func setVisibility(isVisible:Bool, forControls controls:[String]) { + var _controls:[JWControlType]! = [JWControlType]() + + for control:String? in controls { + if (control != nil && !control!.isEmpty) { + let type:JWControlType = RCTConvert.JWControlType(control!) + _controls.append(type) + } + } + + if (_controls.count > 0) { + playerViewController.setVisibility(isVisible ? .visible : .hidden, for: _controls!) + } + } + + // MARK: - JWPlayer Delegate + + func jwplayerIsReady(_ player:JWPlayer) { + settingConfig = false + self.onPlayerReady?([:]) + + if pendingConfig && currentConfig != nil { + self.setConfig(currentConfig) + } + } + + func jwplayer(_ player:JWPlayer, failedWithError code:UInt, message:String) { + self.onPlayerError?(["error": message]) + } + + func jwplayer(_ player:JWPlayer, failedWithSetupError code:UInt, message:String) { + self.onSetupPlayerError?(["error": message]) + } + + func jwplayer(_ player:JWPlayer, encounteredWarning code:UInt, message:String) { + self.onPlayerWarning?(["warning": message]) + } + + func jwplayer(_ player:JWPlayer, encounteredAdError code:UInt, message:String) { + self.onPlayerAdError?(["error": message]) + } + + + func jwplayer(_ player:JWPlayer, encounteredAdWarning code:UInt, message:String) { + self.onPlayerAdWarning?(["warning": message]) + } + + + // MARK: - JWPlayer View Delegate + + func playerView(_ view:JWPlayerView, sizeChangedFrom oldSize:CGSize, to newSize:CGSize) { + let oldSizeDict: [String: Any] = [ + "width": oldSize.width, + "height": oldSize.height + ] + + let newSizeDict: [String: Any] = [ + "width": newSize.width, + "height": newSize.height + ] + + let sizesDict: [String: Any] = [ + "oldSize": oldSizeDict, + "newSize": newSizeDict + ] + + do { + let data = try JSONSerialization.data(withJSONObject: sizesDict, options: .prettyPrinted) + self.onPlayerSizeChange?(["sizes": data]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + // MARK: - JWPlayer View Controller Delegate + + func playerViewController(_ controller:JWPlayerViewController, sizeChangedFrom oldSize:CGSize, to newSize:CGSize) { + let oldSizeDict: [String: Any] = [ + "width": oldSize.width, + "height": oldSize.height + ] + + let newSizeDict: [String: Any] = [ + "width": newSize.width, + "height": newSize.height + ] + + let sizesDict: [String: Any] = [ + "oldSize": oldSizeDict, + "newSize": newSizeDict + ] + + do { + let data = try JSONSerialization.data(withJSONObject: sizesDict, options: .prettyPrinted) + self.onPlayerSizeChange?(["sizes": data]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + func playerViewController(_ controller:JWPlayerViewController, screenTappedAt position:CGPoint) { + self.onScreenTapped?(["x": position.x, "y": position.y]) + } + + func playerViewController(_ controller:JWPlayerViewController, controlBarVisibilityChanged isVisible:Bool, frame:CGRect) { + self.onControlBarVisible?(["visible": isVisible]) + } + + func playerViewControllerWillGoFullScreen(_ controller:JWPlayerViewController) -> JWFullScreenViewController? { + self.onFullScreenRequested?([:]) + return nil + } + + func playerViewControllerDidGoFullScreen(_ controller:JWPlayerViewController) { + self.onFullScreen?([:]) + } + + func playerViewControllerWillDismissFullScreen(_ controller:JWPlayerViewController) { + self.onFullScreenExitRequested?([:]) + } + + func playerViewControllerDidDismissFullScreen(_ controller:JWPlayerViewController) { + self.onFullScreenExit?([:]) + } + + func playerViewController(_ controller:JWPlayerViewController, relatedMenuClosedWithMethod method: JWRelatedInteraction) { + + } + + func playerViewController(_ controller: JWPlayerViewController, relatedMenuOpenedWithItems items: [JWPlayerItem], withMethod method: JWRelatedInteraction) { + + } + + func playerViewController(_ controller: JWPlayerViewController, relatedItemBeganPlaying item: JWPlayerItem, atIndex index: Int, withMethod method: JWRelatedMethod) { + + } + + // MARK: Time events + + func onAdTimeEvent(time:JWTimeData) { + self.onAdTime?(["position": time.position, "duration": time.duration]) + } + + func onMediaTimeEvent(time:JWTimeData) { + self.onTime?(["position": time.position, "duration": time.duration]) + } + + // MARK: - DRM Delegate + + func contentIdentifierForURL(_ url: URL, completionHandler handler: @escaping (Data?) -> Void) { + let data:Data! = url.host?.data(using: String.Encoding.utf8) + handler(data) + } + + func appIdentifierForURL(_ url: URL, completionHandler handler: @escaping (Data?) -> Void) { + guard let fairplayCertUrlString = fairplayCertUrl, let finalUrl = URL(string: fairplayCertUrlString) else { + return + } + + let request = URLRequest(url: finalUrl) + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + print("DRM cert request error - \(error.localizedDescription)") + } + handler(data) + } + task.resume() + } + + func contentKeyWithSPCData(_ spcData: Data, completionHandler handler: @escaping (Data?, Date?, String?) -> Void) { + if processSpcUrl == nil { + return + } + + guard let processSpcUrl = URL(string: processSpcUrl) else { + print("Invalid processSpcUrl") + handler(nil, nil, nil) + return + } + + var ckcRequest = URLRequest(url: processSpcUrl) + ckcRequest.httpMethod = "POST" + ckcRequest.httpBody = spcData + ckcRequest.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type") + + URLSession.shared.dataTask(with: ckcRequest) { (data, response, error) in + if let httpResponse = response as? HTTPURLResponse, (error != nil || httpResponse.statusCode != 200) { + print("DRM ckc request error - %@", error?.localizedDescription ?? "Unknown error") + handler(nil, nil, nil) + return + } + + handler(data, nil, "application/octet-stream") + }.resume() + } + + // MARK: - AV Picture In Picture Delegate + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { +// if let playerView = playerView { +// if keyPath == "playbackLikelyToKeepUp" { +// playerView.player.play() +// } +// } else if let playerViewController = playerViewController { +// if keyPath == "playbackLikelyToKeepUp" { +// playerViewController.player.play() +// } +// } +// + if let keyPath = keyPath, keyPath == "isPictureInPicturePossible", let playerView = playerView, object as? AVPictureInPictureController == playerView.pictureInPictureController { + // Your code here for handling isPictureInPicturePossible + } + } + + func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) { + + } + + func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) { + + } + + func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) { + + } + + func pictureInPictureController(pictureInPictureController:AVPictureInPictureController!, failedToStartPictureInPictureWithError error:NSError!) { + + } + + func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) { + + } + + func pictureInPictureController(_ pictureInPictureController:AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler:@escaping (Bool) -> Void) { + + } + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { + + } + + // MARK: - JWPlayer State Delegate + + func jwplayerContentIsBuffering(_ player:JWPlayer) { + self.onBuffer?([:]) + } + + func jwplayer(_ player:JWPlayer, isBufferingWithReason reason:JWBufferReason) { + self.onBuffer?([:]) + } + + func jwplayer(_ player:JWPlayer, updatedBuffer percent:Double, position time:JWTimeData) { + self.onUpdateBuffer?(["percent": percent, "position": time]) + } + + func jwplayer(_ player:JWPlayer, didFinishLoadingWithTime loadTime:TimeInterval) { + self.onLoaded?([:]) + } + + func jwplayer(_ player:JWPlayer, isAttemptingToPlay playlistItem:JWPlayerItem, reason:JWPlayReason) { + self.onAttemptPlay?([:]) + } + + func jwplayer(_ player:JWPlayer, isPlayingWithReason reason:JWPlayReason) { + self.onPlay?([:]) + + userPaused = false + wasInterrupted = false + } + + func jwplayer(_ player:JWPlayer, willPlayWithReason reason:JWPlayReason) { + self.onBeforePlay?([:]) + } + + func jwplayer(_ player:JWPlayer, didPauseWithReason reason:JWPauseReason) { + self.onPause?([:]) + + if !wasInterrupted { + userPaused = true + } + } + + func jwplayer(_ player:JWPlayer, didBecomeIdleWithReason reason:JWIdleReason) { + self.onIdle?([:]) + } + + func jwplayer(_ player:JWPlayer, isVisible:Bool) { + self.onVisible?(["visible": isVisible]) + } + + func jwplayerContentWillComplete(_ player:JWPlayer) { + self.onBeforeComplete?([:]) + } + + func jwplayerContentDidComplete(_ player:JWPlayer) { + self.onComplete?([:]) + } + + func jwplayer(_ player:JWPlayer, didLoadPlaylistItem item:JWPlayerItem, at index:UInt) { +// var sourceDict: [String: Any] = [:] +// var file: String? +// +// for source in item.videoSources { +// sourceDict["file"] = source.file?.absoluteString +// sourceDict["label"] = source.label +// sourceDict["default"] = source.defaultVideo +// +// if source.defaultVideo { +// file = source.file?.absoluteString +// } +// } + +// var schedDict: [String: Any] = [:] +// +// if let schedules = item.adSchedule { +// for sched in schedules { +// schedDict["offset"] = sched.offset +// schedDict["tags"] = sched.tags +// schedDict["type"] = sched.type +// } +// } + +// var trackDict: [String: Any] = [:] + +// if let tracks = item.mediaTracks { +// for track in tracks { +// trackDict["file"] = track.file?.absoluteString +// trackDict["label"] = track.label +// trackDict["default"] = track.defaultTrack +// } +// } + +// let itemDict: [String: Any] = [ +// "file": file ?? "", +// "mediaId": item.mediaId as Any, +// "title": item.title as Any, +// "description": item.description, +// "image": item.posterImage?.absoluteString ?? "", +// "startTime": item.startTime, +// "adVmap": item.vmapURL?.absoluteString ?? "", +// "recommendations": item.recommendations?.absoluteString ?? "", +// "sources": sourceDict, +// "adSchedule": schedDict, +// "tracks": trackDict +// ] + + do { + let data:Data! = try JSONSerialization.data(withJSONObject: item.toJSONObject(), options:.prettyPrinted) + self.onPlaylistItem?(["playlistItem": String(data:data, encoding:String.Encoding.utf8) as Any, "index": index]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + +// item.addObserver(self, forKeyPath:"playbackLikelyToKeepUp", options:.new, context:nil) + } + + func jwplayer(_ player:JWPlayer, didLoadPlaylist playlist:[JWPlayerItem]) { + let playlistArray:NSMutableArray! = NSMutableArray() + + for item:JWPlayerItem? in playlist { +// var file:String! +// +// var sourceDict: [String: Any] = [:] +// +// for source in item?.videoSources ?? [] { +// sourceDict["file"] = source.file?.absoluteString +// sourceDict["label"] = source.label +// sourceDict["default"] = source.defaultVideo +// +// if source.defaultVideo { +// file = source.file?.absoluteString ?? "" +// } +// } +// +// var schedDict: [String: Any] = [:] +// if let adSchedule = item?.adSchedule { +// for sched in adSchedule { +// schedDict["offset"] = sched.offset +// schedDict["tags"] = sched.tags +// schedDict["type"] = sched.type +// } +// } +// +// var trackDict: [String: Any] = [:] +// +// if let mediaTracks = item?.mediaTracks { +// for track in mediaTracks { +// trackDict["file"] = track.file?.absoluteString +// trackDict["label"] = track.label +// trackDict["default"] = track.defaultTrack +// } +// } +// +// let itemDict: [String: Any] = [ +// "file": file ?? "", +// "mediaId": item?.mediaId ?? "", +// "title": item?.title ?? "", +// "description": item?.description ?? "", +// "image": item?.posterImage?.absoluteString ?? "", +// "startTime": item?.startTime ?? 0, +// "adVmap": item?.vmapURL?.absoluteString ?? "", +// "recommendations": item?.recommendations?.absoluteString ?? "", +// "sources": sourceDict as Any, +// "adSchedule": trackDict, +// "tracks": schedDict +// ] + + playlistArray.add(item?.toJSONObject() as Any) + } + + do { + let data:Data! = try JSONSerialization.data(withJSONObject: playlistArray as Any, options:.prettyPrinted) + + self.onPlaylist?(["playlist": String(data:data as Data, encoding:String.Encoding.utf8) as Any]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + func jwplayerPlaylistHasCompleted(_ player:JWPlayer) { + self.onPlaylistComplete?([:]) + } + + func jwplayer(_ player:JWPlayer, usesMediaType type:JWMediaType) { + + } + + func jwplayer(_ player: JWPlayer, playbackRateChangedTo rate: Double, at time: TimeInterval) { + self.onRateChanged?(["rate": rate, "at": time]) + } + + func jwplayerHasSeeked(_ player:JWPlayer) { + self.onSeeked?([:]) + } + + func jwplayer(_ player: JWPlayer, seekedFrom oldPosition: TimeInterval, to newPosition: TimeInterval) { + self.onSeek?(["from": oldPosition, "to": newPosition]) + } + + func jwplayer(_ player:JWPlayer, updatedCues cues:[JWCue]) { + + } + + // MARK: - JWPlayer Ad Delegate + + func jwplayer(_ player:JWPlayer, adEvent event:JWAdEvent) { + self.onAdEvent?(["client": event.client, "type": event.type]) + } + +// pragma Mark - Casting methods + + func setUpCastController() { + if (playerView != nil) && playerView.player as! Bool && (castController == nil) { + castController = JWCastController(player:playerView.player) + castController.delegate = self + } + + self.scanForDevices() + } + + func scanForDevices() { + if castController != nil { + castController.startDiscovery() + } + } + + func stopScanForDevices() { + if castController != nil { + castController.stopDiscovery() + } + } + + func presentCastDialog() { + GCKCastContext.sharedInstance().presentCastDialog() + } + + func startDiscovery() { + GCKCastContext.sharedInstance().discoveryManager.startDiscovery() + } + + func stopDiscovery() { + GCKCastContext.sharedInstance().discoveryManager.stopDiscovery() + } + + func discoveryActive() -> Bool { + return GCKCastContext.sharedInstance().discoveryManager.discoveryActive + } + + func hasDiscoveredDevices() -> Bool { + return GCKCastContext.sharedInstance().discoveryManager.hasDiscoveredDevices + } + + func discoveryState() -> GCKDiscoveryState { + return GCKCastContext.sharedInstance().discoveryManager.discoveryState + } + + func setPassiveScan(passive:Bool) { + GCKCastContext.sharedInstance().discoveryManager.passiveScan = passive + } + + func castState() -> GCKCastState { + return GCKCastContext.sharedInstance().castState + } + + func deviceCount() -> UInt { + return GCKCastContext.sharedInstance().discoveryManager.deviceCount + } + + func getAvailableDevices() -> [JWCastingDevice]! { + return castController.availableDevices + } + + func connectedDevice() -> JWCastingDevice! { + return castController.connectedDevice + } + + func connectToDevice(device:JWCastingDevice!) { + return castController.connectToDevice(device) + } + + func cast() { + return castController.cast() + } + + func stopCasting() { + return castController.stopCasting() + } + + // MARK: - JWPlayer Cast Delegate + + func castController(_ controller: JWCastController, castingBeganWithDevice device: JWCastingDevice) { + self.onCasting?([:]) + } + + func castController(_ controller:JWCastController, castingEndedWithError error: Error?) { + self.onCastingEnded?(["error": error as Any]) + } + + func castController(_ controller:JWCastController, castingFailedWithError error: Error) { + self.onCastingFailed?(["error": error as Any]) + } + + func castController(_ controller:JWCastController, connectedTo device: JWCastingDevice) { + let dict:NSMutableDictionary! = NSMutableDictionary() + + dict.setObject(device.name, forKey:"name" as NSCopying) + dict.setObject(device.identifier, forKey:"identifier" as NSCopying) + + do { + let data:Data! = try JSONSerialization.data(withJSONObject: dict as Any, options:.prettyPrinted) + + self.onConnectedToCastingDevice?(["device": String(data:data as Data, encoding:String.Encoding.utf8) as Any]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + func castController(_ controller:JWCastController, connectionFailedWithError error: Error) { + self.onConnectionFailed?(["error": error as Any]) + } + + func castController(_ controller: JWCastController, connectionRecoveredWithDevice device:JWCastingDevice) { + self.onConnectionRecovered?([:]) + } + + func castController(_ controller: JWCastController, connectionSuspendedWithDevice device: JWCastingDevice) { + self.onConnectionTemporarilySuspended?([:]) + } + + func castController(_ controller: JWCastController, devicesAvailable devices:[JWCastingDevice]) { + self.availableDevices = devices + + var devicesInfo: [[String: Any]] = [] + for device in devices { + var dict: [String: Any] = [:] + + dict["name"] = device.name + dict["identifier"] = device.identifier + + devicesInfo.append(dict) + } + + do { + let data:Data! = try JSONSerialization.data(withJSONObject: devicesInfo as Any, options:.prettyPrinted) + + self.onCastingDevicesAvailable?(["devices": String(data:data as Data, encoding:String.Encoding.utf8) as Any]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + func castController(_ controller: JWCastController, disconnectedWithError error: (Error)?) { + self.onDisconnectedFromCastingDevice?(["error": error as Any]) + } + + // MARK: - JWPlayer AV Delegate + + func jwplayer(_ player:JWPlayer, audioTracksUpdated levels:[JWMediaSelectionOption]) { + self.onAudioTracks?([:]) + } + + func jwplayer(_ player:JWPlayer, audioTrackChanged currentLevel:Int) { + + } + + func jwplayer(_ player: JWPlayer, captionPresented caption: [String], at time: JWTimeData) { + + } + + func jwplayer(_ player:JWPlayer, captionTrackChanged index:Int) { + + } + + func jwplayer(_ player: JWPlayer, visualQualityChanged currentVisualQuality: JWVisualQuality) { + + } + + func jwplayer(_ player:JWPlayer, qualityLevelChanged currentLevel:Int) { + + } + + func jwplayer(_ player:JWPlayer, qualityLevelsUpdated levels:[JWVideoSource]) { + + } + + func jwplayer(_ player:JWPlayer, updatedCaptionList options:[JWMediaSelectionOption]) { + + } + + // MARK: - JWPlayer audio session && interruption handling + + func initAudioSession(category:String?, categoryOptions:[String]?, mode:String?) { + self.setObservers() + + var somethingChanged:Bool = false + + if !(category == audioCategory) || (categoryOptions != nil && !categoryOptions!.elementsEqual(audioCategoryOptions)) { + somethingChanged = true + audioCategory = category + audioCategoryOptions = categoryOptions + self.setCategory(categoryName: category, categoryOptions:categoryOptions) + } + + if !(mode == audioMode) { + somethingChanged = true + audioMode = mode + self.setMode(modeName: mode) + } + + if somethingChanged { + do { + try audioSession.setActive(true) + print("setActive - success") + } catch { + print("setActive - error: @%@", error) + } + } + } + + func deinitAudioSession() { + do { + try audioSession?.setActive(false, options: .notifyOthersOnDeactivation) + print("setUnactive - success") + } catch { + print("setUnactive - error: @%@", error) + } + audioSession = nil + MPNowPlayingInfoCenter.default().nowPlayingInfo = [:] + UIApplication.shared.endReceivingRemoteControlEvents() + } + + func setObservers() { + if audioSession == nil { + audioSession = AVAudioSession.sharedInstance() + + NotificationCenter.default.addObserver(self, + selector:#selector(handleMediaServicesReset), + name:AVAudioSession.mediaServicesWereResetNotification, + object:audioSession) + + NotificationCenter.default.addObserver(self, + selector: #selector(audioSessionInterrupted(_:)), + name: AVAudioSession.interruptionNotification, + object: audioSession) + + NotificationCenter.default.addObserver(self, + selector:#selector(applicationWillResignActive(_:)), + name:UIApplication.willResignActiveNotification, object:nil) + + NotificationCenter.default.addObserver(self, + selector:#selector(applicationDidEnterBackground(_:)), + name:UIApplication.didEnterBackgroundNotification, + object:nil) + + NotificationCenter.default.addObserver(self, + selector:#selector(applicationWillEnterForeground(_:)), + name:UIApplication.willEnterForegroundNotification, object:nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(audioRouteChanged(_:)), + name: AVAudioSession.routeChangeNotification, + object: nil) + } + } + + func setCategory(categoryName:String!, categoryOptions:[String]!) { + if (audioSession == nil) { + audioSession = AVAudioSession.sharedInstance() + } + + var category:AVAudioSession.Category! = nil + if categoryName.isEqual("Ambient") { + category = .ambient + } else if categoryName.isEqual("SoloAmbient") { + category = .soloAmbient + } else if categoryName.isEqual("Playback") { + category = .playback + } else if categoryName.isEqual("Record") { + category = .record + } else if categoryName.isEqual("PlayAndRecord") { + category = .playAndRecord + } else if categoryName.isEqual("MultiRoute") { + category = .multiRoute + } else { + category = .playback + } + + var options: AVAudioSession.CategoryOptions = [] + if categoryOptions.contains("MixWithOthers") { + options.insert(.mixWithOthers) + } + if categoryOptions.contains("DuckOthers") { + options.insert(.duckOthers) + } + if categoryOptions.contains("AllowBluetooth") { + options.insert(.allowBluetooth) + } + if categoryOptions.contains("InterruptSpokenAudioAndMix") { + options.insert(.interruptSpokenAudioAndMixWithOthers) + } + if categoryOptions.contains("AllowBluetoothA2DP") { + options.insert(.allowBluetoothA2DP) + } + if categoryOptions.contains("AllowAirPlay") { + options.insert(.allowAirPlay) + } + if categoryOptions.contains("OverrideMutedMicrophone") { + if #available(iOS 14.5, *) { + options.insert(.overrideMutedMicrophoneInterruption) + } else { + // Handle the case for earlier versions if needed + } + } + + do { + try audioSession.setCategory(category, options: options) + print("setCategory - success") + } catch { + print("setCategory - error: @%@", error) + } + } + + func setMode(modeName:String!) { + if (audioSession == nil) { + audioSession = AVAudioSession.sharedInstance() + } + + var mode:AVAudioSession.Mode! = nil + + if modeName.isEqual("Default") { + mode = .default + } else if modeName.isEqual("VoiceChat") { + mode = .voiceChat + } else if modeName.isEqual("VideoChat") { + mode = .videoChat + } else if modeName.isEqual("GameChat") { + mode = .gameChat + } else if modeName.isEqual("VideoRecording") { + mode = .videoRecording + } else if modeName.isEqual("Measurement") { + mode = .measurement + } else if modeName.isEqual("MoviePlayback") { + mode = .moviePlayback + } else if modeName.isEqual("SpokenAudio") { + mode = .spokenAudio + } else if modeName.isEqual("VoicePrompt") { + if #available(iOS 12.0, *) { + mode = .voicePrompt + } else { + // Fallback on earlier versions + } + } + + if (mode != nil) { + do { + try audioSession.setMode(mode) + print("setMode - success") + } catch { + print("setMode - error: @%@", error) + } + } + } + + @objc func audioSessionInterrupted(_ notification: Notification) { + guard let userInfo = notification.userInfo, + let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, + let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { + return + } + + switch type { + case .began: + DispatchQueue.main.async { [self] in + wasInterrupted = true + + if (playerView != nil) { + playerView.player.pause() + } else if (playerViewController != nil) { + playerViewController.player.pause() + } + print("handleInterruption :- Pause") + } + case .ended: + guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return } + let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue) + if options.contains(.shouldResume) && !userPaused && backgroundAudioEnabled { + // Interruption ended. Playback should resume. + DispatchQueue.main.async { [self] in + if (playerView != nil) { + playerView.player.play() + } else if (playerViewController != nil) { + playerViewController.player.play() + } + print("handleInterruption :- Play") + } + } + else { + // Interruption ended. Playback should not resume. + DispatchQueue.main.async { [self] in + wasInterrupted = true + + if (playerView != nil) { + playerView.player.pause() + } else if (playerViewController != nil) { + playerViewController.player.pause() + } + print("handleInterruption :- Pause") + } + } + @unknown default: + break + } + } + + // Service reset + @objc func handleMediaServicesReset() { + // • Handle this notification by fully reconfiguring audio + } + + // Inactive + // Hack for ios 14 stopping audio when going to background + @objc func applicationWillResignActive(_ notification: Notification) { + if !userPaused && backgroundAudioEnabled { + if (playerView != nil) && playerView.player.getState() == .playing { + playerView.player.play() + } else if (playerViewController != nil) && playerViewController.player.getState() == .playing { + playerViewController.player.play() + } + } + } + + // Background + @objc func applicationDidEnterBackground(_ notification: Notification) { + + } + + // Active + @objc func applicationWillEnterForeground(_ notification: Notification) { + if !userPaused && backgroundAudioEnabled { + if (playerView != nil) && playerView.player.getState() == .playing { + playerView.player.play() + } else if (playerViewController != nil) && playerViewController.player.getState() == .playing { + playerViewController.player.play() + } + } + } + + // Route change + @objc func audioRouteChanged(_ notification: Notification) { + guard let userInfo = notification.userInfo else { return } + guard let reason = userInfo[AVAudioSessionRouteChangeReasonKey] as? Int else { return } + + if reason == AVAudioSession.RouteChangeReason.oldDeviceUnavailable.hashValue { + if (playerView != nil) { + playerView.player.pause() + } else if (playerViewController != nil) { + playerViewController.player.pause() + } + } + } + +} diff --git a/ios/RNJWPlayer/RNJWPlayerViewController.h b/ios/RNJWPlayer/RNJWPlayerViewController.h deleted file mode 100644 index ef52666c..00000000 --- a/ios/RNJWPlayer/RNJWPlayerViewController.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// RNJWPlayerViewController.m -// RNJWPlayer -// -// Created by Chaim Paneth on 3/30/22. -// - -#if __has_include("React/RCTViewManager.h") -#import "React/RCTViewManager.h" -#else -#import "RCTViewManager.h" -#endif - -#import -#import -#import -#import "JWPlayerKit/JWPlayerObjCViewController.h" -#import "RNJWPlayerView.h" - -@class RNJWPlayerView; - -@interface RNJWPlayerViewController : JWPlayerObjCViewController - -@property(nonatomic, weak)RNJWPlayerView *parentView; - --(void)setDelegates; - --(void)removeDelegates; - -@end diff --git a/ios/RNJWPlayer/RNJWPlayerViewController.m b/ios/RNJWPlayer/RNJWPlayerViewController.m deleted file mode 100644 index 83011cb1..00000000 --- a/ios/RNJWPlayer/RNJWPlayerViewController.m +++ /dev/null @@ -1,719 +0,0 @@ -// -// RNJWPlayerViewController.m -// RNJWPlayer -// -// Created by Chaim Paneth on 3/30/22. -// - -#import "RNJWPlayerViewController.h" - -@implementation RNJWPlayerViewController - --(void)setDelegates -{ - self.delegate = self; - self.playerView.delegate = self; - self.player.delegate = self; - self.player.playbackStateDelegate = self; - self.player.adDelegate = self; - self.player.avDelegate = self; - self.player.contentKeyDataSource = self; -} - --(void)removeDelegates -{ - self.delegate = nil; - self.playerView.delegate = nil; - self.player.delegate = nil; - self.player.playbackStateDelegate = nil; - self.player.adDelegate = nil; - self.player.avDelegate = nil; - self.player.contentKeyDataSource = nil; -} - -#pragma mark - JWPlayer Delegate - -- (void)jwplayerIsReady:(id)player -{ - [super jwplayerIsReady:player]; - - _parentView.settingConfig = NO; - - if (_parentView.onPlayerReady) { - _parentView.onPlayerReady(@{}); - } - - if (_parentView.pendingConfig && _parentView.currentConfig) { - [_parentView setConfig:_parentView.currentConfig]; - } -} - -- (void)jwplayer:(id)player failedWithError:(NSUInteger)code message:(NSString *)message -{ - [super jwplayer:player failedWithError:code message:message]; - - if (_parentView.onPlayerError) { - _parentView.onPlayerError(@{@"code": [NSNumber numberWithInteger:code], @"error": message}); - } -} - -- (void)jwplayer:(id)player failedWithSetupError:(NSUInteger)code message:(NSString *)message -{ - [super jwplayer:player failedWithSetupError:code message:message]; - - if (_parentView.onSetupPlayerError) { - _parentView.onSetupPlayerError(@{@"code": [NSNumber numberWithInteger:code], @"error": message}); - } -} - -- (void)jwplayer:(id)player encounteredWarning:(NSUInteger)code message:(NSString *)message -{ - [super jwplayer:player encounteredWarning:code message:message]; - - if (_parentView.onPlayerWarning) { - _parentView.onPlayerWarning(@{@"code": [NSNumber numberWithInteger:code], @"warning": message}); - } -} - -- (void)jwplayer:(id _Nonnull)player encounteredAdError:(NSUInteger)code message:(NSString * _Nonnull)message { - [super jwplayer:player encounteredAdError:code message:message]; - - if (_parentView.onPlayerAdError) { - _parentView.onPlayerAdError(@{@"code": [NSNumber numberWithInteger:code], @"error": message}); - } -} - - -- (void)jwplayer:(id _Nonnull)player encounteredAdWarning:(NSUInteger)code message:(NSString * _Nonnull)message { - [super jwplayer:player encounteredAdWarning:code message:message]; - - if (_parentView.onPlayerAdWarning) { - _parentView.onPlayerAdWarning(@{@"code": [NSNumber numberWithInteger:code], @"warning": message}); - } -} - - -#pragma mark - JWPlayer View Delegate - -- (void)playerView:(JWPlayerView *)view sizeChangedFrom:(CGSize)oldSize to:(CGSize)newSize -{ - [super playerView:view sizeChangedFrom:oldSize to:newSize]; - - if (_parentView.onPlayerSizeChange) { - NSMutableDictionary* oldSizeDict = [[NSMutableDictionary alloc] init]; - [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.width] forKey:@"width"]; - [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.height] forKey:@"height"]; - - NSMutableDictionary* newSizeDict = [[NSMutableDictionary alloc] init]; - [newSizeDict setObject:[NSNumber numberWithFloat: newSize.width] forKey:@"width"]; - [newSizeDict setObject:[NSNumber numberWithFloat: newSize.height] forKey:@"height"]; - - NSMutableDictionary* sizesDict = [[NSMutableDictionary alloc] init]; - [sizesDict setObject:oldSizeDict forKey:@"oldSize"]; - [sizesDict setObject:newSizeDict forKey:@"newSize"]; - - NSError* error = nil; - NSData* data = [NSJSONSerialization dataWithJSONObject:sizesDict options:NSJSONWritingPrettyPrinted error: &error]; - _parentView.onPlayerSizeChange(@{@"sizes": data}); - } -} - -#pragma mark - JWPlayer View Controller Delegate - -- (void)playerViewController:(JWPlayerViewController *)controller sizeChangedFrom:(CGSize)oldSize to:(CGSize)newSize -{ - if (_parentView.onPlayerSizeChange) { - NSMutableDictionary* oldSizeDict = [[NSMutableDictionary alloc] init]; - [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.width] forKey:@"width"]; - [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.height] forKey:@"height"]; - - NSMutableDictionary* newSizeDict = [[NSMutableDictionary alloc] init]; - [newSizeDict setObject:[NSNumber numberWithFloat: newSize.width] forKey:@"width"]; - [newSizeDict setObject:[NSNumber numberWithFloat: newSize.height] forKey:@"height"]; - - NSMutableDictionary* sizesDict = [[NSMutableDictionary alloc] init]; - [sizesDict setObject:oldSizeDict forKey:@"oldSize"]; - [sizesDict setObject:newSizeDict forKey:@"newSize"]; - - NSError* error = nil; - NSData* data = [NSJSONSerialization dataWithJSONObject:sizesDict options:NSJSONWritingPrettyPrinted error: &error]; - _parentView.onPlayerSizeChange(@{@"sizes": data}); - } -} - -- (void)playerViewController:(JWPlayerViewController *)controller screenTappedAt:(CGPoint)position -{ - if (_parentView.onScreenTapped) { - _parentView.onScreenTapped(@{@"x": @(position.x), @"y": @(position.y)}); - } -} - -- (void)playerViewController:(JWPlayerViewController *)controller controlBarVisibilityChanged:(BOOL)isVisible frame:(CGRect)frame -{ - if (_parentView.onControlBarVisible) { - _parentView.onControlBarVisible(@{@"visible": @(isVisible)}); - } -} - -- (JWFullScreenViewController * _Nullable)playerViewControllerWillGoFullScreen:(JWPlayerViewController * _Nonnull)controller { - if (_parentView.onFullScreenRequested) { - _parentView.onFullScreenRequested(@{}); - } - return nil; -} - -- (void)playerViewControllerDidGoFullScreen:(JWPlayerViewController *)controller -{ - if (_parentView.onFullScreen) { - _parentView.onFullScreen(@{}); - } -} - -- (void)playerViewControllerWillDismissFullScreen:(JWPlayerViewController *)controller -{ - if (_parentView.onFullScreenExitRequested) { - _parentView.onFullScreenExitRequested(@{}); - } -} - -- (void)playerViewControllerDidDismissFullScreen:(JWPlayerViewController *)controller -{ - if (_parentView.onFullScreenExit) { - _parentView.onFullScreenExit(@{}); - } -} - -- (void)playerViewController:(JWPlayerViewController *)controller relatedMenuClosedWithMethod:(enum JWRelatedInteraction)method -{ - -} - -- (void)playerViewController:(JWPlayerViewController *)controller relatedMenuOpenedWithItems:(NSArray *)items withMethod:(enum JWRelatedInteraction)method -{ - -} - -- (void)playerViewController:(JWPlayerViewController *)controller relatedItemBeganPlaying:(JWPlayerItem *)item atIndex:(NSInteger)index withMethod:(enum JWRelatedMethod)method -{ - -} - -#pragma mark Time events - --(void)onAdTimeEvent:(JWTimeData * _Nonnull)time { - [super onAdTimeEvent:time]; - if (_parentView.onAdTime) { - _parentView.onAdTime(@{@"position": @(time.position), @"duration": @(time.duration)}); - } -} - -- (void)onMediaTimeEvent:(JWTimeData * _Nonnull)time { - [super onMediaTimeEvent:time]; - if (_parentView.onTime) { - _parentView.onTime(@{@"position": @(time.position), @"duration": @(time.duration)}); - } -} - -#pragma mark - DRM Delegate - -- (void)contentIdentifierForURL:(NSURL * _Nonnull)url completionHandler:(void (^ _Nonnull)(NSData * _Nullable))handler { - NSData *data = [url.host dataUsingEncoding:NSUTF8StringEncoding]; - handler(data); -} - -- (void)appIdentifierForURL:(NSURL * _Nonnull)url completionHandler:(void (^ _Nonnull)(NSData * _Nullable))handler { - if (_parentView.fairplayCertUrl == nil) { - return; - } - NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:_parentView.fairplayCertUrl]]; - NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (error) { - NSLog(@"DRM cert request error - %@", error.localizedDescription); - } - handler(data); - }]; - - [task resume]; -} - -- (void)contentKeyWithSPCData:(NSData * _Nonnull)spcData completionHandler:(void (^ _Nonnull)(NSData * _Nullable, NSDate * _Nullable, NSString * _Nullable))handler { - if (_parentView.processSpcUrl == nil) { - return; - } - - NSMutableURLRequest *ckcRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:_parentView.processSpcUrl]]; - [ckcRequest setHTTPMethod:@"POST"]; - [ckcRequest setHTTPBody:spcData]; - [ckcRequest addValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"]; - - [[[NSURLSession sharedSession] dataTaskWithRequest:ckcRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - if (error != nil || (httpResponse != nil && httpResponse.statusCode != 200)) { - NSLog(@"DRM ckc request error - %@", error); - handler(nil, nil, nil); - return; - } - handler(data, nil, @"application/octet-stream"); - }] resume]; -} - - -#pragma mark - AV Picture In Picture Delegate - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { - [_parentView.playerViewController.player play]; - } -} - -- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController -{ - -} - -- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController -{ - -} - -- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController -{ - -} - -- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error -{ - -} - -- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController -{ - -} - -- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler -{ - -} - -#pragma mark - JWPlayer State Delegate - -- (void)jwplayerContentIsBuffering:(id)player -{ - [super jwplayerContentIsBuffering:player]; - - if (_parentView.onBuffer) { - _parentView.onBuffer(@{}); - } -} - -- (void)jwplayer:(id)player isBufferingWithReason:(enum JWBufferReason)reason -{ - [super jwplayer:player isBufferingWithReason:reason]; - - if (_parentView.onBuffer) { - _parentView.onBuffer(@{}); - } -} - -- (void)jwplayer:(id)player updatedBuffer:(double)percent position:(JWTimeData *)time -{ - [super jwplayer:player updatedBuffer:percent position:time]; - - if (_parentView.onUpdateBuffer) { - _parentView.onUpdateBuffer(@{@"percent": @(percent), @"position": time}); - } -} - -- (void)jwplayer:(id)player didFinishLoadingWithTime:(NSTimeInterval)loadTime -{ - [super jwplayer:player didFinishLoadingWithTime:loadTime]; - - if (_parentView.onLoaded) { - _parentView.onLoaded(@{}); - } -} - -- (void)jwplayer:(id)player isAttemptingToPlay:(JWPlayerItem *)playlistItem reason:(enum JWPlayReason)reason -{ - [super jwplayer:player isAttemptingToPlay:playlistItem reason:reason]; - - if (_parentView.onAttemptPlay) { - _parentView.onAttemptPlay(@{}); - } -} - -- (void)jwplayer:(id)player isPlayingWithReason:(enum JWPlayReason)reason -{ - [super jwplayer:player isPlayingWithReason:reason]; - - if (_parentView.onPlay) { - _parentView.onPlay(@{}); - } - - _parentView.userPaused = NO; - _parentView.wasInterrupted = NO; -} - -- (void)jwplayer:(id)player willPlayWithReason:(enum JWPlayReason)reason -{ - [super jwplayer:player willPlayWithReason:reason]; - - if (_parentView.onBeforePlay) { - _parentView.onBeforePlay(@{}); - } -} - -- (void)jwplayer:(id)player didPauseWithReason:(enum JWPauseReason)reason -{ - [super jwplayer:player didPauseWithReason:reason]; - - if (_parentView.onPause) { - _parentView.onPause(@{}); - } - - if (!_parentView.wasInterrupted) { - _parentView.userPaused = YES; - } -} - -- (void)jwplayer:(id)player didBecomeIdleWithReason:(enum JWIdleReason)reason -{ - [super jwplayer:player didBecomeIdleWithReason:reason]; - - if (_parentView.onIdle) { - _parentView.onIdle(@{}); - } -} - -- (void)jwplayer:(id)player isVisible:(BOOL)isVisible -{ - [super jwplayer:player isVisible:isVisible]; - - if (_parentView.onVisible) { - _parentView.onVisible(@{@"visible": @(isVisible)}); - } -} - -- (void)jwplayerContentWillComplete:(id)player -{ - [super jwplayerContentWillComplete:player]; - - if (_parentView.onBeforeComplete) { - _parentView.onBeforeComplete(@{}); - } -} - -- (void)jwplayerContentDidComplete:(id)player -{ - [super jwplayerContentDidComplete:player]; - - if (_parentView.onComplete) { - _parentView.onComplete(@{}); - } -} - -- (void)jwplayer:(id)player didLoadPlaylistItem:(JWPlayerItem *)item at:(NSUInteger)index -{ - [super jwplayer:player didLoadPlaylistItem:item at:index]; - - if (_parentView.onPlaylistItem) { - NSString *file; - - NSMutableDictionary* sourceDict = [[NSMutableDictionary alloc] init]; - for (JWVideoSource* source in item.videoSources) { - [sourceDict setObject:source.file forKey:@"file"]; - [sourceDict setObject:source.label forKey:@"label"]; - [sourceDict setObject:@(source.defaultVideo) forKey:@"default"]; - - if (source.defaultVideo) { - file = [source.file absoluteString]; - } - } - - NSMutableDictionary* schedDict = [[NSMutableDictionary alloc] init]; - for (JWAdBreak* sched in item.adSchedule) { - [schedDict setObject:sched.offset forKey:@"offset"]; - [schedDict setObject:sched.tags forKey:@"tags"]; - [schedDict setObject:@(sched.type) forKey:@"type"]; - } - - NSMutableDictionary* trackDict = [[NSMutableDictionary alloc] init]; - for (JWMediaTrack* track in item.mediaTracks) { - [trackDict setObject:track.file forKey:@"file"]; - [trackDict setObject:track.label forKey:@"label"]; - [trackDict setObject:@(track.defaultTrack) forKey:@"default"]; - } - - NSDictionary* itemDict = [NSDictionary dictionaryWithObjectsAndKeys: - file, @"file", - item.mediaId, @"mediaId", - item.title, @"title", - item.description, @"description", - item.posterImage.absoluteString, @"image", - @(item.startTime), @"startTime", - item.vmapURL.absoluteString, @"adVmap", - item.recommendations.absoluteString, @"recommendations", - sourceDict, @"sources", - schedDict, @"adSchedule", - trackDict, @"tracks", - nil]; - - NSError *error; - NSData *data = [NSJSONSerialization dataWithJSONObject:itemDict options:NSJSONWritingPrettyPrinted error: &error]; - - _parentView.onPlaylistItem(@{@"playlistItem": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], @"index": [NSNumber numberWithInteger:index]}); - } - - [item addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil]; -} - -- (void)jwplayer:(id)player didLoadPlaylist:(NSArray *)playlist -{ - [super jwplayer:player didLoadPlaylist:playlist]; - - if (_parentView.onPlaylist) { - NSMutableArray* playlistArray = [[NSMutableArray alloc] init]; - - for (JWPlayerItem* item in playlist) { - NSString *file; - - NSMutableDictionary* sourceDict = [[NSMutableDictionary alloc] init]; - for (JWVideoSource* source in item.videoSources) { - [sourceDict setObject:source.file forKey:@"file"]; - [sourceDict setObject:source.label forKey:@"label"]; - [sourceDict setObject:@(source.defaultVideo) forKey:@"default"]; - - if (source.defaultVideo) { - file = [source.file absoluteString]; - } - } - - NSMutableDictionary* schedDict = [[NSMutableDictionary alloc] init]; - for (JWAdBreak* sched in item.adSchedule) { - [schedDict setObject:sched.offset forKey:@"offset"]; - [schedDict setObject:sched.tags forKey:@"tags"]; - [schedDict setObject:@(sched.type) forKey:@"type"]; - } - - NSMutableDictionary* trackDict = [[NSMutableDictionary alloc] init]; - for (JWMediaTrack* track in item.mediaTracks) { - [trackDict setObject:track.file forKey:@"file"]; - [trackDict setObject:track.label forKey:@"label"]; - [trackDict setObject:@(track.defaultTrack) forKey:@"default"]; - } - - NSDictionary* itemDict = [NSDictionary dictionaryWithObjectsAndKeys: - file, @"file", - item.mediaId, @"mediaId", - item.title, @"title", - item.description, @"description", - item.posterImage.absoluteString, @"image", - @(item.startTime), @"startTime", - item.vmapURL.absoluteString, @"adVmap", - item.recommendations.absoluteString, @"recommendations", - sourceDict, @"sources", - schedDict, @"adSchedule", - trackDict, @"tracks", - nil]; - - [playlistArray addObject:itemDict]; - } - - NSError *error; - NSData* data = [NSJSONSerialization dataWithJSONObject:playlistArray options:NSJSONWritingPrettyPrinted error: &error]; - - _parentView.onPlaylist(@{@"playlist": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}); - } -} - -- (void)jwplayerPlaylistHasCompleted:(id)player -{ - [super jwplayerPlaylistHasCompleted:player]; - - if (_parentView.onPlaylistComplete) { - _parentView.onPlaylistComplete(@{}); - } -} - -- (void)jwplayer:(id)player usesMediaType:(enum JWMediaType)type -{ - [super jwplayer:player usesMediaType:type]; -} - -- (void)jwplayer:(id)player seekedFrom:(NSTimeInterval)oldPosition to:(NSTimeInterval)newPosition -{ - [super jwplayer:player seekedFrom:oldPosition to:newPosition]; - - if (_parentView.onSeek) { - _parentView.onSeek(@{@"from": @(oldPosition), @"to": @(newPosition)}); - } -} - -- (void)jwplayerHasSeeked:(id)player -{ - [super jwplayerHasSeeked:player]; - - if (_parentView.onSeeked) { - _parentView.onSeeked(@{}); - } -} - -- (void)jwplayer:(id)player playbackRateChangedTo:(double)rate at:(NSTimeInterval)time -{ - [super jwplayer:player playbackRateChangedTo:rate at:time]; - - if (_parentView.onRateChanged) { - _parentView.onRateChanged(@{@"rate": [NSNumber numberWithDouble:rate], @"at": [NSNumber numberWithDouble:time]}); - } -} - -- (void)jwplayer:(id)player updatedCues:(NSArray * _Nonnull)cues -{ - [super jwplayer:player updatedCues:cues]; -} - -#pragma mark - JWPlayer Ad Delegate - -- (void)jwplayer:(id)player adEvent:(JWAdEvent *)event { - [super jwplayer:player adEvent:event]; - - if (_parentView.onAdEvent) { - _parentView.onAdEvent(@{@"client": @(event.client), @"type": @(event.type)}); - } -} - -#pragma mark - JWPlayer Cast Delegate - -- (void)castController:(JWCastController * _Nonnull)controller castingBeganWithDevice:(JWCastingDevice * _Nonnull)device { - [super castController:controller castingBeganWithDevice:device]; - - if (_parentView.onCasting) { - _parentView.onCasting(@{}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller castingEndedWithError:(NSError * _Nullable)error { - [super castController:controller castingEndedWithError:error]; - - if (_parentView.onCastingEnded) { - _parentView.onCastingEnded(@{@"error": error}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller castingFailedWithError:(NSError * _Nonnull)error { - [super castController:controller castingFailedWithError:error]; - - if (_parentView.onCastingFailed) { - _parentView.onCastingFailed(@{@"error": error}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller connectedTo:(JWCastingDevice * _Nonnull)device { - [super castController:controller connectedTo:device]; - - if (_parentView.onConnectedToCastingDevice) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - - [dict setObject:device.name forKey:@"name"]; - [dict setObject:device.identifier forKey:@"identifier"]; - - NSError *error; - NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error: &error]; - - _parentView.onConnectedToCastingDevice(@{@"device": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller connectionFailedWithError:(NSError * _Nonnull)error { - [super castController:controller connectionFailedWithError:error]; - - if (_parentView.onConnectionFailed) { - _parentView.onConnectionFailed(@{@"error": error}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller connectionRecoveredWithDevice:(JWCastingDevice * _Nonnull)device { - [super castController:controller connectionRecoveredWithDevice:device]; - - if (_parentView.onConnectionRecovered) { - _parentView.onConnectionRecovered(@{}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller connectionSuspendedWithDevice:(JWCastingDevice * _Nonnull)device { - [super castController:controller connectionSuspendedWithDevice:device]; - - if (_parentView.onConnectionTemporarilySuspended) { - _parentView.onConnectionTemporarilySuspended(@{}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller devicesAvailable:(NSArray * _Nonnull)devices { - [super castController:controller devicesAvailable:devices]; - - _parentView.availableDevices = devices; - - if (_parentView.onCastingDevicesAvailable) { - NSMutableArray *devicesInfo = [[NSMutableArray alloc] init]; - - for (JWCastingDevice *device in devices) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - - [dict setObject:device.name forKey:@"name"]; - [dict setObject:device.identifier forKey:@"identifier"]; - - [devicesInfo addObject:dict]; - } - - NSError *error; - NSData *data = [NSJSONSerialization dataWithJSONObject:devicesInfo options:NSJSONWritingPrettyPrinted error: &error]; - - _parentView.onCastingDevicesAvailable(@{@"devices": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}); - } -} - -- (void)castController:(JWCastController * _Nonnull)controller disconnectedWithError:(NSError * _Nullable)error { - [super castController:controller disconnectedWithError:error]; - - if (_parentView.onDisconnectedFromCastingDevice) { - _parentView.onDisconnectedFromCastingDevice(@{@"error": error}); - } -} - -#pragma mark - JWPlayer AV Delegate - -- (void)jwplayer:(id _Nonnull)player audioTracksUpdated:(NSArray * _Nonnull)levels { - [super jwplayer:player audioTracksUpdated:levels]; - - if (_parentView.onAudioTracks) { - _parentView.onAudioTracks(@{}); - } -} - -- (void)jwplayer:(id _Nonnull)player audioTrackChanged:(NSInteger)currentLevel { - [super jwplayer:player audioTrackChanged:currentLevel]; -} - -- (void)jwplayer:(id _Nonnull)player captionPresented:(NSArray * _Nonnull)caption at:(JWTimeData * _Nonnull)time { - [super jwplayer:player captionPresented:caption at:time]; -} - -- (void)jwplayer:(id _Nonnull)player captionTrackChanged:(NSInteger)index { - [super jwplayer:player captionTrackChanged:index]; -} - -- (void)jwplayer:(id _Nonnull)player qualityLevelChanged:(NSInteger)currentLevel { - [super jwplayer:player qualityLevelChanged:currentLevel]; -} - -- (void)jwplayer:(id _Nonnull)player qualityLevelsUpdated:(NSArray * _Nonnull)levels { - [super jwplayer:player qualityLevelsUpdated:levels]; -} - -- (void)jwplayer:(id _Nonnull)player updatedCaptionList:(NSArray * _Nonnull)options { - [super jwplayer:player updatedCaptionList:options]; -} - -@end diff --git a/ios/RNJWPlayer/RNJWPlayerViewController.swift b/ios/RNJWPlayer/RNJWPlayerViewController.swift new file mode 100644 index 00000000..2a621e4c --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerViewController.swift @@ -0,0 +1,592 @@ +// +// RNJWPlayerViewController.m +// RNJWPlayer +// +// Created by Chaim Paneth on 3/30/22. +// + +import UIKit +import AVFoundation +import AVKit +import MediaPlayer +import React +import GoogleCast +import JWPlayerKit + +class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerDelegate, JWDRMContentKeyDataSource { + + var parentView:RNJWPlayerView! + + func setDelegates() { + self.delegate = self +// self.playerView.delegate = self +// self.player.delegate = self +// self.player.playbackStateDelegate = self +// self.player.adDelegate = self +// self.player.avDelegate = self + self.player.contentKeyDataSource = self + } + + func removeDelegates() { + self.delegate = nil +// self.playerView.delegate = nil +// self.player.delegate = nil +// self.player.playbackStateDelegate = nil +// self.player.adDelegate = nil +// self.player.avDelegate = nil + self.player.contentKeyDataSource = nil + } + + // MARK: - JWPlayer Delegate + + override func jwplayerIsReady(_ player:JWPlayer) { + super.jwplayerIsReady(player) + + parentView.settingConfig = false + parentView.onPlayerReady?([:]) + + if parentView.pendingConfig && (parentView.currentConfig != nil) { + parentView.setConfig(parentView.currentConfig) + } + } + + override func jwplayer(_ player:JWPlayer, failedWithError code:UInt, message:String) { + super.jwplayer(player, failedWithError:code, message:message) + parentView.onPlayerError?(["error": message]) + } + + override func jwplayer(_ player:JWPlayer, failedWithSetupError code:UInt, message:String) { + super.jwplayer(player, failedWithSetupError:code, message:message) + parentView.onSetupPlayerError?(["error": message]) + } + + override func jwplayer(_ player:JWPlayer, encounteredWarning code:UInt, message:String) { + super.jwplayer(player, encounteredWarning:code, message:message) + parentView.onPlayerWarning?(["warning": message]) + } + + override func jwplayer(_ player:JWPlayer, encounteredAdError code:UInt, message:String) { + super.jwplayer(player, encounteredAdError:code, message:message) + parentView.onPlayerAdError?(["error": message]) + } + + + override func jwplayer(_ player:JWPlayer, encounteredAdWarning code:UInt, message:String) { + super.jwplayer(player, encounteredAdWarning:code, message:message) + parentView.onPlayerAdWarning?(["warning": message]) + } + + + // MARK: - JWPlayer View Delegate + + override func playerView(_ view:JWPlayerView, sizeChangedFrom oldSize:CGSize, to newSize:CGSize) { + let oldSizeDict: [String: Any] = [ + "width": oldSize.width, + "height": oldSize.height + ] + + let newSizeDict: [String: Any] = [ + "width": newSize.width, + "height": newSize.height + ] + + let sizesDict: [String: Any] = [ + "oldSize": oldSizeDict, + "newSize": newSizeDict + ] + + do { + let data = try JSONSerialization.data(withJSONObject: sizesDict, options: .prettyPrinted) + parentView.onPlayerSizeChange?(["sizes": data]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + // MARK: - JWPlayer View Controller Delegate + + func playerViewController(_ controller:JWPlayerViewController, sizeChangedFrom oldSize:CGSize, to newSize:CGSize) { + let oldSizeDict: [String: Any] = [ + "width": oldSize.width, + "height": oldSize.height + ] + + let newSizeDict: [String: Any] = [ + "width": newSize.width, + "height": newSize.height + ] + + let sizesDict: [String: Any] = [ + "oldSize": oldSizeDict, + "newSize": newSizeDict + ] + + do { + let data = try JSONSerialization.data(withJSONObject: sizesDict, options: .prettyPrinted) + parentView.onPlayerSizeChange?(["sizes": data]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + func playerViewController(_ controller:JWPlayerViewController, screenTappedAt position:CGPoint) { + parentView.onScreenTapped?(["x": position.x, "y": position.y]) + } + + func playerViewController(_ controller:JWPlayerViewController, controlBarVisibilityChanged isVisible:Bool, frame:CGRect) { + parentView.onControlBarVisible?(["visible": isVisible]) + } + + func playerViewControllerWillGoFullScreen(_ controller:JWPlayerViewController) -> JWFullScreenViewController? { + parentView.onFullScreenRequested?([:]) + return nil + } + + func playerViewControllerDidGoFullScreen(_ controller:JWPlayerViewController) { + parentView.onFullScreen?([:]) + } + + func playerViewControllerWillDismissFullScreen(_ controller:JWPlayerViewController) { + parentView.onFullScreenExitRequested?([:]) + } + + func playerViewControllerDidDismissFullScreen(_ controller:JWPlayerViewController) { + parentView.onFullScreenExit?([:]) + } + + func playerViewController(_ controller:JWPlayerViewController, relatedMenuClosedWithMethod method:JWRelatedInteraction) { + + } + + func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedMenuOpenedWithItems items: [JWPlayerKit.JWPlayerItem], withMethod method: JWPlayerKit.JWRelatedInteraction) { + + } + + func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedItemBeganPlaying item: JWPlayerKit.JWPlayerItem, atIndex index: Int, withMethod method: JWPlayerKit.JWRelatedMethod) { + + } + + // MARK: Time events + + func onAdTimeEvent(time:JWTimeData) { + super.onAdTimeEvent(time) + parentView.onAdTime?(["position": time.position, "duration": time.duration]) + } + + func onMediaTimeEvent(time:JWTimeData) { + super.onMediaTimeEvent(time) + parentView.onTime?(["position": time.position, "duration": time.duration]) + } + + // MARK: - DRM Delegate + + func contentIdentifierForURL(_ url: URL, completionHandler handler: @escaping (Data?) -> Void) { + let data:Data! = url.host?.data(using: String.Encoding.utf8) + handler(data) + } + + func appIdentifierForURL(_ url: URL, completionHandler handler: @escaping (Data?) -> Void) { + guard let fairplayCertUrlString = parentView.fairplayCertUrl, let fairplayCertUrl = URL(string: fairplayCertUrlString) else { + return + } + + let request = URLRequest(url: fairplayCertUrl) + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + print("DRM cert request error - \(error.localizedDescription)") + } + handler(data) + } + task.resume() + } + + func contentKeyWithSPCData(_ spcData: Data, completionHandler handler: @escaping (Data?, Date?, String?) -> Void) { + if parentView.processSpcUrl == nil { + return + } + + let ckcRequest = NSMutableURLRequest(url: NSURL(string: parentView.processSpcUrl)! as URL) + ckcRequest.httpMethod = "POST" + ckcRequest.httpBody = spcData + ckcRequest.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type") + + URLSession.shared.dataTask(with: ckcRequest as URLRequest) { (data, response, error) in + if let httpResponse = response as? HTTPURLResponse, (error != nil || httpResponse.statusCode != 200) { + NSLog("DRM ckc request error - %@", error.debugDescription) + handler(nil, nil, nil) + return + } + + handler(data, nil, "application/octet-stream") + }.resume() + } + + // MARK: - AV Picture In Picture Delegate + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) +// if (keyPath == "playbackLikelyToKeepUp") { +// parentView.playerViewController.player.play() +// } + } + + override func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) { + super.pictureInPictureControllerDidStopPictureInPicture(pictureInPictureController) + } + + override func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) { + super.pictureInPictureControllerDidStartPictureInPicture(pictureInPictureController) + } + + override func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) { + super.pictureInPictureControllerWillStopPictureInPicture(pictureInPictureController) + } + + override func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { + super.pictureInPictureController(pictureInPictureController, failedToStartPictureInPictureWithError: error) + } + + override func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) { + super.pictureInPictureControllerWillStartPictureInPicture(pictureInPictureController) + } + + override func pictureInPictureController(_ pictureInPictureController:AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler:@escaping (Bool) -> Void) { + super.pictureInPictureController(pictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: completionHandler) + } + + // MARK: - JWPlayer State Delegate + + override func jwplayerContentIsBuffering(_ player:JWPlayer) { + super.jwplayerContentIsBuffering(player) + parentView.onBuffer?([:]) + } + + override func jwplayer(_ player:JWPlayer, isBufferingWithReason reason:JWBufferReason) { + super.jwplayer(player, isBufferingWithReason:reason) + parentView.onBuffer?([:]) + } + + override func jwplayer(_ player:JWPlayer, updatedBuffer percent:Double, position time:JWTimeData) { + super.jwplayer(player, updatedBuffer:percent, position:time) + parentView.onUpdateBuffer?(["percent": percent, "position": time as Any]) + } + + override func jwplayer(_ player:JWPlayer, didFinishLoadingWithTime loadTime:TimeInterval) { + super.jwplayer(player, didFinishLoadingWithTime:loadTime) + parentView.onLoaded?([:]) + } + + override func jwplayer(_ player:JWPlayer, isAttemptingToPlay playlistItem:JWPlayerItem, reason:JWPlayReason) { + super.jwplayer(player, isAttemptingToPlay:playlistItem, reason:reason) + parentView.onAttemptPlay?([:]) + } + + override func jwplayer(_ player:JWPlayer, isPlayingWithReason reason:JWPlayReason) { + super.jwplayer(player, isPlayingWithReason:reason) + + parentView.onPlay?([:]) + + parentView.userPaused = false + parentView.wasInterrupted = false + } + + override func jwplayer(_ player:JWPlayer, willPlayWithReason reason:JWPlayReason) { + super.jwplayer(player, willPlayWithReason:reason) + parentView.onBeforePlay?([:]) + } + + override func jwplayer(_ player:JWPlayer, didPauseWithReason reason:JWPauseReason) { + super.jwplayer(player, didPauseWithReason:reason) + parentView.onPause?([:]) + + if !parentView.wasInterrupted { + parentView.userPaused = true + } + } + + override func jwplayer(_ player:JWPlayer, didBecomeIdleWithReason reason:JWIdleReason) { + super.jwplayer(player, didBecomeIdleWithReason:reason) + parentView.onIdle?([:]) + } + + override func jwplayer(_ player:JWPlayer, isVisible:Bool) { + super.jwplayer(player, isVisible:isVisible) + parentView.onVisible?(["visible": isVisible]) + } + + override func jwplayerContentWillComplete(_ player:JWPlayer) { + super.jwplayerContentWillComplete(player) + parentView.onBeforeComplete?([:]) + } + + override func jwplayerContentDidComplete(_ player:JWPlayer) { + super.jwplayerContentDidComplete(player) + parentView.onComplete?([:]) + } + + override func jwplayer(_ player:JWPlayer, didLoadPlaylistItem item:JWPlayerItem, at index:UInt) { + super.jwplayer(player, didLoadPlaylistItem: item, at: index) + +// var sourceDict: [String: Any] = [:] +// var file: String? +// +// for source in item.videoSources { +// sourceDict["file"] = source.file?.absoluteString +// sourceDict["label"] = source.label +// sourceDict["default"] = source.defaultVideo +// +// if source.defaultVideo { +// file = source.file?.absoluteString +// } +// } +// +// var schedDict: [String: Any] = [:] +// +// if let schedules = item.adSchedule { +// for sched in schedules { +// schedDict["offset"] = sched.offset +// schedDict["tags"] = sched.tags +// schedDict["type"] = sched.type +// } +// } +// +// var trackDict: [String: Any] = [:] +// +// if let tracks = item.mediaTracks { +// for track in tracks { +// trackDict["file"] = track.file?.absoluteString +// trackDict["label"] = track.label +// trackDict["default"] = track.defaultTrack +// } +// } +// +// let itemDict: [String: Any] = [ +// "file": file ?? "", +// "mediaId": item.mediaId as Any, +// "title": item.title as Any, +// "description": item.description, +// "image": item.posterImage?.absoluteString ?? "", +// "startTime": item.startTime, +// "adVmap": item.vmapURL?.absoluteString ?? "", +// "recommendations": item.recommendations?.absoluteString ?? "", +// "sources": sourceDict, +// "adSchedule": schedDict, +// "tracks": trackDict +// ] + + do { + let data:Data! = try JSONSerialization.data(withJSONObject: item.toJSONObject(), options:.prettyPrinted) + + parentView.onPlaylistItem?(["playlistItem": String(data:data, encoding:String.Encoding.utf8) as Any, "index": index]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + +// item.addObserver(self, forKeyPath:"playbackLikelyToKeepUp", options:.new, context:nil) + } + + override func jwplayer(_ player:JWPlayer, didLoadPlaylist playlist:[JWPlayerItem]) { + super.jwplayer(player, didLoadPlaylist: playlist) + + let playlistArray:NSMutableArray! = NSMutableArray() + + for item:JWPlayerItem? in playlist { +// var file:String! +// +// var sourceDict: [String: Any] = [:] +// +// for source in item?.videoSources ?? [] { +// sourceDict["file"] = source.file?.absoluteString +// sourceDict["label"] = source.label +// sourceDict["default"] = source.defaultVideo +// +// if source.defaultVideo { +// file = source.file?.absoluteString ?? "" +// } +// } +// +// var schedDict: [String: Any] = [:] +// if let adSchedule = item?.adSchedule { +// for sched in adSchedule { +// schedDict["offset"] = sched.offset +// schedDict["tags"] = sched.tags +// schedDict["type"] = sched.type +// } +// } +// +// var trackDict: [String: Any] = [:] +// +// if let mediaTracks = item?.mediaTracks { +// for track in mediaTracks { +// trackDict["file"] = track.file?.absoluteString +// trackDict["label"] = track.label +// trackDict["default"] = track.defaultTrack +// } +// } +// +// let itemDict: [String: Any] = [ +// "file": file ?? "", +// "mediaId": item?.mediaId ?? "", +// "title": item?.title ?? "", +// "description": item?.description ?? "", +// "image": item?.posterImage?.absoluteString ?? "", +// "startTime": item?.startTime ?? 0, +// "adVmap": item?.vmapURL?.absoluteString ?? "", +// "recommendations": item?.recommendations?.absoluteString ?? "", +// "sources": sourceDict as Any, +// "adSchedule": trackDict, +// "tracks": schedDict +// ] + + playlistArray.add(item?.toJSONObject() as Any) + } + + do { + let data:Data! = try JSONSerialization.data(withJSONObject: playlistArray as Any, options:.prettyPrinted) + + parentView.onPlaylist?(["playlist": String(data:data as Data, encoding:String.Encoding.utf8) as Any]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + override func jwplayerPlaylistHasCompleted(_ player:JWPlayer) { + super.jwplayerPlaylistHasCompleted(player) + parentView.onPlaylistComplete?([:]) + } + + override func jwplayer(_ player:JWPlayer, usesMediaType type:JWMediaType) { + super.jwplayer(player, usesMediaType:type) + } + + override func jwplayer(_ player:JWPlayer, seekedFrom oldPosition:TimeInterval, to newPosition:TimeInterval) { + super.jwplayer(player, seekedFrom:oldPosition, to:newPosition) + parentView.onSeek?(["from": oldPosition, "to": newPosition]) + } + + override func jwplayerHasSeeked(_ player:JWPlayer) { + super.jwplayerHasSeeked(player) + parentView.onSeeked?([:]) + } + + override func jwplayer(_ player:JWPlayer, playbackRateChangedTo rate:Double, at time:TimeInterval) { + super.jwplayer(player, playbackRateChangedTo:rate, at:time) + parentView.onRateChanged?(["rate": rate, "at": time]) + } + + override func jwplayer(_ player:JWPlayer, updatedCues cues:[JWCue]) { + super.jwplayer(player, updatedCues:cues) + } + + // MARK: - JWPlayer Ad Delegate + + override func jwplayer(_ player: JWPlayer, adEvent event: JWAdEvent) { + super.jwplayer(player, adEvent:event) + parentView.onAdEvent?(["client": event.client, "type": event.type]) + } + + // MARK: - JWPlayer Cast Delegate + + override func castController(_ controller:JWCastController, castingBeganWithDevice device:JWCastingDevice) { + super.castController(controller, castingBeganWithDevice:device) + parentView.onCasting?([:]) + } + + override func castController(_ controller:JWCastController, castingEndedWithError error: Error?) { + super.castController(controller, castingEndedWithError:error) + parentView.onCastingEnded?(["error": error as Any]) + } + + override func castController(_ controller:JWCastController, castingFailedWithError error: Error) { + super.castController(controller, castingFailedWithError:error) + parentView.onCastingFailed?(["error": error as Any]) + } + + override func castController(_ controller:JWCastController, connectedTo device: JWCastingDevice) { + let dict:NSMutableDictionary! = NSMutableDictionary() + + dict.setObject(device.name, forKey:"name" as NSCopying) + dict.setObject(device.identifier, forKey:"identifier" as NSCopying) + + do { + let data:Data! = try JSONSerialization.data(withJSONObject: dict as Any, options:.prettyPrinted) + + parentView.onConnectedToCastingDevice?(["device": String(data:data as Data, encoding:String.Encoding.utf8) as Any]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + override func castController(_ controller:JWCastController, connectionFailedWithError error: Error) { + super.castController(controller, connectionFailedWithError:error) + parentView.onConnectionFailed?(["error": error as Any]) + } + + override func castController(_ controller:JWCastController, connectionRecoveredWithDevice device:JWCastingDevice) { + super.castController(controller, connectionRecoveredWithDevice:device) + parentView.onConnectionRecovered?([:]) + } + + override func castController(_ controller:JWCastController, connectionSuspendedWithDevice device:JWCastingDevice) { + super.castController(controller, connectionSuspendedWithDevice:device) + parentView.onConnectionTemporarilySuspended?([:]) + } + + override func castController(_ controller: JWCastController, devicesAvailable devices:[JWCastingDevice]) { + parentView.availableDevices = devices + + var devicesInfo: [[String: Any]] = [] + for device in devices { + var dict: [String: Any] = [:] + + dict["name"] = device.name + dict["identifier"] = device.identifier + + devicesInfo.append(dict) + } + + do { + let data:Data! = try JSONSerialization.data(withJSONObject: devicesInfo as Any, options:.prettyPrinted) + + parentView.onCastingDevicesAvailable?(["devices": String(data:data as Data, encoding:String.Encoding.utf8) as Any]) + } catch { + print("Error converting dictionary to JSON data: \(error)") + } + } + + override func castController(_ controller: JWCastController, disconnectedWithError error: (Error)?) { + super.castController(controller, disconnectedWithError:error) + parentView.onDisconnectedFromCastingDevice?(["error": error as Any]) + } + + // MARK: - JWPlayer AV Delegate + + override func jwplayer(_ player:JWPlayer, audioTracksUpdated levels:[JWMediaSelectionOption]) { + super.jwplayer(player, audioTracksUpdated:levels) + parentView.onAudioTracks?([:]) + } + + override func jwplayer(_ player:JWPlayer, audioTrackChanged currentLevel:Int) { + super.jwplayer(player, audioTrackChanged:currentLevel) + } + + override func jwplayer(_ player:JWPlayer, captionPresented caption:[String], at time:JWTimeData) { + super.jwplayer(player, captionPresented:caption, at:time) + } + + override func jwplayer(_ player:JWPlayer, captionTrackChanged index:Int) { + super.jwplayer(player, captionTrackChanged:index) + } + + override func jwplayer(_ player:JWPlayer, qualityLevelChanged currentLevel:Int) { + super.jwplayer(player, qualityLevelChanged:currentLevel) + } + + override func jwplayer(_ player:JWPlayer, qualityLevelsUpdated levels:[JWVideoSource]) { + super.jwplayer(player, qualityLevelsUpdated:levels) + } + + override func jwplayer(_ player:JWPlayer, updatedCaptionList options:[JWMediaSelectionOption]) { + super.jwplayer(player, updatedCaptionList:options) + } +} diff --git a/ios/RNJWPlayer/RNJWPlayerViewManager.m b/ios/RNJWPlayer/RNJWPlayerViewManager.m index 57141513..527135f5 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.m +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.m @@ -1,611 +1,146 @@ -#import +#if __has_include("React/RCTViewManager.h") +#import "React/RCTViewManager.h" +#else +#import "RCTViewManager.h" +#endif -#import "RNJWPlayerViewManager.h" -#import "RNJWPlayerView.h" +#import #import "RCTUIManager.h" -@interface RNJWPlayerViewManager () - -@end - -@implementation RNJWPlayerViewManager - -RCT_EXPORT_MODULE() - -- (UIView*)view -{ - return [[RNJWPlayerView alloc] init]; -} +@interface RCT_EXTERN_MODULE(RNJWPlayerViewManager, RCTViewManager) /* player state events */ -RCT_EXPORT_VIEW_PROPERTY(onTime, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onLoaded, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onSeek, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onSeeked, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onRateChanged, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onPlaylist, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onPlaylistComplete, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onBeforeComplete, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onComplete, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onVisible, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onBeforePlay, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onAttemptPlay, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onPlay, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onPause, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onBuffer, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onUpdateBuffer, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onIdle, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onPlaylistItem, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onTime, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onLoaded, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onSeek, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onSeeked, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onRateChanged, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlaylist, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlaylistComplete, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onBeforeComplete, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onComplete, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onVisible, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onBeforePlay, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onAttemptPlay, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlay, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPause, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onBuffer, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onUpdateBuffer, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onIdle, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlaylistItem, RCTDirectEventBlock); /* av events */ -RCT_EXPORT_VIEW_PROPERTY(onAudioTracks, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onAudioTracks, RCTDirectEventBlock); /* player events */ -RCT_EXPORT_VIEW_PROPERTY(onPlayerReady, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onSetupPlayerError, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onPlayerError, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onPlayerWarning, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlayerReady, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onSetupPlayerError, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlayerError, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlayerWarning, RCTDirectEventBlock); /* ad events */ -RCT_EXPORT_VIEW_PROPERTY(onPlayerAdWarning, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onPlayerAdError, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onAdEvent, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onAdTime, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlayerAdWarning, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlayerAdError, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onAdEvent, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onAdTime, RCTDirectEventBlock); /* jwplayer view controller events */ -RCT_EXPORT_VIEW_PROPERTY(onControlBarVisible, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onScreenTapped, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onFullScreen, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onFullScreenRequested, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onFullScreenExit, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onFullScreenExitRequested, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onControlBarVisible, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onScreenTapped, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onFullScreen, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onFullScreenRequested, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onFullScreenExit, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onFullScreenExitRequested, RCTDirectEventBlock); /* jwplayer view events */ -RCT_EXPORT_VIEW_PROPERTY(onPlayerSizeChange, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlayerSizeChange, RCTDirectEventBlock); /* casting events */ -RCT_EXPORT_VIEW_PROPERTY(onCastingDevicesAvailable, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onConnectedToCastingDevice, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onDisconnectedFromCastingDevice, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onConnectionTemporarilySuspended, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onConnectionRecovered, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onConnectionFailed, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onCasting, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onCastingEnded, RCTBubblingEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onCastingFailed, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onCastingDevicesAvailable, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onConnectedToCastingDevice, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onDisconnectedFromCastingDevice, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onConnectionTemporarilySuspended, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onConnectionRecovered, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onConnectionFailed, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onCasting, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onCastingEnded, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onCastingFailed, RCTDirectEventBlock); /* props */ RCT_EXPORT_VIEW_PROPERTY(config, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); -RCT_REMAP_METHOD(state, - tag:(nonnull NSNumber*)reactTag +RCT_EXTERN_METHOD(state: (nonnull NSNumber*)reactTag stateWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - - NSError *error = [[NSError alloc] init]; - reject(@"no_player", @"There is no playerViewController or playerView", error); - } else { - if (view.playerViewController) { - resolve([NSNumber numberWithInt:[view.playerViewController.player getState]]); - } else if (view.playerView) { - resolve([NSNumber numberWithInt:[view.playerView.player getState]]); - } else { - NSError *error = [[NSError alloc] init]; - reject(@"no_player", @"There is no playerView", error); - }; - } - }]; -} - -RCT_EXPORT_METHOD(pause:(nonnull NSNumber*)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - view.userPaused = YES; - if (view.playerView) { - [view.playerView.player pause]; - } else if (view.playerViewController) { - [view.playerViewController.player pause]; - } - } - }]; -} - -RCT_EXPORT_METHOD(play:(nonnull NSNumber *)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerView) { - [view.playerView.player play]; - } else if (view.playerViewController) { - [view.playerViewController.player play]; - } - } - }]; - -} - -RCT_EXPORT_METHOD(stop:(nonnull NSNumber *)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - view.userPaused = YES; - if (view.playerView) { - [view.playerView.player stop]; - } else if (view.playerViewController) { - [view.playerViewController.player stop]; - } - } - }]; - -} - -RCT_REMAP_METHOD(position, - tag:(nonnull NSNumber *)reactTag + +RCT_EXTERN_METHOD(pause: (nonnull NSNumber*)reactTag) + +RCT_EXTERN_METHOD(play: (nonnull NSNumber *)reactTag) + +RCT_EXTERN_METHOD(stop: (nonnull NSNumber *)reactTag) + +RCT_EXTERN_METHOD(position: (nonnull NSNumber *)reactTag positionResolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - - NSError *error = [[NSError alloc] init]; - reject(@"no_player", @"There is no playerView", error); - } else { - if (view.playerView) { - resolve(@(view.playerView.player.time.position)); - } else if (view.playerViewController) { - resolve(@(view.playerViewController.player.time.position)); - } - } - }]; -} - -RCT_EXPORT_METHOD(toggleSpeed:(nonnull NSNumber*)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerView) { - if ([view.playerView.player playbackRate] < 2.0) { - view.playerView.player.playbackRate = [view.playerView.player playbackRate] + 0.5; - } else { - view.playerView.player.playbackRate = 0.5; - } - } else if (view.playerViewController) { - if ([view.playerViewController.player playbackRate] < 2.0) { - view.playerViewController.player.playbackRate = [view.playerViewController.player playbackRate] + 0.5; - } else { - view.playerViewController.player.playbackRate = 0.5; - } - } - } - }]; - -} - -RCT_EXPORT_METHOD(setSpeed:(nonnull NSNumber*)reactTag: (double)speed) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerView) { - view.playerView.player.playbackRate = speed; - } else if (view.playerViewController) { - view.playerViewController.player.playbackRate = speed; - } - } - }]; -} - -RCT_EXPORT_METHOD(setPlaylistIndex: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerView) { - [view.playerView.player loadPlayerItemAtIndex:[index integerValue]]; - } else if (view.playerViewController) { - [view.playerViewController.player loadPlayerItemAtIndex:[index integerValue]]; - } - } - }]; -} - -RCT_EXPORT_METHOD(seekTo :(nonnull NSNumber *)reactTag: (nonnull NSNumber *)time) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerView) { - [view.playerView.player seekTo:[time integerValue]]; - } else if (view.playerViewController) { - [view.playerViewController.player seekTo:[time integerValue]]; - } - } - }]; -} - -RCT_EXPORT_METHOD(setVolume: (nonnull NSNumber *)reactTag :(nonnull NSNumber *)volume) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerViewController == nil && view.playerView == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerView) { - [view.playerView.player setVolume:[volume floatValue]]; - } else if (view.playerViewController) { - [view.playerViewController.player setVolume:[volume floatValue]]; - } - } - }]; -} - -RCT_EXPORT_METHOD(togglePIP: (nonnull NSNumber *)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || view.playerView == nil) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - AVPictureInPictureController* pipController = view.playerView.pictureInPictureController; - if (pipController != nil && pipController.pictureInPicturePossible) { - if (pipController.pictureInPictureActive) { - [pipController stopPictureInPicture]; - } else { - [pipController startPictureInPicture]; - } - } - } - }]; -} - -RCT_EXPORT_METHOD(setUpCastController: (nonnull NSNumber *)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || view.playerView == nil) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - [view setUpCastController]; - } - }]; -} - -RCT_EXPORT_METHOD(presentCastDialog: (nonnull NSNumber *)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || view.playerView == nil) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - [view presentCastDialog]; - } - }]; -} - -RCT_REMAP_METHOD(connectedDevice, - tag:(nonnull NSNumber *)reactTag + rejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(toggleSpeed: (nonnull NSNumber*)reactTag) + +RCT_EXTERN_METHOD(setSpeed: (nonnull NSNumber*)reactTag: (double)speed) + +RCT_EXTERN_METHOD(setPlaylistIndex: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) + +RCT_EXTERN_METHOD(seekTo: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)time) + +RCT_EXTERN_METHOD(setVolume: (nonnull NSNumber *)reactTag :(nonnull NSNumber *)volume) + +RCT_EXTERN_METHOD(togglePIP: (nonnull NSNumber *)reactTag) + +RCT_EXTERN_METHOD(setUpCastController: (nonnull NSNumber *)reactTag) + +RCT_EXTERN_METHOD(presentCastDialog: (nonnull NSNumber *)reactTag) + +RCT_EXTERN_METHOD(connectedDevice: (nonnull NSNumber *)reactTag resolve:(RCTPromiseResolveBlock)resolve rejecte:(RCTPromiseRejectBlock)reject) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || view.playerView == nil) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - - NSError *error = [[NSError alloc] init]; - reject(@"no_player", @"There is no player", error); - } else { - JWCastingDevice *device = view.connectedDevice; - - if (device != nil) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - - [dict setObject:device.name forKey:@"name"]; - [dict setObject:device.identifier forKey:@"identifier"]; - - NSError *error; - NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error: &error]; - - resolve([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); - } else { - NSError *error = [[NSError alloc] init]; - reject(@"no_connected_device", @"There is no connected device", error); - } - } - }]; -} - -RCT_REMAP_METHOD(availableDevices, - tag:(nonnull NSNumber *)reactTag + +RCT_EXTERN_METHOD(availableDevices: (nonnull NSNumber *)reactTag solve:(RCTPromiseResolveBlock)resolve eject:(RCTPromiseRejectBlock)reject) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || view.playerView == nil) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - - NSError *error = [[NSError alloc] init]; - reject(@"no_player", @"There is no player", error); - } else { - if (view.availableDevices != nil) { - NSMutableArray *devicesInfo = [[NSMutableArray alloc] init]; - - for (JWCastingDevice *device in view.availableDevices) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - - [dict setObject:device.name forKey:@"name"]; - [dict setObject:device.identifier forKey:@"identifier"]; - - [devicesInfo addObject:dict]; - } - - NSError *error; - NSData *data = [NSJSONSerialization dataWithJSONObject:devicesInfo options:NSJSONWritingPrettyPrinted error: &error]; - - resolve([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); - } else { - NSError *error = [[NSError alloc] init]; - reject(@"no_available_device", @"There are no available devices", error); - } - } - }]; -} - -RCT_REMAP_METHOD(castState, - tag:(nonnull NSNumber *)reactTag + +RCT_EXTERN_METHOD(castState: (nonnull NSNumber *)reactTag solver:(RCTPromiseResolveBlock)resolve ejecter:(RCTPromiseRejectBlock)reject) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || view.playerView == nil) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - - NSError *error = [[NSError alloc] init]; - reject(@"no_player", @"There is no player", error); - } else { - #if USE_GOOGLE_CAST - resolve([NSNumber numberWithInt:[view castState]]); - #else - NSError *error = [[NSError alloc] init]; - reject(@"missing_google_cast_pod", @"GoogleCast is not installed.", error); - #endif - } - }]; -} - -RCT_REMAP_METHOD(getAudioTracks, - tag:(nonnull NSNumber *)reactTag + +RCT_EXTERN_METHOD(getAudioTracks: (nonnull NSNumber *)reactTag resolve:(RCTPromiseResolveBlock)resolve eject:(RCTPromiseRejectBlock)reject) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerView == nil && view.playerViewController == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - - NSError *error = [[NSError alloc] init]; - reject(@"no_player", @"There is no player", error); - } else { - NSArray *audioTracks; - if (view.playerView) { - audioTracks = [view.playerView.player audioTracks]; - } else if (view.playerViewController) { - audioTracks = [view.playerViewController.player audioTracks]; - } - - if (audioTracks) { - NSMutableArray *results = [[NSMutableArray alloc] init]; - for (int i = 0; i < audioTracks.count; i++) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - id audioTrack = [audioTracks objectAtIndex:i]; - [dict setObject:audioTrack[@"language"] forKey:@"language"]; - [dict setObject:audioTrack[@"autoselect"] forKey:@"autoSelect"]; - [dict setObject:audioTrack[@"defaulttrack"] forKey:@"defaultTrack"]; - [dict setObject:audioTrack[@"name"] forKey:@"name"]; - [dict setObject:audioTrack[@"groupid"] forKey:@"groupId"]; - [results addObject:dict]; - } - resolve(results); - } else { - NSError *error = [[NSError alloc] init]; - reject(@"no_audio_tracks", @"There are no audio tracks.", error); - } - } - }]; -} - -RCT_REMAP_METHOD(getCurrentAudioTrack, - tag:(nonnull NSNumber *)reactTag + +RCT_EXTERN_METHOD(getCurrentAudioTrack: (nonnull NSNumber *)reactTag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerView == nil && view.playerViewController == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - - NSError *error = [[NSError alloc] init]; - reject(@"no_player", @"There is no player", error); - } else { - if (view.playerView) { - resolve([NSNumber numberWithInteger:[view.playerView.player currentAudioTrack]]); - } else if (view.playerViewController) { - resolve([NSNumber numberWithInteger:[view.playerViewController.player currentAudioTrack]]); - } else { - NSError *error = [[NSError alloc] init]; - reject(@"no_player", @"There is no player", error); - } - } - }]; -} - -RCT_EXPORT_METHOD(setCurrentAudioTrack: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerView == nil && view.playerViewController == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerView) { - [view.playerView.player setCurrentAudioTrack:[index integerValue]]; - } else if (view.playerViewController) { - [view.playerViewController.player setCurrentAudioTrack:[index integerValue]]; - } - } - }]; -} - -RCT_EXPORT_METHOD(setControls: (nonnull NSNumber *)reactTag: (BOOL)show) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerView == nil && view.playerViewController == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerViewController) { - [view toggleUIGroup:view.playerViewController.view :@"JWPlayerKit.InterfaceView" :nil :show]; - } - } - }]; -} - -RCT_EXPORT_METHOD(setVisibility: (nonnull NSNumber *)reactTag: (BOOL)visibilty: (nonnull NSArray *)controls) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerView == nil && view.playerViewController == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerViewController) { - [view setVisibility:visibilty forControls:controls]; - } - } - }]; -} - -RCT_EXPORT_METHOD(setLockScreenControls: (nonnull NSNumber *)reactTag: (BOOL)show) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerView == nil && view.playerViewController == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerViewController) { - view.playerViewController.enableLockScreenControls = show; - } - } - }]; -} - -RCT_EXPORT_METHOD(setCurrentCaptions: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerView == nil && view.playerViewController == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerView) { - [view.playerView.player setCurrentCaptionsTrack:[index integerValue] + 1]; - } else if (view.playerViewController) { - [view.playerViewController.player setCurrentCaptionsTrack:[index integerValue] + 1]; - } - } - }]; -} - -RCT_EXPORT_METHOD(setLicenseKey: (nonnull NSNumber *)reactTag: (nonnull NSString *)license) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - [view setLicense:license]; - } - }]; -} - -RCT_EXPORT_METHOD(quite) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - for (id view in viewRegistry) { - if ([view isKindOfClass:[RNJWPlayerView class]]) { - RNJWPlayerView *rnjwView = view; - if (rnjwView.playerView) { - [rnjwView.playerView.player pause]; - [rnjwView.playerView.player stop]; - } else if (rnjwView.playerViewController) { - [rnjwView.playerViewController.player pause]; - [rnjwView.playerViewController.player stop]; - } - } - } - }]; -} - -RCT_EXPORT_METHOD(reset) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - for (id view in viewRegistry) { - if ([view isKindOfClass:[RNJWPlayerView class]]) { - RNJWPlayerView *rnjwView = view; - if (rnjwView) { - [rnjwView startDeinitProcess]; - } - } - } - }]; -} - -RCT_EXPORT_METHOD(loadPlaylist: (nonnull NSNumber *)reactTag: (nonnull NSArray *)playlist) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerView == nil && view.playerViewController == nil)) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - NSMutableArray *playlistArray = [[NSMutableArray alloc] init]; - - for (id item in playlist) { - JWPlayerItem *playerItem = [view getPlayerItem:item]; - [playlistArray addObject:playerItem]; - } - - if (view.playerView) { - [view.playerView.player loadPlaylistWithItems:playlistArray]; - } else if (view.playerViewController) { - [view.playerViewController.player loadPlaylistWithItems:playlistArray]; - } - } - }]; -} - -RCT_EXPORT_METHOD(setFullscreen: (nonnull NSNumber *)reactTag: (BOOL)fullscreen) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RNJWPlayerView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RNJWPlayerView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); - } else { - if (view.playerViewController) { - if (fullscreen) { - [view.playerViewController transitionToFullScreenAnimated:YES completion:nil]; - } else { - [view.playerViewController dismissFullScreenAnimated:YES completion:nil]; - } - } else { - RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerViewController, got: %@", view); - } - } - }]; -} + +RCT_EXTERN_METHOD(setCurrentAudioTrack: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) + +RCT_EXTERN_METHOD(setControls: (nonnull NSNumber *)reactTag: (BOOL)show) + +RCT_EXTERN_METHOD(setVisibility: (nonnull NSNumber *)reactTag: (BOOL)visibilty: (nonnull NSArray *)controls) + +RCT_EXTERN_METHOD(setLockScreenControls: (nonnull NSNumber *)reactTag: (BOOL)show) + +RCT_EXTERN_METHOD(setCurrentCaptions: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) + +RCT_EXTERN_METHOD(setCurrentCaptions: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) + +RCT_EXTERN_METHOD(setLicenseKey: (nonnull NSNumber *)reactTag: (nonnull NSString *)license) + +RCT_EXTERN_METHOD(quite) + +RCT_EXTERN_METHOD(reset) + +RCT_EXTERN_METHOD(loadPlaylist: (nonnull NSNumber *)reactTag: (nonnull NSArray *)playlist) + +RCT_EXTERN_METHOD(setFullscreen: (nonnull NSNumber *)reactTag: (BOOL)fullscreen) @end diff --git a/ios/RNJWPlayer/RNJWPlayerViewManager.swift b/ios/RNJWPlayer/RNJWPlayerViewManager.swift new file mode 100644 index 00000000..cc853ebc --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.swift @@ -0,0 +1,498 @@ +import AVFoundation +import React +import JWPlayerKit + +@objc(RNJWPlayerViewManager) +class RNJWPlayerViewManager: RCTViewManager { + + override func view() -> UIView { + return RNJWPlayerView() + } + + func methodQueue() -> DispatchQueue { + return bridge.uiManager.methodQueue + } + + override class func requiresMainQueueSetup() -> Bool { + return true + } + + @objc func state(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + self.bridge.uiManager.addUIBlock { (_, viewRegistry) in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + let error = NSError(domain: "", code: 0, userInfo: nil) + reject("no_player", "There is no playerViewController or playerView", error) + return + } + + if let playerViewController = view.playerViewController { + resolve(NSNumber(value: playerViewController.player.getState().rawValue)) + } else if let playerView = view.playerView { + resolve(NSNumber(value: playerView.player.getState().rawValue)) + } else { + let error = NSError(domain: "", code: 0, userInfo: nil) + reject("no_player", "There is no playerView", error) + } + } + } + + @objc func pause(_ reactTag: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + view.userPaused = true + if let playerView = view.playerView { + playerView.player.pause() + } else if let playerViewController = view.playerViewController { + playerViewController.player.pause() + } + } + } + + @objc func play(_ reactTag: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerView = view.playerView { + playerView.player.play() + } else if let playerViewController = view.playerViewController { + playerViewController.player.play() + } + } + } + + @objc func stop(_ reactTag: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + view.userPaused = true + if let playerView = view.playerView { + playerView.player.stop() + } else if let playerViewController = view.playerViewController { + playerViewController.player.stop() + } + } + } + + @objc func position(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There is no playerView"]) + reject("no_player", "Invalid view returned from registry, expecting RNJWPlayerView", error) + return + } + + if let playerView = view.playerView { + resolve(playerView.player.time.position as NSNumber) + } else if let playerViewController = view.playerViewController { + resolve(playerViewController.player.time.position as NSNumber) + } else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There is no playerView"]) + reject("no_player", "There is no playerView", error) + } + } + } + + @objc func toggleSpeed(_ reactTag: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerView = view.playerView { + if playerView.player.playbackRate < 2.0 { + playerView.player.playbackRate += 0.5 + } else { + playerView.player.playbackRate = 0.5 + } + } else if let playerViewController = view.playerViewController { + if playerViewController.player.playbackRate < 2.0 { + playerViewController.player.playbackRate += 0.5 + } else { + playerViewController.player.playbackRate = 0.5 + } + } + } + } + + @objc func setSpeed(_ reactTag: NSNumber, speed: Double) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerView = view.playerView { + playerView.player.playbackRate = speed + } else if let playerViewController = view.playerViewController { + playerViewController.player.playbackRate = speed + } + } + } + + @objc func setPlaylistIndex(_ reactTag: NSNumber, index: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerView = view.playerView { + playerView.player.loadPlayerItemAt(index: index.intValue) + } else if let playerViewController = view.playerViewController { + playerViewController.player.loadPlayerItemAt(index: index.intValue) + } + } + } + + @objc func seekTo(_ reactTag: NSNumber, time: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerView = view.playerView { + playerView.player.seek(to: TimeInterval(time.intValue)) + } else if let playerViewController = view.playerViewController { + playerViewController.player.seek(to: TimeInterval(time.intValue)) + } + } + } + + @objc func setVolume(_ reactTag: NSNumber, volume: Double) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerView = view.playerView { + playerView.player.volume = volume + } else if let playerViewController = view.playerViewController { + playerViewController.player.volume = volume + } + } + } + + + @objc func togglePIP(_ reactTag: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView, let pipController = view.playerView?.pictureInPictureController else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if pipController.isPictureInPicturePossible { + if pipController.isPictureInPictureActive { + pipController.stopPictureInPicture() + } else { + pipController.startPictureInPicture() + } + } + } + } + + @objc func setUpCastController(_ reactTag: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + view.setUpCastController() + } + } + + @objc func presentCastDialog(_ reactTag: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + view.presentCastDialog() + } + } + + @objc func connectedDevice(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There is no player"]) + reject("no_player", "Invalid view returned from registry, expecting RNJWPlayerView", error) + return + } + + if let device = view.connectedDevice() { + var dict = [String: Any]() + dict["name"] = device.name + dict["identifier"] = device.identifier + + do { + let data = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted) + resolve(String(data: data, encoding: .utf8)) + } catch { + reject("json_error", "Failed to serialize JSON", error) + } + } else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There is no connected device"]) + reject("no_connected_device", "There is no connected device", error) + } + } + } + + @objc func availableDevices(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There is no player"]) + reject("no_player", "Invalid view returned from registry, expecting RNJWPlayerView", error) + return + } + + if let availableDevices = view.getAvailableDevices() { + var devicesInfo: [[String: Any]] = [] + + for device in availableDevices { + var dict = [String: Any]() + dict["name"] = device.name + dict["identifier"] = device.identifier + devicesInfo.append(dict) + } + + do { + let data = try JSONSerialization.data(withJSONObject: devicesInfo, options: .prettyPrinted) + resolve(String(data: data, encoding: .utf8)) + } catch { + reject("json_error", "Failed to serialize JSON", error) + } + } else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There are no available devices"]) + reject("no_available_device", "There are no available devices", error) + } + } + } + + @objc func castState(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There is no player"]) + reject("no_player", "Invalid view returned from registry, expecting RNJWPlayerView", error) + return + } + + resolve(view.castState) + } + } + + @objc func getAudioTracks(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There is no player"]) + reject("no_player", "Invalid view returned from registry, expecting RNJWPlayerView", error) + return + } + + let audioTracks: [Any]? = view.playerView?.player.audioTracks ?? view.playerViewController?.player.audioTracks + + if let audioTracks = audioTracks as? [[String: Any]] { + var results: [[String: Any]] = [] + for track in audioTracks { + if let language = track["language"], let autoSelect = track["autoselect"], + let defaultTrack = track["defaulttrack"], let name = track["name"], + let groupId = track["groupid"] { + let trackDict: [String: Any] = [ + "language": language, + "autoSelect": autoSelect, + "defaultTrack": defaultTrack, + "name": name, + "groupId": groupId + ] + results.append(trackDict) + } + } + resolve(results) + } else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There are no audio tracks"]) + reject("no_audio_tracks", "There are no audio tracks", error) + } + } + } + + @objc func getCurrentAudioTrack(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There is no player"]) + reject("no_player", "Invalid view returned from registry, expecting RNJWPlayerView", error) + return + } + + if let playerView = view.playerView { + resolve(NSNumber(value: playerView.player.currentAudioTrack)) + } else if let playerViewController = view.playerViewController { + resolve(NSNumber(value: playerViewController.player.currentAudioTrack)) + } else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "There is no player"]) + reject("no_player", "There is no player", error) + } + } + } + + @objc func setCurrentAudioTrack(_ reactTag: NSNumber, index: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerView = view.playerView { + playerView.player.currentAudioTrack = index.intValue + } else if let playerViewController = view.playerViewController { + playerViewController.player.currentAudioTrack = index.intValue + } + } + } + + @objc func setControls(_ reactTag: NSNumber, show: Bool) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerViewController = view.playerViewController { + view.toggleUIGroup(view: playerViewController.view, name: "JWPlayerKit.InterfaceView", ofSubview: nil, show: show) + } + } + } + + @objc func setVisibility(_ reactTag: NSNumber, visibility: Bool, controls: [String]) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if view.playerViewController != nil { + view.setVisibility(isVisible: visibility, forControls: controls) + } + } + } + + @objc func setLockScreenControls(_ reactTag: NSNumber, show: Bool) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerViewController = view.playerViewController { + playerViewController.enableLockScreenControls = show + } + } + } + + @objc func setCurrentCaptions(_ reactTag: NSNumber, index: NSNumber) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerView = view.playerView { + playerView.player.currentCaptionsTrack = index.intValue + 1 + } else if let playerViewController = view.playerViewController { + playerViewController.player.currentCaptionsTrack = index.intValue + 1 + } + } + } + + @objc func setLicenseKey(_ reactTag: NSNumber, license: String) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + view.setLicense(license: license) + } + } + + @objc func quite() { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + for (_, view) in viewRegistry ?? [:] { + if let rnjwView = view as? RNJWPlayerView { + if let playerView = rnjwView.playerView { + playerView.player.pause() + playerView.player.stop() + } else if let playerViewController = rnjwView.playerViewController { + playerViewController.player.pause() + playerViewController.player.stop() + } + } + } + } + } + + @objc func reset() { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + for (_, view) in viewRegistry ?? [:] { + if let rnjwView = view as? RNJWPlayerView { + rnjwView.startDeinitProcess() + } + } + } + } + + @objc func loadPlaylist(_ reactTag: NSNumber, playlist: [Any]) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + var playlistArray = [JWPlayerItem]() + + for item in playlist { + if let playerItem = try? view.getPlayerItem(item: item as! [String: Any]) { + playlistArray.append(playerItem) + } + } + + if let playerView = view.playerView { + playerView.player.loadPlaylist(items: playlistArray) + } else if let playerViewController = view.playerViewController { + playerViewController.player.loadPlaylist(items: playlistArray) + } + } + } + + @objc func setFullscreen(_ reactTag: NSNumber, fullscreen: Bool) { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + guard let view = viewRegistry?[reactTag] as? RNJWPlayerView else { + print("Invalid view returned from registry, expecting RNJWPlayerView, got: \(String(describing: viewRegistry?[reactTag]))") + return + } + + if let playerViewController = view.playerViewController { + if fullscreen { + playerViewController.transitionToFullScreen(animated: true, completion: nil) + } else { + playerViewController.dismissFullScreen(animated: true, completion: nil) + } + } else { + print("Invalid view returned from registry, expecting RNJWPlayerViewController, got: \(view)") + } + } + } + +} diff --git a/package.json b/package.json index 682c089e..42e5ff41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-jw-media-player", - "version": "0.2.41", + "version": "0.2.42", "description": "React-native Android/iOS plugin for JWPlayer SDK (https://www.jwplayer.com/)", "main": "index.js", "types": "./index.d.ts", @@ -33,8 +33,8 @@ "license": "MIT", "homepage": "https://github.com/chaimPaneth/react-native-jw-media-player#readme", "devDependencies": { - "react": ">= 16.13.1", - "react-native": ">= 0.63.2", + "react": ">= 18.2.0", + "react-native": ">= 0.72.5", "lodash": ">= 4.17.21" } }