From ce15441f3009c4879f69058907255453f3d10b10 Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Tue, 5 Dec 2023 16:29:51 +0200 Subject: [PATCH 01/11] init migrate to swift --- Example/ios/Podfile | 2 +- Example/ios/Podfile.lock | 58 +- .../ios/RNJWPlayer.xcodeproj/project.pbxproj | 4 +- RNJWPlayer.podspec | 2 +- ...Manager.h => RNJWPlayer-Bridging-Header.h} | 4 - ios/RNJWPlayer.xcodeproj/project.pbxproj | 42 +- ios/RNJWPlayer/RCTConvert+RNJWPlayer.h | 27 - ios/RNJWPlayer/RCTConvert+RNJWPlayer.m | 61 - ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift | 119 ++ ios/RNJWPlayer/RNJWPlayerView.h | 119 -- ios/RNJWPlayer/RNJWPlayerView.m | 1836 ----------------- ios/RNJWPlayer/RNJWPlayerView.swift | 1822 ++++++++++++++++ ios/RNJWPlayer/RNJWPlayerViewController.h | 31 - ios/RNJWPlayer/RNJWPlayerViewController.m | 715 ------- ios/RNJWPlayer/RNJWPlayerViewController.swift | 591 ++++++ ios/RNJWPlayer/RNJWPlayerViewManager.m | 674 +----- ios/RNJWPlayer/RNJWPlayerViewManager.swift | 490 +++++ 17 files changed, 3188 insertions(+), 3409 deletions(-) rename ios/{RNJWPlayer/RNJWPlayerViewManager.h => RNJWPlayer-Bridging-Header.h} (67%) delete mode 100644 ios/RNJWPlayer/RCTConvert+RNJWPlayer.h delete mode 100644 ios/RNJWPlayer/RCTConvert+RNJWPlayer.m create mode 100644 ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift delete mode 100644 ios/RNJWPlayer/RNJWPlayerView.h delete mode 100644 ios/RNJWPlayer/RNJWPlayerView.m create mode 100644 ios/RNJWPlayer/RNJWPlayerView.swift delete mode 100644 ios/RNJWPlayer/RNJWPlayerViewController.h delete mode 100644 ios/RNJWPlayer/RNJWPlayerViewController.m create mode 100644 ios/RNJWPlayer/RNJWPlayerViewController.swift create mode 100644 ios/RNJWPlayer/RNJWPlayerViewManager.swift diff --git a/Example/ios/Podfile b/Example/ios/Podfile index 37855a77..e977e1e1 100644 --- a/Example/ios/Podfile +++ b/Example/ios/Podfile @@ -1,7 +1,7 @@ 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 target 'RNJWPlayer' do diff --git a/Example/ios/Podfile.lock b/Example/ios/Podfile.lock index 8c78b9e8..d34707d9 100644 --- a/Example/ios/Podfile.lock +++ b/Example/ios/Podfile.lock @@ -73,15 +73,15 @@ 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.8.0): + - google-cast-sdk/Core (= 4.8.0) - Protobuf (~> 3.13) - - google-cast-sdk/Core (4.7.0): + - google-cast-sdk/Core (4.8.0): - Protobuf (~> 3.13) - - JWPlayerKit (4.13.0) + - 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 +289,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 +364,18 @@ 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) + - RNJWPlayer (0.2.39): + - google-cast-sdk (~> 4.8) + - JWPlayerKit (~> 4.17.0) - React - - RNScreens (3.13.1): + - 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) @@ -570,11 +566,11 @@ SPEC CHECKSUMS: FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 476ee3e89abb49e07f822b48323c51c57124b572 - google-cast-sdk: 6731e9a206ce43e0f4433d15598ad8a19f0371d5 - JWPlayerKit: cfc3559afa6aed5314e046bce83f35973c2520db + google-cast-sdk: afeb1aac0744b1bc4f70bc3db8468e33fabbff38 + JWPlayerKit: b0fd3f2abf99f40f8cf8292d550864be4b5df3f9 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c - Protobuf: 120350fc38646e2dedc26f49ecba778184ea1de2 + Protobuf: d94761c33f1239c0a43a0817ca1a5f7f7c900241 RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8 RCTRequired: 00581111c53531e39e3c6346ef0d2c0cf52a5a37 RCTTypeSafety: 07e03ee7800e7dd65cba8e52ad0c2edb06c96604 @@ -588,8 +584,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 +601,14 @@ SPEC CHECKSUMS: RNBootSplash: 2830c475d5793d6cf12312d1e8f5f1decb0dd3e8 RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0 RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df - RNGestureHandler: 61628a2c859172551aa2100d3e73d1e57878392f - RNJWPlayer: cd1846f78dc1e996fe7e93462ac140e46ca417c6 - RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19 - RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4 - SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 + RNGestureHandler: c0d04458598fcb26052494ae23dda8f8f5162b13 + RNJWPlayer: b1d2e429f23fb84c1ee12cf876479381fc3107e1 + RNScreens: 85d3880b52d34db7b8eeebe2f1a0e807c05e69fa + RNVectorIcons: 8b5bb0fa61d54cd2020af4f24a51841ce365c7e9 + SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: 17cd9a50243093b547c1e539c749928dd68152da YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 72302fbb90a843c0cefdfb43fe4bd7e42dee995b +PODFILE CHECKSUM: 0312f3bc93221dbcdcb28d35875a0e2e57051f6a -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/RNJWPlayer.podspec b/RNJWPlayer.podspec index 93fdd380..12e52cfe 100644 --- a/RNJWPlayer.podspec +++ b/RNJWPlayer.podspec @@ -11,7 +11,7 @@ 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 'google-cast-sdk', '~> 4.8' s.dependency 'React' diff --git a/ios/RNJWPlayer/RNJWPlayerViewManager.h b/ios/RNJWPlayer-Bridging-Header.h similarity index 67% rename from ios/RNJWPlayer/RNJWPlayerViewManager.h rename to ios/RNJWPlayer-Bridging-Header.h index bd3866ef..78c8d306 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.h +++ b/ios/RNJWPlayer-Bridging-Header.h @@ -3,7 +3,3 @@ #else #import "RCTViewManager.h" #endif - -@interface RNJWPlayerViewManager: RCTViewManager - -@end diff --git a/ios/RNJWPlayer.xcodeproj/project.pbxproj b/ios/RNJWPlayer.xcodeproj/project.pbxproj index 5077f25e..13a3c910 100644 --- a/ios/RNJWPlayer.xcodeproj/project.pbxproj +++ b/ios/RNJWPlayer.xcodeproj/project.pbxproj @@ -7,12 +7,14 @@ 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 */; }; + 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 */ @@ -29,15 +31,15 @@ /* 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; }; + 8650D6092B1CD19500DD1C7E /* RNJWPlayer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "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 */ @@ -75,12 +77,12 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 2AA52BE126C144B200AD26AE /* RNJWPlayerView.h */, - 2AA52BE426C144B200AD26AE /* RNJWPlayerView.m */, - 2AA52BE326C144B200AD26AE /* RNJWPlayerViewManager.h */, + 8650D6092B1CD19500DD1C7E /* RNJWPlayer-Bridging-Header.h */, 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */, - 2A5DC7BE272B5DB6003BF3E4 /* RCTConvert+RNJWPlayer.h */, - 2A5DC7BF272B5DB6003BF3E4 /* RCTConvert+RNJWPlayer.m */, + 8650D61B2B1D1E1E00DD1C7E /* RNJWPlayerViewManager.swift */, + 8650D60C2B1CD21000DD1C7E /* RNJWPlayerView.swift */, + 8650D6192B1D1E1E00DD1C7E /* RNJWPlayerViewController.swift */, + 8650D61A2B1D1E1E00DD1C7E /* RCTConvert+RNJWPlayer.swift */, 134814211AA4EA7D00B7C361 /* Products */, 3BC75FCA1E43B1DB0011FBAA /* Frameworks */, ); @@ -117,6 +119,7 @@ TargetAttributes = { 58B511DA1A9E6C8500147676 = { CreatedOnToolsVersion = 6.1.1; + LastSwiftMigration = 1410; }; }; }; @@ -143,8 +146,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 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 */, ); runOnlyForDeploymentPostprocessing = 0; @@ -229,6 +234,7 @@ 58B511F01A9E6C8500147676 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../../../ios/**", @@ -240,6 +246,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", @@ -247,12 +254,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/**", @@ -264,6 +275,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", @@ -271,6 +283,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/RNJWPlayerView.h b/ios/RNJWPlayer/RNJWPlayerView.h deleted file mode 100644 index f8cc0db4..00000000 --- a/ios/RNJWPlayer/RNJWPlayerView.h +++ /dev/null @@ -1,119 +0,0 @@ -#if __has_include("React/RCTViewManager.h") -#import "React/RCTViewManager.h" -#else -#import "RCTViewManager.h" -#endif - -#import -#import -#import -#import -#import "RNJWPlayerViewController.h" - -@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 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; -- (GCKCastState)castState; -- (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 5b27b47c..00000000 --- a/ios/RNJWPlayer/RNJWPlayerView.m +++ /dev/null @@ -1,1836 +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"]; - NSURL* tagUrl = [NSURL URLWithString:tag]; - - 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])) { - NSURL* adVmapUrl = [NSURL URLWithString:adVmap]; - [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* advertising; - JWAdsAdvertisingConfigBuilder* adConfigBuilder = [[JWAdsAdvertisingConfigBuilder alloc] init]; - - id adClient = ads[@"adClient"]; - if ((adClient != nil) && (adClient != (id)[NSNull null])) { - int clientType = (int)[RCTConvert JWAdClient:adClient]; - JWAdClient jwAdClient; - switch (clientType) { - case 0: - jwAdClient = JWAdClientJWPlayer; - break; - case 1: - // JWImaAdvertisingConfigBuilder - jwAdClient = JWAdClientGoogleIMA; - break; - case 2: - // JWImaDaiAdvertisingConfigBuilder - jwAdClient = JWAdClientGoogleIMADAI; - break; - case 3: - jwAdClient = JWAdClientUnknown; - break; - - default: - jwAdClient = JWAdClientUnknown; - break; - } - } else { - - } - - // [adConfigBuilder adRules:(JWAdRules * _Nonnull)]; - - 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"]; - NSURL* tagUrl = [NSURL URLWithString:tag]; - - JWAdBreakBuilder* adBreakBuilder = [[JWAdBreakBuilder alloc] init]; - JWAdOffset* offset = [JWAdOffset fromString:offsetString]; - - [adBreakBuilder offset:offset]; - [adBreakBuilder tags:@[tagUrl]]; - - JWAdBreak *adBreak = [adBreakBuilder buildAndReturnError:&error]; - - [scheduleArray addObject:adBreak]; - } - - if (scheduleArray.count > 0) { - [adConfigBuilder schedule:scheduleArray]; - } - } - } - - id tag = ads[@"tag"]; - if (tag != nil && (tag != (id)[NSNull null])) { - NSURL* tagUrl = [NSURL URLWithString:tag]; - [adConfigBuilder tag:tagUrl]; - } - - id adVmap = ads[@"adVmap"]; - if (adVmap != nil && (adVmap != (id)[NSNull null])) { - NSURL* adVmapUrl = [NSURL URLWithString:adVmap]; - [adConfigBuilder vmapURL:adVmapUrl]; - } - - id openBrowserOnAdClick = ads[@"openBrowserOnAdClick"]; - if (openBrowserOnAdClick != nil && (openBrowserOnAdClick != (id)[NSNull null])) { - [adConfigBuilder openBrowserOnAdClick:openBrowserOnAdClick]; - } - - advertising = [adConfigBuilder buildAndReturnError:&error]; - [configBuilder advertising:advertising]; - } - - 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(@{@"error": message}); - } -} - -- (void)jwplayer:(id)player failedWithSetupError:(NSUInteger)code message:(NSString *)message -{ - if (self.onSetupPlayerError) { - self.onSetupPlayerError(@{@"error": message}); - } -} - -- (void)jwplayer:(id)player encounteredWarning:(NSUInteger)code message:(NSString *)message -{ - if (self.onPlayerWarning) { - self.onPlayerWarning(@{@"warning": message}); - } -} - -- (void)jwplayer:(id _Nonnull)player encounteredAdError:(NSUInteger)code message:(NSString * _Nonnull)message { - if (self.onPlayerAdError) { - self.onPlayerAdError(@{@"error": message}); - } -} - - -- (void)jwplayer:(id _Nonnull)player encounteredAdWarning:(NSUInteger)code message:(NSString * _Nonnull)message { - if (self.onPlayerAdWarning) { - self.onPlayerAdWarning(@{@"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 JWRelatedInteraction)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 -{ - -} - -- (void)jwplayer:(id)player updatedCues:(NSArray * _Nonnull)cues -{ - -} - -#pragma mark - JWPlayer Ad Delegate - -- (void)jwplayer:(id _Nonnull)player adEvent:(JWAdEvent * _Nonnull)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]; - } -} - -- (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]; -} - -- (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..d4ed62d3 --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerView.swift @@ -0,0 +1,1822 @@ +// +// 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 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 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]) { + 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 tagURL = URL(string: tag), + 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) + } + + let ads = config["advertising"] as? [String: Any] +// if let adClient = ads?["adClient"] as? Int { +// var jwAdClient: JWAdClient = .unknown +// +// switch adClient { +// case 0: +// jwAdClient = .JWPlayer +// case 1: +// jwAdClient = .GoogleIMA +// case 2: +// jwAdClient = .GoogleIMADAI +// default: +// break +// } +// +// +// } + + let adConfigBuilder = JWAdsAdvertisingConfigBuilder() + + if let schedule = ads?["adSchedule"] as? [[String: Any]] { + _ = schedule.compactMap { item -> JWAdBreak? in + guard let offsetString = item["offset"] as? String, + let tag = item["tag"] as? String, + let tagUrl = URL(string: tag), + let offset = JWAdOffset.from(string: offsetString) else { + return nil + } + + let adBreakBuilder = JWAdBreakBuilder() + adBreakBuilder.offset(offset) + adBreakBuilder.tags([tagUrl]) + + do { + return try adBreakBuilder.build() + } catch { + // Handle the error here, log it, print it, or take appropriate action + print("Error building ad break: \(error)") + return nil + } + } + } + + if let tag = ads?["tag"] as? String { + adConfigBuilder.tag(URL(string: tag)!) + } + + if let adVmap = ads?["adVmap"] as? String { + adConfigBuilder.vmapURL(URL(string: adVmap)!) + } + + if let openBrowserOnAdClick = ads?["openBrowserOnAdClick"] as? Bool { + adConfigBuilder.openBrowserOnAdClick(openBrowserOnAdClick) + } + + let advertising = try adConfigBuilder.build() + configBuilder.advertising(advertising) + + 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: itemDict as Any, 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(itemDict) + } + + 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.onSeek(["from": oldPosition, "to": newPosition]) + } + +// func jwplayer(_ player:JWPlayer, playbackRateChangedTo oldPosition:TimeInterval, at_ newPosition:TimeInterval) { +// self.onSeek(["from": oldPosition, "to": newPosition]) +// } + + func jwplayerHasSeeked(_ player:JWPlayer) { + self.onSeeked?([:]) + } + + func jwplayer(_ player:JWPlayer, seekedFrom rate:Double, to time:TimeInterval) { + + } + + 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 b9703613..00000000 --- a/ios/RNJWPlayer/RNJWPlayerViewController.h +++ /dev/null @@ -1,31 +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 -#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 c71039b9..00000000 --- a/ios/RNJWPlayer/RNJWPlayerViewController.m +++ /dev/null @@ -1,715 +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(@{@"error": message}); - } -} - -- (void)jwplayer:(id)player failedWithSetupError:(NSUInteger)code message:(NSString *)message -{ - [super jwplayer:player failedWithSetupError:code message:message]; - - if (_parentView.onSetupPlayerError) { - _parentView.onSetupPlayerError(@{@"error": message}); - } -} - -- (void)jwplayer:(id)player encounteredWarning:(NSUInteger)code message:(NSString *)message -{ - [super jwplayer:player encounteredWarning:code message:message]; - - if (_parentView.onPlayerWarning) { - _parentView.onPlayerWarning(@{@"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(@{@"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(@{@"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 JWRelatedInteraction)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]; -} - -- (void)jwplayer:(id)player updatedCues:(NSArray * _Nonnull)cues -{ - [super jwplayer:player updatedCues:cues]; -} - -#pragma mark - JWPlayer Ad Delegate - -- (void)jwplayer:(id _Nonnull)player adEvent:(JWAdEvent * _Nonnull)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..bf1b3d88 --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerViewController.swift @@ -0,0 +1,591 @@ +// +// 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?([:]) + } + + // 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 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) { + + } + + 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: itemDict as Any, 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(itemDict) + } + + 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) + } + + 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 5891a5d8..481b02f5 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.m +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.m @@ -1,605 +1,145 @@ -#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(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(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 { - resolve([NSNumber numberWithInt:[view castState]]); - } - }]; -} - -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..4dc62692 --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.swift @@ -0,0 +1,490 @@ +import AVFoundation +import React +import JWPlayerKit + +@objc(RNJWPlayerViewManager) +class RNJWPlayerViewManager: RCTViewManager { + + override func view() -> UIView { + return RNJWPlayerView() + } + + @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)") + } + } + } + +} From fa22c85fc14afce1fb2ba697436028974b5773a9 Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Thu, 7 Dec 2023 15:51:57 +0200 Subject: [PATCH 02/11] done swift migration --- RNJWPlayer.podspec | 7 +- index.js | 2 +- ios/RNJWPlayer-Bridging-Header.h | 5 - ios/RNJWPlayer.xcodeproj/project.pbxproj | 8 +- ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift | 16 +- ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h | 2 + ios/RNJWPlayer/RNJWPlayerView.swift | 1418 ++++++++--------- ios/RNJWPlayer/RNJWPlayerViewController.swift | 264 +-- ios/RNJWPlayer/RNJWPlayerViewManager.m | 90 +- ios/RNJWPlayer/RNJWPlayerViewManager.swift | 259 +-- package.json | 4 +- 11 files changed, 1029 insertions(+), 1046 deletions(-) delete mode 100644 ios/RNJWPlayer-Bridging-Header.h create mode 100644 ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h diff --git a/RNJWPlayer.podspec b/RNJWPlayer.podspec index 12e52cfe..5fa417bd 100644 --- a/RNJWPlayer.podspec +++ b/RNJWPlayer.podspec @@ -14,8 +14,7 @@ Pod::Spec.new do |s| s.source_files = "ios/RNJWPlayer/*.{h,m,swift}" s.dependency 'JWPlayerKit', '~> 4.17.0' s.dependency 'google-cast-sdk', '~> 4.8' - s.dependency 'React' - # s.static_framework = true + s.dependency 'React-Core' s.info_plist = { 'NSBluetoothAlwaysUsageDescription' => 'We will use your Bluetooth for media casting.', 'NSBluetoothPeripheralUsageDescription' => 'We will use your Bluetooth for media casting.', @@ -23,5 +22,9 @@ Pod::Spec.new do |s| '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.static_framework = true + s.xcconfig = { + 'OTHER_LDFLAGS': '-ObjC', + } end diff --git a/index.js b/index.js index 18432ee3..4800c68a 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-Bridging-Header.h b/ios/RNJWPlayer-Bridging-Header.h deleted file mode 100644 index 78c8d306..00000000 --- a/ios/RNJWPlayer-Bridging-Header.h +++ /dev/null @@ -1,5 +0,0 @@ -#if __has_include("React/RCTViewManager.h") -#import "React/RCTViewManager.h" -#else -#import "RCTViewManager.h" -#endif diff --git a/ios/RNJWPlayer.xcodeproj/project.pbxproj b/ios/RNJWPlayer.xcodeproj/project.pbxproj index 13a3c910..ff60ba67 100644 --- a/ios/RNJWPlayer.xcodeproj/project.pbxproj +++ b/ios/RNJWPlayer.xcodeproj/project.pbxproj @@ -35,11 +35,11 @@ 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNJWPlayerViewManager.m; path = RNJWPlayer/RNJWPlayerViewManager.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; }; - 8650D6092B1CD19500DD1C7E /* RNJWPlayer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "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 = ""; }; + 86C824762B21F7C400A612CB /* RNJWPlayer-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RNJWPlayer-Bridging-Header.h"; path = "RNJWPlayer/RNJWPlayer-Bridging-Header.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -77,7 +77,7 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 8650D6092B1CD19500DD1C7E /* RNJWPlayer-Bridging-Header.h */, + 86C824762B21F7C400A612CB /* RNJWPlayer-Bridging-Header.h */, 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */, 8650D61B2B1D1E1E00DD1C7E /* RNJWPlayerViewManager.swift */, 8650D60C2B1CD21000DD1C7E /* RNJWPlayerView.swift */, @@ -254,7 +254,7 @@ ); PRODUCT_NAME = RNJWPlayer; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer/RNJWPlayer-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; @@ -283,7 +283,7 @@ ); PRODUCT_NAME = RNJWPlayer; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer/RNJWPlayer-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Release; diff --git a/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift b/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift index 5ae920b4..a1d19a7e 100644 --- a/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift +++ b/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift @@ -10,7 +10,7 @@ import React import JWPlayerKit extension RCTConvert { - + static func JWAdClient(_ value: String) -> JWAdClient { switch value { case "vast": @@ -23,7 +23,7 @@ extension RCTConvert { return .unknown } } - + static func JWInterfaceBehavior(_ value: String) -> JWInterfaceBehavior { switch value { case "normal": @@ -36,7 +36,7 @@ extension RCTConvert { return .normal } } - + static func JWCaptionEdgeStyle(_ value: String) -> JWCaptionEdgeStyle { switch value { case "none": @@ -53,7 +53,7 @@ extension RCTConvert { return .undefined } } - + static func JWPreload(_ value: String) -> JWPreload { switch value { case "auto": @@ -64,7 +64,7 @@ extension RCTConvert { return .none } } - + static func JWRelatedOnClick(_ value: String) -> JWRelatedOnClick { switch value { case "play": @@ -75,7 +75,7 @@ extension RCTConvert { return .play } } - + static func JWRelatedOnComplete(_ value: String) -> JWRelatedOnComplete { switch value { case "show": @@ -88,7 +88,7 @@ extension RCTConvert { return .show } } - + static func JWControlType(_ value: String) -> JWControlType { switch value { case "forward": @@ -115,5 +115,5 @@ extension RCTConvert { return .fullscreenButton } } - + } diff --git a/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h b/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h new file mode 100644 index 00000000..0bc5bc92 --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h @@ -0,0 +1,2 @@ +#import "React/RCTViewManager.h" +#import "RCTEventDispatcher.h" diff --git a/ios/RNJWPlayer/RNJWPlayerView.swift b/ios/RNJWPlayer/RNJWPlayerView.swift index d4ed62d3..bcf100c2 100644 --- a/ios/RNJWPlayer/RNJWPlayerView.swift +++ b/ios/RNJWPlayer/RNJWPlayerView.swift @@ -5,19 +5,19 @@ // Created by Chaim Paneth on 3/30/22. // -import UIKit import AVFoundation import AVKit -import MediaPlayer -import React import GoogleCast import JWPlayerKit +import MediaPlayer +import React +import UIKit -class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDelegate, JWCastDelegate, JWAVDelegate, JWPlayerViewDelegate, JWPlayerViewControllerDelegate, JWDRMContentKeyDataSource, AVPictureInPictureControllerDelegate { - +class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDelegate, JWCastDelegate, JWAVDelegate, JWPlayerViewDelegate, JWPlayerViewControllerDelegate, JWDRMContentKeyDataSource, AVPictureInPictureControllerDelegate { // MARK: - RNJWPlayer allocation - - var playerViewController:RNJWPlayerViewController! + + private var _eventDispatcher: RCTEventDispatcher? + var playerViewController: RNJWPlayerViewController! var playerView: JWPlayerView! var audioSession: AVAudioSession! var pipEnabled: Bool = true @@ -33,61 +33,62 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele var audioCategoryOptions: [String]! var settingConfig: Bool = false var pendingConfig: Bool = false - var currentConfig: [String : Any]! + 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 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() { + @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 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(eventDispatcher: RCTEventDispatcher!) { super.init(frame: CGRect(x: 20, y: 0, width: UIScreen.main.bounds.width - 40, height: 300)) + self._eventDispatcher = eventDispatcher } required init?(coder: NSCoder) { super.init(coder: coder) - NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.rotated), name: UIDevice.orientationDidChangeNotification, object: nil) } deinit { @@ -97,81 +98,80 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele override func removeFromSuperview() { self.startDeinitProcess() } - + func startDeinitProcess() { - NotificationCenter.default.removeObserver(self, name:UIDevice.orientationDidChangeNotification, object:nil) - + 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) + NotificationCenter.default.removeObserver(self, name: AVAudioSession.mediaServicesWereResetNotification, object: self.audioSession) + NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: self.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 (self.playerViewController != nil) || (self.playerView != nil) { + // playerViewController.player.currentItem!.removeObserver(self, forKeyPath:"playbackLikelyToKeepUp", context:nil) + if self.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) { + + 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) { + if !self.areEqual(value1, value2) { diffKeys.append(key) } } else { @@ -179,65 +179,64 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele 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 + return self.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) + return array1.count == array2.count && zip(array1, array2).allSatisfy(self.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 setConfig(_ config: [String: Any]) { // Create mutable copies of the dictionaries var configCopy = config - var currentConfigCopy = currentConfig - + var currentConfigCopy = self.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!) { + if (currentConfigCopy == nil) || !self.dictionariesAreEqual(configCopy, currentConfigCopy!) { print("There are differences other than the 'playlist' key.") - - if (currentConfigCopy != nil) { - let diffKeys = keysForDifferingValues(in: configCopy, and: currentConfigCopy!) + + if currentConfigCopy != nil { + let diffKeys = self.keysForDifferingValues(in: configCopy, and: currentConfigCopy!) print("There are differences in these keys: \(diffKeys)") } else { print("It's a new config") } - - setNewConfig(config: config) + + self.setNewConfig(config: config) } else { // Compare original dictionaries - if !dictionariesAreEqual(currentConfig, config) { + if !self.dictionariesAreEqual(self.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]) { @@ -245,106 +244,106 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele } } } - + if let playerViewController = playerViewController { playerViewController.player.loadPlaylist(items: playlistArray) } else if let playerView = playerView { playerView.player.loadPlaylist(items: playlistArray) } else { - setNewConfig(config: config) + self.setNewConfig(config: config) } } else { print("There are no differences.") } } } - - func setNewConfig(config: [String : Any]) { - currentConfig = config - - if !settingConfig { - pendingConfig = false - settingConfig = true - + + func setNewConfig(config: [String: Any]) { + self.currentConfig = config + + if !self.settingConfig { + self.pendingConfig = false + self.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 + self.backgroundAudioEnabled = bae + self.pipEnabled = pe } - if backgroundAudioEnabled || pipEnabled { + if self.backgroundAudioEnabled || self.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)) + try self.setupPlayerView(config: config, playerConfig: self.getPlayerConfiguration(config: config)) } else { - self.setupPlayerViewController(config: config, playerConfig: try self.getPlayerConfiguration(config: config)) + try self.setupPlayerViewController(config: config, playerConfig: self.getPlayerConfiguration(config: config)) } } catch { print(error) } - - processSpcUrl = config["processSpcUrl"] as? String - fairplayCertUrl = config["fairplayCertUrl"] as? String - contentUUID = config["contentUUID"] as? String + + self.processSpcUrl = config["processSpcUrl"] as? String + self.fairplayCertUrl = config["fairplayCertUrl"] as? String + self.contentUUID = config["contentUUID"] as? String } else { - pendingConfig = true + self.pendingConfig = true } } - - @objc func setControls(controls:Bool) { - self.toggleUIGroup(view: playerViewController.view, name: "JWPlayerKit.InterfaceView", ofSubview: nil, show: controls) + + @objc func setControls(_ controls: Bool) { + self.toggleUIGroup(view: self.playerViewController.view, name: "JWPlayerKit.InterfaceView", ofSubview: nil, show: controls) } - + // MARK: - RNJWPlayer styling - - func colorWithHexString(hex:String!) -> UIColor! { + + 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) { - + 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) { + let encodedUrl = URL(string: encodedString) + { itemBuilder.file(encodedUrl) } } } - + // Process sources if let itemSources = item["sources"] as? [AnyObject], !itemSources.isEmpty { var sourcesArray = [JWVideoSource]() @@ -525,8 +526,8 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele 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 isDefault = source["default"] as? Bool + { let sourceBuilder = JWVideoSourceBuilder() sourceBuilder.file(fileURL) sourceBuilder.label(label) @@ -540,32 +541,32 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele 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]() @@ -574,8 +575,8 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele 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 isDefault = trackItem["default"] as? Bool + { let trackBuilder = JWCaptionTrackBuilder() trackBuilder.file(fileURL) trackBuilder.label(label) @@ -589,7 +590,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele itemBuilder.mediaTracks(tracksArray) } - + // Process adSchedule if let adsItem = item["adSchedule"] as? [AnyObject], !adsItem.isEmpty { var adsArray = [JWAdBreak]() @@ -598,8 +599,8 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele if let offsetString = adItem["offset"] as? String, let tag = adItem["tag"] as? String, let tagURL = URL(string: tag), - let offset = JWAdOffset.from(string: offsetString) { - + let offset = JWAdOffset.from(string: offsetString) + { let adBreakBuilder = JWAdBreakBuilder() adBreakBuilder.offset(offset) adBreakBuilder.tags([tagURL]) @@ -614,23 +615,22 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele 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() - + 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) { @@ -639,66 +639,67 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele } 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) } - + let ads = config["advertising"] as? [String: Any] -// if let adClient = ads?["adClient"] as? Int { -// var jwAdClient: JWAdClient = .unknown -// -// switch adClient { -// case 0: -// jwAdClient = .JWPlayer -// case 1: -// jwAdClient = .GoogleIMA -// case 2: -// jwAdClient = .GoogleIMADAI -// default: -// break -// } -// -// -// } + // if let adClient = ads?["adClient"] as? Int { + // var jwAdClient: JWAdClient = .unknown + // + // switch adClient { + // case 0: + // jwAdClient = .JWPlayer + // case 1: + // jwAdClient = .GoogleIMA + // case 2: + // jwAdClient = .GoogleIMADAI + // default: + // break + // } + // + // + // } let adConfigBuilder = JWAdsAdvertisingConfigBuilder() - + if let schedule = ads?["adSchedule"] as? [[String: Any]] { _ = schedule.compactMap { item -> JWAdBreak? in guard let offsetString = item["offset"] as? String, let tag = item["tag"] as? String, let tagUrl = URL(string: tag), - let offset = JWAdOffset.from(string: offsetString) else { + let offset = JWAdOffset.from(string: offsetString) + else { return nil } - + let adBreakBuilder = JWAdBreakBuilder() adBreakBuilder.offset(offset) adBreakBuilder.tags([tagUrl]) - + do { return try adBreakBuilder.build() } catch { @@ -708,33 +709,33 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele } } } - + if let tag = ads?["tag"] as? String { adConfigBuilder.tag(URL(string: tag)!) } - + if let adVmap = ads?["adVmap"] as? String { adConfigBuilder.vmapURL(URL(string: adVmap)!) } - + if let openBrowserOnAdClick = ads?["openBrowserOnAdClick"] as? Bool { adConfigBuilder.openBrowserOnAdClick(openBrowserOnAdClick) } - + let advertising = try adConfigBuilder.build() configBuilder.advertising(advertising) - + 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 + if self.playerViewController == nil { + self.playerViewController = RNJWPlayerViewController() + self.playerViewController.parentView = self DispatchQueue.main.async { [self] in if self.reactViewController() != nil { @@ -745,239 +746,237 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele } } - playerViewController.view.frame = self.frame - self.addSubview(playerViewController.view) - playerViewController.setDelegates() + self.playerViewController.view.frame = self.frame + self.addSubview(self.playerViewController.view) + self.playerViewController.setDelegates() } - + if let ib = config["interfaceBehavior"] as? String { - interfaceBehavior = RCTConvert.JWInterfaceBehavior(ib) + self.interfaceBehavior = RCTConvert.JWInterfaceBehavior(ib) } - + if let interfaceFadeDelay = config["interfaceFadeDelay"] as? NSNumber { - playerViewController.interfaceFadeDelay = interfaceFadeDelay.doubleValue + self.playerViewController.interfaceFadeDelay = interfaceFadeDelay.doubleValue } - + if let forceFullScreenOnLandscape = config["fullScreenOnLandscape"] as? Bool { - playerViewController.forceFullScreenOnLandscape = forceFullScreenOnLandscape + self.playerViewController.forceFullScreenOnLandscape = forceFullScreenOnLandscape } - + if let forceLandscapeOnFullScreen = config["landscapeOnFullScreen"] as? Bool { - playerViewController.forceLandscapeOnFullScreen = forceLandscapeOnFullScreen + self.playerViewController.forceLandscapeOnFullScreen = forceLandscapeOnFullScreen } - + if let enableLockScreenControls = config["enableLockScreenControls"] as? Bool { - playerViewController.enableLockScreenControls = enableLockScreenControls && backgroundAudioEnabled + self.playerViewController.enableLockScreenControls = enableLockScreenControls && self.backgroundAudioEnabled } - + if let allowsPictureInPicturePlayback = config["allowsPictureInPicturePlayback"] as? Bool { - playerViewController.allowsPictureInPicturePlayback = allowsPictureInPicturePlayback + self.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() + self.playerViewController.nextUpStyle = try nextUpBuilder.build() } catch { print(error) } } - - // playerViewController.adInterfaceStyle - // playerViewController.logo - // playerView.videoGravity = 0; - // playerView.captionStyle - + + // playerViewController.adInterfaceStyle + // playerViewController.logo + // playerView.videoGravity = 0; + // playerView.captionStyle + if let offlineMsg = config["offlineMessage"] as? String { - playerViewController.offlineMessage = offlineMsg + self.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 + let image = UIImage(data: imageData) + { + self.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 - + if self.playerViewController != nil { + self.playerViewController.player.pause() // hack for stop not always stopping on unmount + self.playerViewController.player.stop() + self.playerViewController.enableLockScreenControls = false + // hack for stop not always stopping on unmount - let configBuilder:JWPlayerConfigurationBuilder! = JWPlayerConfigurationBuilder() + let configBuilder: JWPlayerConfigurationBuilder! = JWPlayerConfigurationBuilder() configBuilder.playlist(items: []) do { let configuration: JWPlayerConfiguration = try configBuilder.build() - playerViewController.player.configurePlayer(with: configuration) + self.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 + self.playerViewController.parentView = nil + self.playerViewController.setVisibility(.hidden, for: [.pictureInPictureButton]) + self.playerViewController.view.removeFromSuperview() + self.playerViewController.removeFromParent() + self.playerViewController.willMove(toParent: nil) + self.playerViewController.removeDelegates() + self.playerViewController = nil } } - + func presentPlayerViewController(configuration: JWPlayerConfiguration!) { if configuration != nil { - playerViewController.player.configurePlayer(with: configuration) - if (interfaceBehavior != nil) { - playerViewController.interfaceBehavior = interfaceBehavior + self.playerViewController.player.configurePlayer(with: configuration) + if self.interfaceBehavior != nil { + self.playerViewController.interfaceBehavior = self.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 + self.playerView = JWPlayerView(frame: self.superview!.frame) + + self.playerView.delegate = self + self.playerView.player.delegate = self + self.playerView.player.playbackStateDelegate = self + self.playerView.player.adDelegate = self + self.playerView.player.avDelegate = self + self.playerView.player.contentKeyDataSource = self + + self.playerView.player.configurePlayer(with: playerConfig) + + if self.pipEnabled { + let pipController: AVPictureInPictureController! = self.playerView.pictureInPictureController pipController.delegate = self - - pipController.addObserver(self, forKeyPath:"isPictureInPicturePossible", options:[.new, .initial], context:nil) + + 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() + self.playerView.player.play() } - + // Time observers - weak var weakSelf:RNJWPlayerView! = self - playerView.player.adTimeObserver = { (time:JWTimeData!) in + weak var weakSelf: RNJWPlayerView! = self + self.playerView.player.adTimeObserver = { (time: JWTimeData!) in weakSelf.onAdTime?(["position": time.position, "duration": time.duration]) } - - playerView.player.mediaTimeObserver = { (time:JWTimeData!) in + + self.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 + if self.playerView != nil { + self.playerView.player.stop() + self.playerView.removeFromSuperview() + self.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) + self.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!) + + 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!) + } + + if _controls.count > 0 { + self.playerViewController.setVisibility(isVisible ? .visible : .hidden, for: _controls!) } } - + // MARK: - JWPlayer Delegate - - func jwplayerIsReady(_ player:JWPlayer) { - settingConfig = false + + func jwplayerIsReady(_ player: JWPlayer) { + self.settingConfig = false self.onPlayerReady?([:]) - - if pendingConfig && currentConfig != nil { - self.setConfig(currentConfig) + + if self.pendingConfig && self.currentConfig != nil { + self.setConfig(self.currentConfig) } } - - func jwplayer(_ player:JWPlayer, failedWithError code:UInt, message:String) { + + func jwplayer(_ player: JWPlayer, failedWithError code: UInt, message: String) { self.onPlayerError?(["error": message]) } - - func jwplayer(_ player:JWPlayer, failedWithSetupError code:UInt, message:String) { + + func jwplayer(_ player: JWPlayer, failedWithSetupError code: UInt, message: String) { self.onSetupPlayerError?(["error": message]) } - - func jwplayer(_ player:JWPlayer, encounteredWarning code:UInt, message:String) { + + func jwplayer(_ player: JWPlayer, encounteredWarning code: UInt, message: String) { self.onPlayerWarning?(["warning": message]) } - - func jwplayer(_ player:JWPlayer, encounteredAdError code:UInt, message:String) { + + func jwplayer(_ player: JWPlayer, encounteredAdError code: UInt, message: String) { self.onPlayerAdError?(["error": message]) } - - - func jwplayer(_ player:JWPlayer, encounteredAdWarning code:UInt, message:String) { + + 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) { + + 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]) @@ -985,25 +984,25 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele print("Error converting dictionary to JSON data: \(error)") } } - + // MARK: - JWPlayer View Controller Delegate - - func playerViewController(_ controller:JWPlayerViewController, sizeChangedFrom oldSize:CGSize, to newSize:CGSize) { + + 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]) @@ -1011,68 +1010,62 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele print("Error converting dictionary to JSON data: \(error)") } } - - func playerViewController(_ controller:JWPlayerViewController, screenTappedAt position:CGPoint) { + + func playerViewController(_ controller: JWPlayerViewController, screenTappedAt position: CGPoint) { self.onScreenTapped?(["x": position.x, "y": position.y]) } - - func playerViewController(_ controller:JWPlayerViewController, controlBarVisibilityChanged isVisible:Bool, frame:CGRect) { + + func playerViewController(_ controller: JWPlayerViewController, controlBarVisibilityChanged isVisible: Bool, frame: CGRect) { self.onControlBarVisible?(["visible": isVisible]) } - - func playerViewControllerWillGoFullScreen(_ controller:JWPlayerViewController) -> JWFullScreenViewController? { + + func playerViewControllerWillGoFullScreen(_ controller: JWPlayerViewController) -> JWFullScreenViewController? { self.onFullScreenRequested?([:]) return nil } - - func playerViewControllerDidGoFullScreen(_ controller:JWPlayerViewController) { + + func playerViewControllerDidGoFullScreen(_ controller: JWPlayerViewController) { self.onFullScreen?([:]) } - - func playerViewControllerWillDismissFullScreen(_ controller:JWPlayerViewController) { + + func playerViewControllerWillDismissFullScreen(_ controller: JWPlayerViewController) { self.onFullScreenExitRequested?([:]) } - - func playerViewControllerDidDismissFullScreen(_ controller:JWPlayerViewController) { + + 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) { - - } + 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) { + + func onAdTimeEvent(time: JWTimeData) { self.onAdTime?(["position": time.position, "duration": time.duration]) } - - func onMediaTimeEvent(time:JWTimeData) { + + 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) + 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 + let task = URLSession.shared.dataTask(with: request) { data, _, error in if let error = error { print("DRM cert request error - \(error.localizedDescription)") } @@ -1080,153 +1073,139 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele } 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) { + + 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() -// } -// } -// + + 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) { - - } - + 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) { + + func jwplayerContentIsBuffering(_ player: JWPlayer) { self.onBuffer?([:]) } - - func jwplayer(_ player:JWPlayer, isBufferingWithReason reason:JWBufferReason) { + + func jwplayer(_ player: JWPlayer, isBufferingWithReason reason: JWBufferReason) { self.onBuffer?([:]) } - - func jwplayer(_ player:JWPlayer, updatedBuffer percent:Double, position time:JWTimeData) { + + func jwplayer(_ player: JWPlayer, updatedBuffer percent: Double, position time: JWTimeData) { self.onUpdateBuffer?(["percent": percent, "position": time]) } - - func jwplayer(_ player:JWPlayer, didFinishLoadingWithTime loadTime:TimeInterval) { + + func jwplayer(_ player: JWPlayer, didFinishLoadingWithTime loadTime: TimeInterval) { self.onLoaded?([:]) } - - func jwplayer(_ player:JWPlayer, isAttemptingToPlay playlistItem:JWPlayerItem, reason:JWPlayReason) { + + func jwplayer(_ player: JWPlayer, isAttemptingToPlay playlistItem: JWPlayerItem, reason: JWPlayReason) { self.onAttemptPlay?([:]) } - - func jwplayer(_ player:JWPlayer, isPlayingWithReason reason:JWPlayReason) { + + func jwplayer(_ player: JWPlayer, isPlayingWithReason reason: JWPlayReason) { self.onPlay?([:]) - - userPaused = false - wasInterrupted = false + + self.userPaused = false + self.wasInterrupted = false } - - func jwplayer(_ player:JWPlayer, willPlayWithReason reason:JWPlayReason) { + + func jwplayer(_ player: JWPlayer, willPlayWithReason reason: JWPlayReason) { self.onBeforePlay?([:]) } - - func jwplayer(_ player:JWPlayer, didPauseWithReason reason:JWPauseReason) { + + func jwplayer(_ player: JWPlayer, didPauseWithReason reason: JWPauseReason) { self.onPause?([:]) - - if !wasInterrupted { - userPaused = true + + if !self.wasInterrupted { + self.userPaused = true } } - - func jwplayer(_ player:JWPlayer, didBecomeIdleWithReason reason:JWIdleReason) { + + func jwplayer(_ player: JWPlayer, didBecomeIdleWithReason reason: JWIdleReason) { self.onIdle?([:]) } - - func jwplayer(_ player:JWPlayer, isVisible:Bool) { + + func jwplayer(_ player: JWPlayer, isVisible: Bool) { self.onVisible?(["visible": isVisible]) } - - func jwplayerContentWillComplete(_ player:JWPlayer) { + + func jwplayerContentWillComplete(_ player: JWPlayer) { self.onBeforeComplete?([:]) } - - func jwplayerContentDidComplete(_ player:JWPlayer) { + + func jwplayerContentDidComplete(_ player: JWPlayer) { self.onComplete?([:]) } - - func jwplayer(_ player:JWPlayer, didLoadPlaylistItem item:JWPlayerItem, at index:UInt) { + + 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 @@ -1236,7 +1215,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele } var trackDict: [String: Any] = [:] - + if let tracks = item.mediaTracks { for track in tracks { trackDict["file"] = track.file?.absoluteString @@ -1244,7 +1223,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele trackDict["default"] = track.defaultTrack } } - + let itemDict: [String: Any] = [ "file": file ?? "", "mediaId": item.mediaId as Any, @@ -1258,36 +1237,36 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele "adSchedule": schedDict, "tracks": trackDict ] - + do { - let data:Data! = try JSONSerialization.data(withJSONObject: itemDict as Any, options:.prettyPrinted) - - self.onPlaylistItem?(["playlistItem": String(data:data, encoding:String.Encoding.utf8) as Any, "index": index]) + let data: Data! = try JSONSerialization.data(withJSONObject: itemDict as Any, 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) + + // 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! - + + 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 { @@ -1296,9 +1275,9 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele schedDict["type"] = sched.type } } - + var trackDict: [String: Any] = [:] - + if let mediaTracks = item?.mediaTracks { for track in mediaTracks { trackDict["file"] = track.file?.absoluteString @@ -1306,7 +1285,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele trackDict["default"] = track.defaultTrack } } - + let itemDict: [String: Any] = [ "file": file ?? "", "mediaId": item?.mediaId ?? "", @@ -1320,315 +1299,295 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele "adSchedule": trackDict, "tracks": schedDict ] - + playlistArray.add(itemDict) - } - + } + 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]) + 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) { + + func jwplayerPlaylistHasCompleted(_ player: JWPlayer) { self.onPlaylistComplete?([:]) } - - func jwplayer(_ player:JWPlayer, usesMediaType type:JWMediaType) { - - } + + func jwplayer(_ player: JWPlayer, usesMediaType type: JWMediaType) {} func jwplayer(_ player: JWPlayer, playbackRateChangedTo rate: Double, at time: TimeInterval) { -// self.onSeek(["from": oldPosition, "to": newPosition]) + // self.onSeek(["from": oldPosition, "to": newPosition]) } - -// func jwplayer(_ player:JWPlayer, playbackRateChangedTo oldPosition:TimeInterval, at_ newPosition:TimeInterval) { -// self.onSeek(["from": oldPosition, "to": newPosition]) -// } - - func jwplayerHasSeeked(_ player:JWPlayer) { + + // func jwplayer(_ player:JWPlayer, playbackRateChangedTo oldPosition:TimeInterval, at_ newPosition:TimeInterval) { + // self.onSeek(["from": oldPosition, "to": newPosition]) + // } + + func jwplayerHasSeeked(_ player: JWPlayer) { self.onSeeked?([:]) } - - func jwplayer(_ player:JWPlayer, seekedFrom rate:Double, to time:TimeInterval) { - - } - - func jwplayer(_ player:JWPlayer, updatedCues cues:[JWCue]) { - - } - + + func jwplayer(_ player: JWPlayer, seekedFrom rate: Double, to time: TimeInterval) {} + + func jwplayer(_ player: JWPlayer, updatedCues cues: [JWCue]) {} + // MARK: - JWPlayer Ad Delegate - - func jwplayer(_ player:JWPlayer, adEvent event:JWAdEvent) { + + func jwplayer(_ player: JWPlayer, adEvent event: JWAdEvent) { self.onAdEvent?(["client": event.client, "type": event.type]) } - -// pragma Mark - Casting methods - + + // 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() + if (self.playerView != nil) && self.playerView.player as! Bool && (self.castController == nil) { + self.castController = JWCastController(player: self.playerView.player) + self.castController.delegate = self + } + + self.scanForDevices() } - + func scanForDevices() { - if castController != nil { - castController.startDiscovery() - } + if self.castController != nil { + self.castController.startDiscovery() + } } - + func stopScanForDevices() { - if castController != nil { - castController.stopDiscovery() - } + if self.castController != nil { + self.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) { + + 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 + return self.castController.availableDevices } - + func connectedDevice() -> JWCastingDevice! { - return castController.connectedDevice + return self.castController.connectedDevice } - - func connectToDevice(device:JWCastingDevice!) { - return castController.connectToDevice(device) + + func connectToDevice(device: JWCastingDevice!) { + return self.castController.connectToDevice(device) } - + func cast() { - return castController.cast() + return self.castController.cast() } - + func stopCasting() { - return castController.stopCasting() + return self.castController.stopCasting() } - + // MARK: - JWPlayer Cast Delegate func castController(_ controller: JWCastController, castingBeganWithDevice device: JWCastingDevice) { self.onCasting?([:]) } - func castController(_ controller:JWCastController, castingEndedWithError error: Error?) { + func castController(_ controller: JWCastController, castingEndedWithError error: Error?) { self.onCastingEnded?(["error": error as Any]) } - - func castController(_ controller:JWCastController, castingFailedWithError error: Error) { + + 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) - + + 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]) + 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) { + func castController(_ controller: JWCastController, connectionFailedWithError error: Error) { self.onConnectionFailed?(["error": error as Any]) } - - func castController(_ controller: JWCastController, connectionRecoveredWithDevice device:JWCastingDevice) { + + 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]) { + + 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]) + 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)?) { + func castController(_ controller: JWCastController, disconnectedWithError error: Error?) { self.onDisconnectedFromCastingDevice?(["error": error as Any]) } - + // MARK: - JWPlayer AV Delegate - - func jwplayer(_ player:JWPlayer, audioTracksUpdated levels:[JWMediaSelectionOption]) { + + 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, audioTrackChanged currentLevel: Int) {} - func jwplayer(_ player:JWPlayer, qualityLevelChanged 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]) {} - 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?) { + + func initAudioSession(category: String?, categoryOptions: [String]?, mode: String?) { self.setObservers() - - var somethingChanged:Bool = false - - if !(category == audioCategory) || (categoryOptions != nil && !categoryOptions!.elementsEqual(audioCategoryOptions)) { + + var somethingChanged = false + + if !(category == self.audioCategory) || (categoryOptions != nil && !categoryOptions!.elementsEqual(self.audioCategoryOptions)) { somethingChanged = true - audioCategory = category - audioCategoryOptions = categoryOptions - self.setCategory(categoryName: category, categoryOptions:categoryOptions) + self.audioCategory = category + self.audioCategoryOptions = categoryOptions + self.setCategory(categoryName: category, categoryOptions: categoryOptions) } - - if !(mode == audioMode) { + + if !(mode == self.audioMode) { somethingChanged = true - audioMode = mode + self.audioMode = mode self.setMode(modeName: mode) } - + if somethingChanged { do { - try audioSession.setActive(true) + try self.audioSession.setActive(true) print("setActive - success") } catch { print("setActive - error: @%@", error) } } } - + func deinitAudioSession() { do { - try audioSession?.setActive(false, options: .notifyOthersOnDeactivation) + try self.audioSession?.setActive(false, options: .notifyOthersOnDeactivation) print("setUnactive - success") } catch { print("setUnactive - error: @%@", error) } - audioSession = nil + self.audioSession = nil MPNowPlayingInfoCenter.default().nowPlayingInfo = [:] UIApplication.shared.endReceivingRemoteControlEvents() } - + func setObservers() { - if audioSession == nil { - audioSession = AVAudioSession.sharedInstance() - + if self.audioSession == nil { + self.audioSession = AVAudioSession.sharedInstance() + NotificationCenter.default.addObserver(self, - selector:#selector(handleMediaServicesReset), - name:AVAudioSession.mediaServicesWereResetNotification, - object:audioSession) - + selector: #selector(self.handleMediaServicesReset), + name: AVAudioSession.mediaServicesWereResetNotification, + object: self.audioSession) + NotificationCenter.default.addObserver(self, - selector: #selector(audioSessionInterrupted(_:)), + selector: #selector(self.audioSessionInterrupted(_:)), name: AVAudioSession.interruptionNotification, - object: audioSession) - + object: self.audioSession) + NotificationCenter.default.addObserver(self, - selector:#selector(applicationWillResignActive(_:)), - name:UIApplication.willResignActiveNotification, object:nil) - + selector: #selector(self.applicationWillResignActive(_:)), + name: UIApplication.willResignActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, - selector:#selector(applicationDidEnterBackground(_:)), - name:UIApplication.didEnterBackgroundNotification, - object:nil) - + selector: #selector(self.applicationDidEnterBackground(_:)), + name: UIApplication.didEnterBackgroundNotification, + object: nil) + NotificationCenter.default.addObserver(self, - selector:#selector(applicationWillEnterForeground(_:)), - name:UIApplication.willEnterForegroundNotification, object:nil) - + selector: #selector(self.applicationWillEnterForeground(_:)), + name: UIApplication.willEnterForegroundNotification, object: nil) + NotificationCenter.default.addObserver(self, - selector: #selector(audioRouteChanged(_:)), + selector: #selector(self.audioRouteChanged(_:)), name: AVAudioSession.routeChangeNotification, object: nil) } } - - func setCategory(categoryName:String!, categoryOptions:[String]!) { - if (audioSession == nil) { - audioSession = AVAudioSession.sharedInstance() + + func setCategory(categoryName: String!, categoryOptions: [String]!) { + if self.audioSession == nil { + self.audioSession = AVAudioSession.sharedInstance() } - - var category:AVAudioSession.Category! = nil + + var category: AVAudioSession.Category! = nil if categoryName.isEqual("Ambient") { category = .ambient } else if categoryName.isEqual("SoloAmbient") { @@ -1644,7 +1603,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele } else { category = .playback } - + var options: AVAudioSession.CategoryOptions = [] if categoryOptions.contains("MixWithOthers") { options.insert(.mixWithOthers) @@ -1673,20 +1632,20 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele } do { - try audioSession.setCategory(category, options: options) + try self.audioSession.setCategory(category, options: options) print("setCategory - success") } catch { print("setCategory - error: @%@", error) } } - - func setMode(modeName:String!) { - if (audioSession == nil) { - audioSession = AVAudioSession.sharedInstance() + + func setMode(modeName: String!) { + if self.audioSession == nil { + self.audioSession = AVAudioSession.sharedInstance() } - - var mode:AVAudioSession.Mode! = nil - + + var mode: AVAudioSession.Mode! = nil + if modeName.isEqual("Default") { mode = .default } else if modeName.isEqual("VoiceChat") { @@ -1710,10 +1669,10 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele // Fallback on earlier versions } } - - if (mode != nil) { + + if mode != nil { do { - try audioSession.setMode(mode) + try self.audioSession.setMode(mode) print("setMode - success") } catch { print("setMode - error: @%@", error) @@ -1723,46 +1682,46 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele @objc func audioSessionInterrupted(_ notification: Notification) { guard let userInfo = notification.userInfo, - let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, - let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { - return + 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() + self.wasInterrupted = true + + if self.playerView != nil { + self.playerView.player.pause() + } else if self.playerViewController != nil { + self.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 { + if options.contains(.shouldResume) && !self.userPaused && self.backgroundAudioEnabled { // Interruption ended. Playback should resume. DispatchQueue.main.async { [self] in - if (playerView != nil) { - playerView.player.play() - } else if (playerViewController != nil) { - playerViewController.player.play() + if self.playerView != nil { + self.playerView.player.play() + } else if self.playerViewController != nil { + self.playerViewController.player.play() } print("handleInterruption :- Play") } - } - else { + } 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() + self.wasInterrupted = true + + if self.playerView != nil { + self.playerView.player.pause() + } else if self.playerViewController != nil { + self.playerViewController.player.pause() } print("handleInterruption :- Pause") } @@ -1771,52 +1730,49 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele 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() + if !self.userPaused && self.backgroundAudioEnabled { + if (self.playerView != nil) && self.playerView.player.getState() == .playing { + self.playerView.player.play() + } else if (self.playerViewController != nil) && self.playerViewController.player.getState() == .playing { + self.playerViewController.player.play() } } } - + // Background - @objc func applicationDidEnterBackground(_ notification: Notification) { - - } - + @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() + if !self.userPaused && self.backgroundAudioEnabled { + if (self.playerView != nil) && self.playerView.player.getState() == .playing { + self.playerView.player.play() + } else if (self.playerViewController != nil) && self.playerViewController.player.getState() == .playing { + self.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() + if self.playerView != nil { + self.playerView.player.pause() + } else if self.playerViewController != nil { + self.playerViewController.player.pause() } } } - } diff --git a/ios/RNJWPlayer/RNJWPlayerViewController.swift b/ios/RNJWPlayer/RNJWPlayerViewController.swift index bf1b3d88..add9a68b 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewController.swift +++ b/ios/RNJWPlayer/RNJWPlayerViewController.swift @@ -16,85 +16,85 @@ 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.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.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]) @@ -102,25 +102,25 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD 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]) @@ -128,51 +128,51 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD 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?([:]) } - + // 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 @@ -189,7 +189,7 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD } func playerViewController(_ controller:JWPlayerViewController, relatedMenuClosedWithMethod method:JWRelatedInteraction) { - + } func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedMenuOpenedWithItems items: [JWPlayerKit.JWPlayerItem], withMethod method: JWPlayerKit.JWRelatedInteraction) { @@ -199,149 +199,149 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedItemBeganPlaying item: JWPlayerKit.JWPlayerItem, atIndex index: Int, withMethod method: JWPlayerKit.JWRelatedMethod) { } - + 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() -// } + // 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 @@ -351,7 +351,7 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD } var trackDict: [String: Any] = [:] - + if let tracks = item.mediaTracks { for track in tracks { trackDict["file"] = track.file?.absoluteString @@ -359,7 +359,7 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD trackDict["default"] = track.defaultTrack } } - + let itemDict: [String: Any] = [ "file": file ?? "", "mediaId": item.mediaId as Any, @@ -373,38 +373,38 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD "adSchedule": schedDict, "tracks": trackDict ] - + do { let data:Data! = try JSONSerialization.data(withJSONObject: itemDict as Any, 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) + + // 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 { @@ -413,9 +413,9 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD schedDict["type"] = sched.type } } - + var trackDict: [String: Any] = [:] - + if let mediaTracks = item?.mediaTracks { for track in mediaTracks { trackDict["file"] = track.file?.absoluteString @@ -423,7 +423,7 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD trackDict["default"] = track.defaultTrack } } - + let itemDict: [String: Any] = [ "file": file ?? "", "mediaId": item?.mediaId ?? "", @@ -437,95 +437,95 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD "adSchedule": trackDict, "tracks": schedDict ] - + playlistArray.add(itemDict) - } - + } + 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) } - + 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?([:]) @@ -533,58 +533,58 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD 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 481b02f5..e615f197 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.m +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.m @@ -1,13 +1,9 @@ -#if __has_include("React/RCTViewManager.h") +#import #import "React/RCTViewManager.h" -#else -#import "RCTViewManager.h" -#endif +#import "RCTUIManager.h" #import -#import "RCTUIManager.h" - @interface RCT_EXTERN_MODULE(RNJWPlayerViewManager, RCTViewManager) /* player state events */ @@ -70,76 +66,76 @@ @interface RCT_EXTERN_MODULE(RNJWPlayerViewManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(config, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); -RCT_EXTERN_METHOD(state: (nonnull NSNumber*)reactTag - stateWithResolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(state:(nonnull NSNumber*)reactTag + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(pause: (nonnull NSNumber*)reactTag) +RCT_EXTERN_METHOD(pause:(nonnull NSNumber*)reactTag) -RCT_EXTERN_METHOD(play: (nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(play:(nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(stop: (nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(stop:(nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(position: (nonnull NSNumber *)reactTag - positionResolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(position:(nonnull NSNumber *)reactTag + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(toggleSpeed: (nonnull NSNumber*)reactTag) +RCT_EXTERN_METHOD(toggleSpeed:(nonnull NSNumber*)reactTag) -RCT_EXTERN_METHOD(setSpeed: (nonnull NSNumber*)reactTag: (double)speed) +RCT_EXTERN_METHOD(setSpeed:(nonnull NSNumber*)reactTag speed:(double)speed) -RCT_EXTERN_METHOD(setPlaylistIndex: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) +RCT_EXTERN_METHOD(setPlaylistIndex:(nonnull NSNumber *)reactTag index:(nonnull NSNumber *)index) -RCT_EXTERN_METHOD(seekTo: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)time) +RCT_EXTERN_METHOD(seekTo:(nonnull NSNumber *)reactTag time:(nonnull NSNumber *)time) -RCT_EXTERN_METHOD(setVolume: (nonnull NSNumber *)reactTag :(nonnull NSNumber *)volume) +RCT_EXTERN_METHOD(setVolume:(nonnull NSNumber *)reactTag volume:(nonnull NSNumber *)volume) -RCT_EXTERN_METHOD(togglePIP: (nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(togglePIP:(nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(setUpCastController: (nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(setUpCastController:(nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(presentCastDialog: (nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(presentCastDialog:(nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(connectedDevice: (nonnull NSNumber *)reactTag - resolve:(RCTPromiseResolveBlock)resolve - rejecte:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(connectedDevice:(nonnull NSNumber *)reactTag + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(availableDevices: (nonnull NSNumber *)reactTag - solve:(RCTPromiseResolveBlock)resolve - eject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(availableDevices:(nonnull NSNumber *)reactTag + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(castState: (nonnull NSNumber *)reactTag - solver:(RCTPromiseResolveBlock)resolve - ejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(castState:(nonnull NSNumber *)reactTag + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getAudioTracks: (nonnull NSNumber *)reactTag - resolve:(RCTPromiseResolveBlock)resolve - eject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getAudioTracks:(nonnull NSNumber *)reactTag + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getCurrentAudioTrack: (nonnull NSNumber *)reactTag - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getCurrentAudioTrack:(nonnull NSNumber *)reactTag + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(setCurrentAudioTrack: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) +RCT_EXTERN_METHOD(setCurrentAudioTrack:(nonnull NSNumber *)reactTag index:(nonnull NSNumber *)index) -RCT_EXTERN_METHOD(setControls: (nonnull NSNumber *)reactTag: (BOOL)show) +RCT_EXTERN_METHOD(setControls:(nonnull NSNumber *)reactTag show:(BOOL)show) -RCT_EXTERN_METHOD(setVisibility: (nonnull NSNumber *)reactTag: (BOOL)visibilty: (nonnull NSArray *)controls) +RCT_EXTERN_METHOD(setVisibility:(nonnull NSNumber *)reactTag visibilty:(BOOL)visibilty controls:(nonnull NSArray *)controls) -RCT_EXTERN_METHOD(setLockScreenControls: (nonnull NSNumber *)reactTag: (BOOL)show) +RCT_EXTERN_METHOD(setLockScreenControls:(nonnull NSNumber *)reactTag show:(BOOL)show) -RCT_EXTERN_METHOD(setCurrentCaptions: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) +RCT_EXTERN_METHOD(setCurrentCaptions:(nonnull NSNumber *)reactTag index:(nonnull NSNumber *)index) -RCT_EXTERN_METHOD(setCurrentCaptions: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) +RCT_EXTERN_METHOD(setCurrentCaptions:(nonnull NSNumber *)reactTag index:(nonnull NSNumber *)index) -RCT_EXTERN_METHOD(setLicenseKey: (nonnull NSNumber *)reactTag: (nonnull NSString *)license) +RCT_EXTERN_METHOD(setLicenseKey:(nonnull NSNumber *)reactTag license:(nonnull NSString *)license) RCT_EXTERN_METHOD(quite) RCT_EXTERN_METHOD(reset) -RCT_EXTERN_METHOD(loadPlaylist: (nonnull NSNumber *)reactTag: (nonnull NSArray *)playlist) +RCT_EXTERN_METHOD(loadPlaylist:(nonnull NSNumber *)reactTag playlist:(nonnull NSArray *)playlist) -RCT_EXTERN_METHOD(setFullscreen: (nonnull NSNumber *)reactTag: (BOOL)fullscreen) +RCT_EXTERN_METHOD(setFullscreen:(nonnull NSNumber *)reactTag fullscreen:(BOOL)fullscreen) @end diff --git a/ios/RNJWPlayer/RNJWPlayerViewManager.swift b/ios/RNJWPlayer/RNJWPlayerViewManager.swift index 4dc62692..2013d3ce 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.swift +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.swift @@ -6,17 +6,22 @@ import JWPlayerKit class RNJWPlayerViewManager: RCTViewManager { override func view() -> UIView { - return RNJWPlayerView() + return RNJWPlayerView(eventDispatcher: bridge.eventDispatcher() as? RCTEventDispatcher) } - - @objc func state(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - self.bridge.uiManager.addUIBlock { (_, viewRegistry) in + + func methodQueue() -> DispatchQueue { + return bridge.uiManager.methodQueue + } + + @objc(state:resolver:rejecter:) + func state(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + 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 { @@ -28,13 +33,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc func pause(_ reactTag: NSNumber) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @objc(pause:) + func pause(reactTag: NSNumber) { + 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() @@ -44,13 +50,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc func play(_ reactTag: NSNumber) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @objc(play:) + func play(reactTag: NSNumber) { + 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 { @@ -58,14 +65,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func stop(_ reactTag: NSNumber) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(stop:) + func stop(reactTag: NSNumber) { + 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() @@ -74,15 +82,16 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func position(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(position:resolver:rejecter:) + func position(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + 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 { @@ -93,14 +102,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func toggleSpeed(_ reactTag: NSNumber) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(toggleSpeed:) + func toggleSpeed(reactTag: NSNumber) { + 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 @@ -116,14 +126,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func setSpeed(_ reactTag: NSNumber, speed: Double) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(setSpeed:speed:) + func setSpeed(reactTag: NSNumber, speed: Double) { + 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 { @@ -131,14 +142,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func setPlaylistIndex(_ reactTag: NSNumber, index: NSNumber) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(setPlaylistIndex:index:) + func setPlaylistIndex(reactTag: NSNumber, index: NSNumber) { + 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 { @@ -146,14 +158,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func seekTo(_ reactTag: NSNumber, time: NSNumber) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(seekTo:time:) + func seekTo(reactTag: NSNumber, time: NSNumber) { + 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 { @@ -162,13 +175,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc func setVolume(_ reactTag: NSNumber, volume: Double) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @objc(setVolume:volume:) + func setVolume(reactTag: NSNumber, volume: Double) { + 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 { @@ -176,15 +190,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - - @objc func togglePIP(_ reactTag: NSNumber) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(togglePIP:) + func togglePIP(reactTag: NSNumber) { + 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() @@ -194,42 +208,45 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func setUpCastController(_ reactTag: NSNumber) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(setUpCastController:) + func setUpCastController(reactTag: NSNumber) { + 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 + + @objc(presentCastDialog:) + func presentCastDialog(reactTag: NSNumber) { + 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 + + @objc(connectedDevice:resolver:rejecter:) + func connectedDevice(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + 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)) @@ -242,25 +259,26 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func availableDevices(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(availableDevices:resolver:rejecter:) + func availableDevices(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + 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)) @@ -273,29 +291,31 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func castState(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(castState:resolver:rejecter:) + func castState(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + 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 + + @objc(getAudioTracks:resolver:rejecter:) + func getAudioTracks(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + 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 { @@ -320,14 +340,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc func getCurrentAudioTrack(_ reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @objc(getCurrentAudioTrack:resolver:rejecter:) + func getCurrentAudioTrack(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + 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 { @@ -338,14 +359,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func setCurrentAudioTrack(_ reactTag: NSNumber, index: NSNumber) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(setCurrentAudioTrack:index:) + func setCurrentAudioTrack(reactTag: NSNumber, index: NSNumber) { + 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 { @@ -354,52 +376,56 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc func setControls(_ reactTag: NSNumber, show: Bool) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @objc(setControls:show:) + func setControls(reactTag: NSNumber, show: Bool) { + 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 + + @objc(setVisibility:visibility:controls:) + func setVisibility(reactTag: NSNumber, visibility: Bool, controls: [String]) { + 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 + + @objc(setLockScreenControls:show:) + func setLockScreenControls(reactTag: NSNumber, show: Bool) { + 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 + + @objc(setCurrentCaptions:index:) + func setCurrentCaptions(reactTag: NSNumber, index: NSNumber) { + 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 { @@ -407,20 +433,22 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func setLicenseKey(_ reactTag: NSNumber, license: String) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(setLicenseKey:license:) + func setLicenseKey(reactTag: NSNumber, license: String) { + 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 + + @objc(quite) + func quite() { + bridge.uiManager.addUIBlock { uiManager, viewRegistry in for (_, view) in viewRegistry ?? [:] { if let rnjwView = view as? RNJWPlayerView { if let playerView = rnjwView.playerView { @@ -434,9 +462,10 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func reset() { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(reset) + func reset() { + bridge.uiManager.addUIBlock { uiManager, viewRegistry in for (_, view) in viewRegistry ?? [:] { if let rnjwView = view as? RNJWPlayerView { rnjwView.startDeinitProcess() @@ -444,22 +473,23 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func loadPlaylist(_ reactTag: NSNumber, playlist: [Any]) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(loadPlaylist:playlist:) + func loadPlaylist(reactTag: NSNumber, playlist: [Any]) { + 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 { @@ -467,14 +497,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc func setFullscreen(_ reactTag: NSNumber, fullscreen: Bool) { - self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc(setFullscreen:fullscreen:) + func setFullscreen(reactTag: NSNumber, fullscreen: Bool) { + 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) @@ -486,5 +517,5 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - + } diff --git a/package.json b/package.json index 07df5415..2a623e34 100644 --- a/package.json +++ b/package.json @@ -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" } } From a8d64b8b02cce102ca8e8950d0e36def5e80d71b Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Tue, 19 Dec 2023 10:41:13 +0200 Subject: [PATCH 03/11] working example, started working with structs, added callbacks --- ios/RNJWPlayer.xcodeproj/project.pbxproj | 12 +- ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift | 16 +- ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h | 5 +- ios/RNJWPlayer/RNJWPlayerModels.swift | 149 ++ ios/RNJWPlayer/RNJWPlayerView.swift | 1568 +++++++++-------- ios/RNJWPlayer/RNJWPlayerViewController.swift | 427 ++--- ios/RNJWPlayer/RNJWPlayerViewManager.m | 91 +- ios/RNJWPlayer/RNJWPlayerViewManager.swift | 259 ++- 8 files changed, 1349 insertions(+), 1178 deletions(-) create mode 100644 ios/RNJWPlayer/RNJWPlayerModels.swift diff --git a/ios/RNJWPlayer.xcodeproj/project.pbxproj b/ios/RNJWPlayer.xcodeproj/project.pbxproj index ff60ba67..a0902218 100644 --- a/ios/RNJWPlayer.xcodeproj/project.pbxproj +++ b/ios/RNJWPlayer.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 2AA52BE726C144B200AD26AE /* RNJWPlayerViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */; }; 3BC75FCC1E43B1DB0011FBAA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC75FCB1E43B1DB0011FBAA /* UIKit.framework */; }; 3BC75FD11E43B3090011FBAA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC75FD01E43B3090011FBAA /* Foundation.framework */; }; + 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 */; }; @@ -35,11 +36,12 @@ 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNJWPlayerViewManager.m; path = RNJWPlayer/RNJWPlayerViewManager.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; }; + 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 = ""; }; - 86C824762B21F7C400A612CB /* RNJWPlayer-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RNJWPlayer-Bridging-Header.h"; path = "RNJWPlayer/RNJWPlayer-Bridging-Header.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -77,9 +79,10 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 86C824762B21F7C400A612CB /* RNJWPlayer-Bridging-Header.h */, + 86182A322B2AFA660040739A /* RNJWPlayer-Bridging-Header.h */, 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */, 8650D61B2B1D1E1E00DD1C7E /* RNJWPlayerViewManager.swift */, + 86182A302B2AFA170040739A /* RNJWPlayerModels.swift */, 8650D60C2B1CD21000DD1C7E /* RNJWPlayerView.swift */, 8650D6192B1D1E1E00DD1C7E /* RNJWPlayerViewController.swift */, 8650D61A2B1D1E1E00DD1C7E /* RCTConvert+RNJWPlayer.swift */, @@ -151,6 +154,7 @@ 8650D61E2B1D1E1E00DD1C7E /* RNJWPlayerViewController.swift in Sources */, 8650D60D2B1CD21000DD1C7E /* RNJWPlayerView.swift in Sources */, 2AA52BE726C144B200AD26AE /* RNJWPlayerViewManager.m in Sources */, + 86182A312B2AFA170040739A /* RNJWPlayerModels.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -254,7 +258,7 @@ ); PRODUCT_NAME = RNJWPlayer; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer/RNJWPlayer-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; @@ -283,7 +287,7 @@ ); PRODUCT_NAME = RNJWPlayer; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer/RNJWPlayer-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "RNJWPlayer-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Release; diff --git a/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift b/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift index a1d19a7e..5ae920b4 100644 --- a/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift +++ b/ios/RNJWPlayer/RCTConvert+RNJWPlayer.swift @@ -10,7 +10,7 @@ import React import JWPlayerKit extension RCTConvert { - + static func JWAdClient(_ value: String) -> JWAdClient { switch value { case "vast": @@ -23,7 +23,7 @@ extension RCTConvert { return .unknown } } - + static func JWInterfaceBehavior(_ value: String) -> JWInterfaceBehavior { switch value { case "normal": @@ -36,7 +36,7 @@ extension RCTConvert { return .normal } } - + static func JWCaptionEdgeStyle(_ value: String) -> JWCaptionEdgeStyle { switch value { case "none": @@ -53,7 +53,7 @@ extension RCTConvert { return .undefined } } - + static func JWPreload(_ value: String) -> JWPreload { switch value { case "auto": @@ -64,7 +64,7 @@ extension RCTConvert { return .none } } - + static func JWRelatedOnClick(_ value: String) -> JWRelatedOnClick { switch value { case "play": @@ -75,7 +75,7 @@ extension RCTConvert { return .play } } - + static func JWRelatedOnComplete(_ value: String) -> JWRelatedOnComplete { switch value { case "show": @@ -88,7 +88,7 @@ extension RCTConvert { return .show } } - + static func JWControlType(_ value: String) -> JWControlType { switch value { case "forward": @@ -115,5 +115,5 @@ extension RCTConvert { return .fullscreenButton } } - + } diff --git a/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h b/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h index 0bc5bc92..78c8d306 100644 --- a/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h +++ b/ios/RNJWPlayer/RNJWPlayer-Bridging-Header.h @@ -1,2 +1,5 @@ +#if __has_include("React/RCTViewManager.h") #import "React/RCTViewManager.h" -#import "RCTEventDispatcher.h" +#else +#import "RCTViewManager.h" +#endif 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.swift b/ios/RNJWPlayer/RNJWPlayerView.swift index bcf100c2..b024424a 100644 --- a/ios/RNJWPlayer/RNJWPlayerView.swift +++ b/ios/RNJWPlayer/RNJWPlayerView.swift @@ -5,19 +5,19 @@ // Created by Chaim Paneth on 3/30/22. // +import UIKit import AVFoundation import AVKit -import GoogleCast -import JWPlayerKit import MediaPlayer import React -import UIKit +import GoogleCast +import JWPlayerKit -class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDelegate, JWCastDelegate, JWAVDelegate, JWPlayerViewDelegate, JWPlayerViewControllerDelegate, JWDRMContentKeyDataSource, AVPictureInPictureControllerDelegate { - // MARK: - RNJWPlayer allocation +class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDelegate, JWCastDelegate, JWAVDelegate, JWPlayerViewDelegate, JWPlayerViewControllerDelegate, JWDRMContentKeyDataSource, AVPictureInPictureControllerDelegate { - private var _eventDispatcher: RCTEventDispatcher? - var playerViewController: RNJWPlayerViewController! + // MARK: - RNJWPlayer allocation + + var playerViewController:RNJWPlayerViewController! var playerView: JWPlayerView! var audioSession: AVAudioSession! var pipEnabled: Bool = true @@ -33,62 +33,62 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg var audioCategoryOptions: [String]! var settingConfig: Bool = false var pendingConfig: Bool = false - var currentConfig: [String: Any]! + 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 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(eventDispatcher: RCTEventDispatcher!) { + @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)) - self._eventDispatcher = eventDispatcher } required init?(coder: NSCoder) { super.init(coder: coder) - NotificationCenter.default.addObserver(self, selector: #selector(self.rotated), name: UIDevice.orientationDidChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil) } deinit { @@ -98,80 +98,81 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg override func removeFromSuperview() { self.startDeinitProcess() } - + func startDeinitProcess() { - NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil) - + NotificationCenter.default.removeObserver(self, name:UIDevice.orientationDidChangeNotification, object:nil) + self.reset() super.removeFromSuperview() } - + func reset() { - NotificationCenter.default.removeObserver(self, name: AVAudioSession.mediaServicesWereResetNotification, object: self.audioSession) - NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: self.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 (self.playerViewController != nil) || (self.playerView != nil) { - // playerViewController.player.currentItem!.removeObserver(self, forKeyPath:"playbackLikelyToKeepUp", context:nil) - if self.playerView != nil { - NotificationCenter.default.removeObserver(self, forKeyPath: "isPictureInPicturePossible", context: nil) + 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 { + + 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 !self.areEqual(value1, value2) { + if !areEqual(value1, value2) { diffKeys.append(key) } } else { @@ -179,64 +180,65 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg 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 self.keysForDifferingValues(in: dict1, and: dict2).isEmpty + 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(self.areEqual) + 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 setConfig(_ config: [String: Any]) { // Create mutable copies of the dictionaries var configCopy = config - var currentConfigCopy = self.currentConfig - + var currentConfigCopy = currentConfig + // Remove the playlist key configCopy.removeValue(forKey: "playlist") currentConfigCopy?.removeValue(forKey: "playlist") - + // Compare dictionaries without the playlist key - if (currentConfigCopy == nil) || !self.dictionariesAreEqual(configCopy, currentConfigCopy!) { + if (currentConfigCopy == nil) || !dictionariesAreEqual(configCopy, currentConfigCopy!) { print("There are differences other than the 'playlist' key.") - - if currentConfigCopy != nil { - let diffKeys = self.keysForDifferingValues(in: configCopy, and: currentConfigCopy!) + + 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") } - - self.setNewConfig(config: config) + + setNewConfig(config: config) } else { // Compare original dictionaries - if !self.dictionariesAreEqual(self.currentConfig, config) { + 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]) { @@ -244,106 +246,106 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg } } } - + if let playerViewController = playerViewController { playerViewController.player.loadPlaylist(items: playlistArray) } else if let playerView = playerView { playerView.player.loadPlaylist(items: playlistArray) } else { - self.setNewConfig(config: config) + setNewConfig(config: config) } } else { print("There are no differences.") } } } - - func setNewConfig(config: [String: Any]) { - self.currentConfig = config - - if !self.settingConfig { - self.pendingConfig = false - self.settingConfig = true - + + func setNewConfig(config: [String : Any]) { + 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 { - self.backgroundAudioEnabled = bae - self.pipEnabled = pe + backgroundAudioEnabled = bae + pipEnabled = pe } - if self.backgroundAudioEnabled || self.pipEnabled { + 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 { - try self.setupPlayerView(config: config, playerConfig: self.getPlayerConfiguration(config: config)) + self.setupPlayerView(config: config, playerConfig: try self.getPlayerConfiguration(config: config)) } else { - try self.setupPlayerViewController(config: config, playerConfig: self.getPlayerConfiguration(config: config)) + self.setupPlayerViewController(config: config, playerConfig: try self.getPlayerConfiguration(config: config)) } } catch { print(error) } - - self.processSpcUrl = config["processSpcUrl"] as? String - self.fairplayCertUrl = config["fairplayCertUrl"] as? String - self.contentUUID = config["contentUUID"] as? String + + processSpcUrl = config["processSpcUrl"] as? String + fairplayCertUrl = config["fairplayCertUrl"] as? String + contentUUID = config["contentUUID"] as? String } else { - self.pendingConfig = true + pendingConfig = true } } - - @objc func setControls(_ controls: Bool) { - self.toggleUIGroup(view: self.playerViewController.view, name: "JWPlayerKit.InterfaceView", ofSubview: nil, show: controls) + + @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! { + + 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) - { + 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) - { + let encodedUrl = URL(string: encodedString) { itemBuilder.file(encodedUrl) } } } - + // Process sources if let itemSources = item["sources"] as? [AnyObject], !itemSources.isEmpty { var sourcesArray = [JWVideoSource]() @@ -526,8 +526,8 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg 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 isDefault = source["default"] as? Bool { + let sourceBuilder = JWVideoSourceBuilder() sourceBuilder.file(fileURL) sourceBuilder.label(label) @@ -541,32 +541,32 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg 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]() @@ -575,8 +575,8 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg 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 isDefault = trackItem["default"] as? Bool { + let trackBuilder = JWCaptionTrackBuilder() trackBuilder.file(fileURL) trackBuilder.label(label) @@ -590,7 +590,7 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg itemBuilder.mediaTracks(tracksArray) } - + // Process adSchedule if let adsItem = item["adSchedule"] as? [AnyObject], !adsItem.isEmpty { var adsArray = [JWAdBreak]() @@ -599,8 +599,8 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg if let offsetString = adItem["offset"] as? String, let tag = adItem["tag"] as? String, let tagURL = URL(string: tag), - let offset = JWAdOffset.from(string: offsetString) - { + let offset = JWAdOffset.from(string: offsetString) { + let adBreakBuilder = JWAdBreakBuilder() adBreakBuilder.offset(offset) adBreakBuilder.tags([tagURL]) @@ -615,22 +615,23 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg 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() - + 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) { @@ -639,67 +640,66 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg } 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) } - + let ads = config["advertising"] as? [String: Any] - // if let adClient = ads?["adClient"] as? Int { - // var jwAdClient: JWAdClient = .unknown - // - // switch adClient { - // case 0: - // jwAdClient = .JWPlayer - // case 1: - // jwAdClient = .GoogleIMA - // case 2: - // jwAdClient = .GoogleIMADAI - // default: - // break - // } - // - // - // } +// if let adClient = ads?["adClient"] as? Int { +// var jwAdClient: JWAdClient = .unknown +// +// switch adClient { +// case 0: +// jwAdClient = .JWPlayer +// case 1: +// jwAdClient = .GoogleIMA +// case 2: +// jwAdClient = .GoogleIMADAI +// default: +// break +// } +// +// +// } let adConfigBuilder = JWAdsAdvertisingConfigBuilder() - + if let schedule = ads?["adSchedule"] as? [[String: Any]] { _ = schedule.compactMap { item -> JWAdBreak? in guard let offsetString = item["offset"] as? String, let tag = item["tag"] as? String, let tagUrl = URL(string: tag), - let offset = JWAdOffset.from(string: offsetString) - else { + let offset = JWAdOffset.from(string: offsetString) else { return nil } - + let adBreakBuilder = JWAdBreakBuilder() adBreakBuilder.offset(offset) adBreakBuilder.tags([tagUrl]) - + do { return try adBreakBuilder.build() } catch { @@ -709,33 +709,33 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg } } } - + if let tag = ads?["tag"] as? String { adConfigBuilder.tag(URL(string: tag)!) } - + if let adVmap = ads?["adVmap"] as? String { adConfigBuilder.vmapURL(URL(string: adVmap)!) } - + if let openBrowserOnAdClick = ads?["openBrowserOnAdClick"] as? Bool { adConfigBuilder.openBrowserOnAdClick(openBrowserOnAdClick) } - + let advertising = try adConfigBuilder.build() configBuilder.advertising(advertising) - + let playerConfig = try configBuilder.build() - + return playerConfig } - + // MARK: - JWPlayer View Controller helpers - + func setupPlayerViewController(config: [String: Any], playerConfig: JWPlayerConfiguration) { - if self.playerViewController == nil { - self.playerViewController = RNJWPlayerViewController() - self.playerViewController.parentView = self + if playerViewController == nil { + playerViewController = RNJWPlayerViewController() + playerViewController.parentView = self DispatchQueue.main.async { [self] in if self.reactViewController() != nil { @@ -746,237 +746,239 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg } } - self.playerViewController.view.frame = self.frame - self.addSubview(self.playerViewController.view) - self.playerViewController.setDelegates() + playerViewController.view.frame = self.frame + self.addSubview(playerViewController.view) + playerViewController.setDelegates() } - + if let ib = config["interfaceBehavior"] as? String { - self.interfaceBehavior = RCTConvert.JWInterfaceBehavior(ib) + interfaceBehavior = RCTConvert.JWInterfaceBehavior(ib) } - + if let interfaceFadeDelay = config["interfaceFadeDelay"] as? NSNumber { - self.playerViewController.interfaceFadeDelay = interfaceFadeDelay.doubleValue + playerViewController.interfaceFadeDelay = interfaceFadeDelay.doubleValue } - + if let forceFullScreenOnLandscape = config["fullScreenOnLandscape"] as? Bool { - self.playerViewController.forceFullScreenOnLandscape = forceFullScreenOnLandscape + playerViewController.forceFullScreenOnLandscape = forceFullScreenOnLandscape } - + if let forceLandscapeOnFullScreen = config["landscapeOnFullScreen"] as? Bool { - self.playerViewController.forceLandscapeOnFullScreen = forceLandscapeOnFullScreen + playerViewController.forceLandscapeOnFullScreen = forceLandscapeOnFullScreen } - + if let enableLockScreenControls = config["enableLockScreenControls"] as? Bool { - self.playerViewController.enableLockScreenControls = enableLockScreenControls && self.backgroundAudioEnabled + playerViewController.enableLockScreenControls = enableLockScreenControls && backgroundAudioEnabled } - + if let allowsPictureInPicturePlayback = config["allowsPictureInPicturePlayback"] as? Bool { - self.playerViewController.allowsPictureInPicturePlayback = allowsPictureInPicturePlayback + 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 { - self.playerViewController.nextUpStyle = try nextUpBuilder.build() + playerViewController.nextUpStyle = try nextUpBuilder.build() } catch { print(error) } } - - // playerViewController.adInterfaceStyle - // playerViewController.logo - // playerView.videoGravity = 0; - // playerView.captionStyle - + + // playerViewController.adInterfaceStyle + // playerViewController.logo + // playerView.videoGravity = 0; + // playerView.captionStyle + if let offlineMsg = config["offlineMessage"] as? String { - self.playerViewController.offlineMessage = offlineMsg + 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) - { - self.playerViewController.offlinePosterImage = image + let image = UIImage(data: imageData) { + playerViewController.offlinePosterImage = image } } } - + self.presentPlayerViewController(configuration: playerConfig) } - + func dismissPlayerViewController() { - if self.playerViewController != nil { - self.playerViewController.player.pause() // hack for stop not always stopping on unmount - self.playerViewController.player.stop() - self.playerViewController.enableLockScreenControls = false - + 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() + let configBuilder:JWPlayerConfigurationBuilder! = JWPlayerConfigurationBuilder() configBuilder.playlist(items: []) do { let configuration: JWPlayerConfiguration = try configBuilder.build() - self.playerViewController.player.configurePlayer(with: configuration) + playerViewController.player.configurePlayer(with: configuration) } catch { print(error) } - self.playerViewController.parentView = nil - self.playerViewController.setVisibility(.hidden, for: [.pictureInPictureButton]) - self.playerViewController.view.removeFromSuperview() - self.playerViewController.removeFromParent() - self.playerViewController.willMove(toParent: nil) - self.playerViewController.removeDelegates() - self.playerViewController = nil + + 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 { - self.playerViewController.player.configurePlayer(with: configuration) - if self.interfaceBehavior != nil { - self.playerViewController.interfaceBehavior = self.interfaceBehavior + playerViewController.player.configurePlayer(with: configuration) + if (interfaceBehavior != nil) { + playerViewController.interfaceBehavior = interfaceBehavior } } } - + // MARK: - JWPlayer View helpers - + func setupPlayerView(config: [String: Any], playerConfig: JWPlayerConfiguration) { - self.playerView = JWPlayerView(frame: self.superview!.frame) - - self.playerView.delegate = self - self.playerView.player.delegate = self - self.playerView.player.playbackStateDelegate = self - self.playerView.player.adDelegate = self - self.playerView.player.avDelegate = self - self.playerView.player.contentKeyDataSource = self - - self.playerView.player.configurePlayer(with: playerConfig) - - if self.pipEnabled { - let pipController: AVPictureInPictureController! = self.playerView.pictureInPictureController + 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) + + pipController.addObserver(self, forKeyPath:"isPictureInPicturePossible", options:[.new, .initial], context:nil) } - + self.addSubview(self.playerView) - + if let autostart = config["autostart"] as? Bool, autostart { - self.playerView.player.play() + playerView.player.play() } - + // Time observers - weak var weakSelf: RNJWPlayerView! = self - self.playerView.player.adTimeObserver = { (time: JWTimeData!) in + weak var weakSelf:RNJWPlayerView! = self + playerView.player.adTimeObserver = { (time:JWTimeData!) in weakSelf.onAdTime?(["position": time.position, "duration": time.duration]) } - - self.playerView.player.mediaTimeObserver = { (time: JWTimeData!) in + + playerView.player.mediaTimeObserver = { (time:JWTimeData!) in weakSelf.onTime?(["position": time.position, "duration": time.duration]) } } - + func removePlayerView() { - if self.playerView != nil { - self.playerView.player.stop() - self.playerView.removeFromSuperview() - self.playerView = nil + 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 { - self.toggleUIGroup(view: subview, name: name, ofSubview: ofSubview, show: show) + 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!) + + 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 { - self.playerViewController.setVisibility(isVisible ? .visible : .hidden, for: _controls!) + } + + if (_controls.count > 0) { + playerViewController.setVisibility(isVisible ? .visible : .hidden, for: _controls!) } } - + // MARK: - JWPlayer Delegate - - func jwplayerIsReady(_ player: JWPlayer) { - self.settingConfig = false + + func jwplayerIsReady(_ player:JWPlayer) { + settingConfig = false self.onPlayerReady?([:]) - - if self.pendingConfig && self.currentConfig != nil { - self.setConfig(self.currentConfig) + + if pendingConfig && currentConfig != nil { + self.setConfig(currentConfig) } } - - func jwplayer(_ player: JWPlayer, failedWithError code: UInt, message: String) { + + func jwplayer(_ player:JWPlayer, failedWithError code:UInt, message:String) { self.onPlayerError?(["error": message]) } - - func jwplayer(_ player: JWPlayer, failedWithSetupError code: UInt, message: String) { + + func jwplayer(_ player:JWPlayer, failedWithSetupError code:UInt, message:String) { self.onSetupPlayerError?(["error": message]) } - - func jwplayer(_ player: JWPlayer, encounteredWarning code: UInt, message: String) { + + func jwplayer(_ player:JWPlayer, encounteredWarning code:UInt, message:String) { self.onPlayerWarning?(["warning": message]) } - - func jwplayer(_ player: JWPlayer, encounteredAdError code: UInt, message: String) { + + func jwplayer(_ player:JWPlayer, encounteredAdError code:UInt, message:String) { self.onPlayerAdError?(["error": message]) } - - func jwplayer(_ player: JWPlayer, encounteredAdWarning code: UInt, message: String) { + + + 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) { + + 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]) @@ -984,25 +986,25 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg print("Error converting dictionary to JSON data: \(error)") } } - + // MARK: - JWPlayer View Controller Delegate - - func playerViewController(_ controller: JWPlayerViewController, sizeChangedFrom oldSize: CGSize, to newSize: CGSize) { + + 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]) @@ -1010,62 +1012,68 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg print("Error converting dictionary to JSON data: \(error)") } } - - func playerViewController(_ controller: JWPlayerViewController, screenTappedAt position: CGPoint) { + + func playerViewController(_ controller:JWPlayerViewController, screenTappedAt position:CGPoint) { self.onScreenTapped?(["x": position.x, "y": position.y]) } - - func playerViewController(_ controller: JWPlayerViewController, controlBarVisibilityChanged isVisible: Bool, frame: CGRect) { + + func playerViewController(_ controller:JWPlayerViewController, controlBarVisibilityChanged isVisible:Bool, frame:CGRect) { self.onControlBarVisible?(["visible": isVisible]) } - - func playerViewControllerWillGoFullScreen(_ controller: JWPlayerViewController) -> JWFullScreenViewController? { + + func playerViewControllerWillGoFullScreen(_ controller:JWPlayerViewController) -> JWFullScreenViewController? { self.onFullScreenRequested?([:]) return nil } - - func playerViewControllerDidGoFullScreen(_ controller: JWPlayerViewController) { + + func playerViewControllerDidGoFullScreen(_ controller:JWPlayerViewController) { self.onFullScreen?([:]) } - - func playerViewControllerWillDismissFullScreen(_ controller: JWPlayerViewController) { + + func playerViewControllerWillDismissFullScreen(_ controller:JWPlayerViewController) { self.onFullScreenExitRequested?([:]) } - - func playerViewControllerDidDismissFullScreen(_ controller: JWPlayerViewController) { + + 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, 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) {} + func playerViewController(_ controller: JWPlayerViewController, relatedItemBeganPlaying item: JWPlayerItem, atIndex index: Int, withMethod method: JWRelatedMethod) { + + } // MARK: Time events - - func onAdTimeEvent(time: JWTimeData) { + + func onAdTimeEvent(time:JWTimeData) { self.onAdTime?(["position": time.position, "duration": time.duration]) } - - func onMediaTimeEvent(time: JWTimeData) { + + 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) + 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, _, error in + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in if let error = error { print("DRM cert request error - \(error.localizedDescription)") } @@ -1073,521 +1081,550 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg } 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 { + + 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() - // } - // } - // + + 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 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) {} - + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { + + } + // MARK: - JWPlayer State Delegate - - func jwplayerContentIsBuffering(_ player: JWPlayer) { + + func jwplayerContentIsBuffering(_ player:JWPlayer) { self.onBuffer?([:]) } - - func jwplayer(_ player: JWPlayer, isBufferingWithReason reason: JWBufferReason) { + + func jwplayer(_ player:JWPlayer, isBufferingWithReason reason:JWBufferReason) { self.onBuffer?([:]) } - - func jwplayer(_ player: JWPlayer, updatedBuffer percent: Double, position time: JWTimeData) { + + func jwplayer(_ player:JWPlayer, updatedBuffer percent:Double, position time:JWTimeData) { self.onUpdateBuffer?(["percent": percent, "position": time]) } - - func jwplayer(_ player: JWPlayer, didFinishLoadingWithTime loadTime: TimeInterval) { + + func jwplayer(_ player:JWPlayer, didFinishLoadingWithTime loadTime:TimeInterval) { self.onLoaded?([:]) } - - func jwplayer(_ player: JWPlayer, isAttemptingToPlay playlistItem: JWPlayerItem, reason: JWPlayReason) { + + func jwplayer(_ player:JWPlayer, isAttemptingToPlay playlistItem:JWPlayerItem, reason:JWPlayReason) { self.onAttemptPlay?([:]) } - - func jwplayer(_ player: JWPlayer, isPlayingWithReason reason: JWPlayReason) { + + func jwplayer(_ player:JWPlayer, isPlayingWithReason reason:JWPlayReason) { self.onPlay?([:]) - - self.userPaused = false - self.wasInterrupted = false + + userPaused = false + wasInterrupted = false } - - func jwplayer(_ player: JWPlayer, willPlayWithReason reason: JWPlayReason) { + + func jwplayer(_ player:JWPlayer, willPlayWithReason reason:JWPlayReason) { self.onBeforePlay?([:]) } - - func jwplayer(_ player: JWPlayer, didPauseWithReason reason: JWPauseReason) { + + func jwplayer(_ player:JWPlayer, didPauseWithReason reason:JWPauseReason) { self.onPause?([:]) - - if !self.wasInterrupted { - self.userPaused = true + + if !wasInterrupted { + userPaused = true } } - - func jwplayer(_ player: JWPlayer, didBecomeIdleWithReason reason: JWIdleReason) { + + func jwplayer(_ player:JWPlayer, didBecomeIdleWithReason reason:JWIdleReason) { self.onIdle?([:]) } - - func jwplayer(_ player: JWPlayer, isVisible: Bool) { + + func jwplayer(_ player:JWPlayer, isVisible:Bool) { self.onVisible?(["visible": isVisible]) } - - func jwplayerContentWillComplete(_ player: JWPlayer) { + + func jwplayerContentWillComplete(_ player:JWPlayer) { self.onBeforeComplete?([:]) } - - func jwplayerContentDidComplete(_ player: JWPlayer) { + + 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 - ] - + + 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: itemDict as Any, options: .prettyPrinted) - - self.onPlaylistItem?(["playlistItem": String(data: data, encoding: String.Encoding.utf8) as Any, "index": index]) + 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) + +// 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(itemDict) - } - + + 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]) + 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) { + + func jwplayerPlaylistHasCompleted(_ player:JWPlayer) { self.onPlaylistComplete?([:]) } - - func jwplayer(_ player: JWPlayer, usesMediaType type: JWMediaType) {} + + func jwplayer(_ player:JWPlayer, usesMediaType type:JWMediaType) { + + } func jwplayer(_ player: JWPlayer, playbackRateChangedTo rate: Double, at time: TimeInterval) { - // self.onSeek(["from": oldPosition, "to": newPosition]) + self.onRateChanged?(["rate": rate, "at": time]) } - - // func jwplayer(_ player:JWPlayer, playbackRateChangedTo oldPosition:TimeInterval, at_ newPosition:TimeInterval) { - // self.onSeek(["from": oldPosition, "to": newPosition]) - // } - - func jwplayerHasSeeked(_ player: JWPlayer) { + + func jwplayerHasSeeked(_ player:JWPlayer) { self.onSeeked?([:]) } - func jwplayer(_ player: JWPlayer, seekedFrom rate: Double, to time: TimeInterval) {} - - func jwplayer(_ player: JWPlayer, updatedCues cues: [JWCue]) {} - + 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) { + + func jwplayer(_ player:JWPlayer, adEvent event:JWAdEvent) { self.onAdEvent?(["client": event.client, "type": event.type]) } - - // pragma Mark - Casting methods - + +// pragma Mark - Casting methods + func setUpCastController() { - if (self.playerView != nil) && self.playerView.player as! Bool && (self.castController == nil) { - self.castController = JWCastController(player: self.playerView.player) - self.castController.delegate = self - } - - self.scanForDevices() + if (playerView != nil) && playerView.player as! Bool && (castController == nil) { + castController = JWCastController(player:playerView.player) + castController.delegate = self + } + + self.scanForDevices() } - + func scanForDevices() { - if self.castController != nil { - self.castController.startDiscovery() - } + if castController != nil { + castController.startDiscovery() + } } - + func stopScanForDevices() { - if self.castController != nil { - self.castController.stopDiscovery() - } + 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) { + + 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 self.castController.availableDevices + return castController.availableDevices } - + func connectedDevice() -> JWCastingDevice! { - return self.castController.connectedDevice + return castController.connectedDevice } - - func connectToDevice(device: JWCastingDevice!) { - return self.castController.connectToDevice(device) + + func connectToDevice(device:JWCastingDevice!) { + return castController.connectToDevice(device) } - + func cast() { - return self.castController.cast() + return castController.cast() } - + func stopCasting() { - return self.castController.stopCasting() + return castController.stopCasting() } - + // MARK: - JWPlayer Cast Delegate func castController(_ controller: JWCastController, castingBeganWithDevice device: JWCastingDevice) { self.onCasting?([:]) } - func castController(_ controller: JWCastController, castingEndedWithError error: Error?) { + func castController(_ controller:JWCastController, castingEndedWithError error: Error?) { self.onCastingEnded?(["error": error as Any]) } - - func castController(_ controller: JWCastController, castingFailedWithError error: Error) { + + 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) + + 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]) + 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) { + func castController(_ controller:JWCastController, connectionFailedWithError error: Error) { self.onConnectionFailed?(["error": error as Any]) } - - func castController(_ controller: JWCastController, connectionRecoveredWithDevice device: JWCastingDevice) { + + 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]) { + + 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]) + 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?) { + func castController(_ controller: JWCastController, disconnectedWithError error: (Error)?) { self.onDisconnectedFromCastingDevice?(["error": error as Any]) } - + // MARK: - JWPlayer AV Delegate - - func jwplayer(_ player: JWPlayer, audioTracksUpdated levels: [JWMediaSelectionOption]) { + + func jwplayer(_ player:JWPlayer, audioTracksUpdated levels:[JWMediaSelectionOption]) { self.onAudioTracks?([:]) } + + func jwplayer(_ player:JWPlayer, audioTrackChanged currentLevel:Int) { + + } - 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, captionPresented caption: [String], at time: JWTimeData) { + + } + + func jwplayer(_ player:JWPlayer, captionTrackChanged index:Int) { + + } + + func jwplayer(_ player: JWPlayer, visualQualityChanged currentVisualQuality: JWVisualQuality) { + + } - func jwplayer(_ player: JWPlayer, updatedCaptionList options: [JWMediaSelectionOption]) {} + 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?) { + + func initAudioSession(category:String?, categoryOptions:[String]?, mode:String?) { self.setObservers() - - var somethingChanged = false - - if !(category == self.audioCategory) || (categoryOptions != nil && !categoryOptions!.elementsEqual(self.audioCategoryOptions)) { + + var somethingChanged:Bool = false + + if !(category == audioCategory) || (categoryOptions != nil && !categoryOptions!.elementsEqual(audioCategoryOptions)) { somethingChanged = true - self.audioCategory = category - self.audioCategoryOptions = categoryOptions - self.setCategory(categoryName: category, categoryOptions: categoryOptions) + audioCategory = category + audioCategoryOptions = categoryOptions + self.setCategory(categoryName: category, categoryOptions:categoryOptions) } - - if !(mode == self.audioMode) { + + if !(mode == audioMode) { somethingChanged = true - self.audioMode = mode + audioMode = mode self.setMode(modeName: mode) } - + if somethingChanged { do { - try self.audioSession.setActive(true) + try audioSession.setActive(true) print("setActive - success") } catch { print("setActive - error: @%@", error) } } } - + func deinitAudioSession() { do { - try self.audioSession?.setActive(false, options: .notifyOthersOnDeactivation) + try audioSession?.setActive(false, options: .notifyOthersOnDeactivation) print("setUnactive - success") } catch { print("setUnactive - error: @%@", error) } - self.audioSession = nil + audioSession = nil MPNowPlayingInfoCenter.default().nowPlayingInfo = [:] UIApplication.shared.endReceivingRemoteControlEvents() } - + func setObservers() { - if self.audioSession == nil { - self.audioSession = AVAudioSession.sharedInstance() - + if audioSession == nil { + audioSession = AVAudioSession.sharedInstance() + NotificationCenter.default.addObserver(self, - selector: #selector(self.handleMediaServicesReset), - name: AVAudioSession.mediaServicesWereResetNotification, - object: self.audioSession) - + selector:#selector(handleMediaServicesReset), + name:AVAudioSession.mediaServicesWereResetNotification, + object:audioSession) + NotificationCenter.default.addObserver(self, - selector: #selector(self.audioSessionInterrupted(_:)), + selector: #selector(audioSessionInterrupted(_:)), name: AVAudioSession.interruptionNotification, - object: self.audioSession) - + object: audioSession) + NotificationCenter.default.addObserver(self, - selector: #selector(self.applicationWillResignActive(_:)), - name: UIApplication.willResignActiveNotification, object: nil) - + selector:#selector(applicationWillResignActive(_:)), + name:UIApplication.willResignActiveNotification, object:nil) + NotificationCenter.default.addObserver(self, - selector: #selector(self.applicationDidEnterBackground(_:)), - name: UIApplication.didEnterBackgroundNotification, - object: nil) - + selector:#selector(applicationDidEnterBackground(_:)), + name:UIApplication.didEnterBackgroundNotification, + object:nil) + NotificationCenter.default.addObserver(self, - selector: #selector(self.applicationWillEnterForeground(_:)), - name: UIApplication.willEnterForegroundNotification, object: nil) - + selector:#selector(applicationWillEnterForeground(_:)), + name:UIApplication.willEnterForegroundNotification, object:nil) + NotificationCenter.default.addObserver(self, - selector: #selector(self.audioRouteChanged(_:)), + selector: #selector(audioRouteChanged(_:)), name: AVAudioSession.routeChangeNotification, object: nil) } } - - func setCategory(categoryName: String!, categoryOptions: [String]!) { - if self.audioSession == nil { - self.audioSession = AVAudioSession.sharedInstance() + + func setCategory(categoryName:String!, categoryOptions:[String]!) { + if (audioSession == nil) { + audioSession = AVAudioSession.sharedInstance() } - - var category: AVAudioSession.Category! = nil + + var category:AVAudioSession.Category! = nil if categoryName.isEqual("Ambient") { category = .ambient } else if categoryName.isEqual("SoloAmbient") { @@ -1603,7 +1640,7 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg } else { category = .playback } - + var options: AVAudioSession.CategoryOptions = [] if categoryOptions.contains("MixWithOthers") { options.insert(.mixWithOthers) @@ -1632,20 +1669,20 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg } do { - try self.audioSession.setCategory(category, options: options) + try audioSession.setCategory(category, options: options) print("setCategory - success") } catch { print("setCategory - error: @%@", error) } } - - func setMode(modeName: String!) { - if self.audioSession == nil { - self.audioSession = AVAudioSession.sharedInstance() + + func setMode(modeName:String!) { + if (audioSession == nil) { + audioSession = AVAudioSession.sharedInstance() } - - var mode: AVAudioSession.Mode! = nil - + + var mode:AVAudioSession.Mode! = nil + if modeName.isEqual("Default") { mode = .default } else if modeName.isEqual("VoiceChat") { @@ -1669,10 +1706,10 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg // Fallback on earlier versions } } - - if mode != nil { + + if (mode != nil) { do { - try self.audioSession.setMode(mode) + try audioSession.setMode(mode) print("setMode - success") } catch { print("setMode - error: @%@", error) @@ -1682,46 +1719,46 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg @objc func audioSessionInterrupted(_ notification: Notification) { guard let userInfo = notification.userInfo, - let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, - let type = AVAudioSession.InterruptionType(rawValue: typeValue) - else { - return + let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, + let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { + return } switch type { case .began: DispatchQueue.main.async { [self] in - self.wasInterrupted = true - - if self.playerView != nil { - self.playerView.player.pause() - } else if self.playerViewController != nil { - self.playerViewController.player.pause() + 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) && !self.userPaused && self.backgroundAudioEnabled { + if options.contains(.shouldResume) && !userPaused && backgroundAudioEnabled { // Interruption ended. Playback should resume. DispatchQueue.main.async { [self] in - if self.playerView != nil { - self.playerView.player.play() - } else if self.playerViewController != nil { - self.playerViewController.player.play() + if (playerView != nil) { + playerView.player.play() + } else if (playerViewController != nil) { + playerViewController.player.play() } print("handleInterruption :- Play") } - } else { + } + else { // Interruption ended. Playback should not resume. DispatchQueue.main.async { [self] in - self.wasInterrupted = true - - if self.playerView != nil { - self.playerView.player.pause() - } else if self.playerViewController != nil { - self.playerViewController.player.pause() + wasInterrupted = true + + if (playerView != nil) { + playerView.player.pause() + } else if (playerViewController != nil) { + playerViewController.player.pause() } print("handleInterruption :- Pause") } @@ -1730,49 +1767,52 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDeleg 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 !self.userPaused && self.backgroundAudioEnabled { - if (self.playerView != nil) && self.playerView.player.getState() == .playing { - self.playerView.player.play() - } else if (self.playerViewController != nil) && self.playerViewController.player.getState() == .playing { - self.playerViewController.player.play() + 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) {} - + @objc func applicationDidEnterBackground(_ notification: Notification) { + + } + // Active @objc func applicationWillEnterForeground(_ notification: Notification) { - if !self.userPaused && self.backgroundAudioEnabled { - if (self.playerView != nil) && self.playerView.player.getState() == .playing { - self.playerView.player.play() - } else if (self.playerViewController != nil) && self.playerViewController.player.getState() == .playing { - self.playerViewController.player.play() + 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 self.playerView != nil { - self.playerView.player.pause() - } else if self.playerViewController != nil { - self.playerViewController.player.pause() + if (playerView != nil) { + playerView.player.pause() + } else if (playerViewController != nil) { + playerViewController.player.pause() } } } + } diff --git a/ios/RNJWPlayer/RNJWPlayerViewController.swift b/ios/RNJWPlayer/RNJWPlayerViewController.swift index add9a68b..91df58af 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewController.swift +++ b/ios/RNJWPlayer/RNJWPlayerViewController.swift @@ -16,85 +16,85 @@ 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.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.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]) @@ -102,25 +102,25 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD 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]) @@ -128,51 +128,51 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD 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?([:]) } - + // 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 @@ -189,7 +189,7 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD } func playerViewController(_ controller:JWPlayerViewController, relatedMenuClosedWithMethod method:JWRelatedInteraction) { - + } func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedMenuOpenedWithItems items: [JWPlayerKit.JWPlayerItem], withMethod method: JWPlayerKit.JWRelatedInteraction) { @@ -199,333 +199,334 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedItemBeganPlaying item: JWPlayerKit.JWPlayerItem, atIndex index: Int, withMethod method: JWPlayerKit.JWRelatedMethod) { } - + 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() - // } +// 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 - ] - +// 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: itemDict as Any, options:.prettyPrinted) - + 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) + +// 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(itemDict) - } - +// 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?([:]) @@ -533,58 +534,58 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD 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 e615f197..527135f5 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.m +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.m @@ -1,9 +1,13 @@ -#import +#if __has_include("React/RCTViewManager.h") #import "React/RCTViewManager.h" -#import "RCTUIManager.h" +#else +#import "RCTViewManager.h" +#endif #import +#import "RCTUIManager.h" + @interface RCT_EXTERN_MODULE(RNJWPlayerViewManager, RCTViewManager) /* player state events */ @@ -11,6 +15,7 @@ @interface RCT_EXTERN_MODULE(RNJWPlayerViewManager, RCTViewManager) 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); @@ -66,76 +71,76 @@ @interface RCT_EXTERN_MODULE(RNJWPlayerViewManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(config, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); -RCT_EXTERN_METHOD(state:(nonnull NSNumber*)reactTag - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(state: (nonnull NSNumber*)reactTag + stateWithResolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(pause:(nonnull NSNumber*)reactTag) +RCT_EXTERN_METHOD(pause: (nonnull NSNumber*)reactTag) -RCT_EXTERN_METHOD(play:(nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(play: (nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(stop:(nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(stop: (nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(position:(nonnull NSNumber *)reactTag - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(position: (nonnull NSNumber *)reactTag + positionResolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(toggleSpeed:(nonnull NSNumber*)reactTag) +RCT_EXTERN_METHOD(toggleSpeed: (nonnull NSNumber*)reactTag) -RCT_EXTERN_METHOD(setSpeed:(nonnull NSNumber*)reactTag speed:(double)speed) +RCT_EXTERN_METHOD(setSpeed: (nonnull NSNumber*)reactTag: (double)speed) -RCT_EXTERN_METHOD(setPlaylistIndex:(nonnull NSNumber *)reactTag index:(nonnull NSNumber *)index) +RCT_EXTERN_METHOD(setPlaylistIndex: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) -RCT_EXTERN_METHOD(seekTo:(nonnull NSNumber *)reactTag time:(nonnull NSNumber *)time) +RCT_EXTERN_METHOD(seekTo: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)time) -RCT_EXTERN_METHOD(setVolume:(nonnull NSNumber *)reactTag volume:(nonnull NSNumber *)volume) +RCT_EXTERN_METHOD(setVolume: (nonnull NSNumber *)reactTag :(nonnull NSNumber *)volume) -RCT_EXTERN_METHOD(togglePIP:(nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(togglePIP: (nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(setUpCastController:(nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(setUpCastController: (nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(presentCastDialog:(nonnull NSNumber *)reactTag) +RCT_EXTERN_METHOD(presentCastDialog: (nonnull NSNumber *)reactTag) -RCT_EXTERN_METHOD(connectedDevice:(nonnull NSNumber *)reactTag - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(connectedDevice: (nonnull NSNumber *)reactTag + resolve:(RCTPromiseResolveBlock)resolve + rejecte:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(availableDevices:(nonnull NSNumber *)reactTag - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(availableDevices: (nonnull NSNumber *)reactTag + solve:(RCTPromiseResolveBlock)resolve + eject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(castState:(nonnull NSNumber *)reactTag - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(castState: (nonnull NSNumber *)reactTag + solver:(RCTPromiseResolveBlock)resolve + ejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getAudioTracks:(nonnull NSNumber *)reactTag - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getAudioTracks: (nonnull NSNumber *)reactTag + resolve:(RCTPromiseResolveBlock)resolve + eject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getCurrentAudioTrack:(nonnull NSNumber *)reactTag - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getCurrentAudioTrack: (nonnull NSNumber *)reactTag + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(setCurrentAudioTrack:(nonnull NSNumber *)reactTag index:(nonnull NSNumber *)index) +RCT_EXTERN_METHOD(setCurrentAudioTrack: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) -RCT_EXTERN_METHOD(setControls:(nonnull NSNumber *)reactTag show:(BOOL)show) +RCT_EXTERN_METHOD(setControls: (nonnull NSNumber *)reactTag: (BOOL)show) -RCT_EXTERN_METHOD(setVisibility:(nonnull NSNumber *)reactTag visibilty:(BOOL)visibilty controls:(nonnull NSArray *)controls) +RCT_EXTERN_METHOD(setVisibility: (nonnull NSNumber *)reactTag: (BOOL)visibilty: (nonnull NSArray *)controls) -RCT_EXTERN_METHOD(setLockScreenControls:(nonnull NSNumber *)reactTag show:(BOOL)show) +RCT_EXTERN_METHOD(setLockScreenControls: (nonnull NSNumber *)reactTag: (BOOL)show) -RCT_EXTERN_METHOD(setCurrentCaptions:(nonnull NSNumber *)reactTag index:(nonnull NSNumber *)index) +RCT_EXTERN_METHOD(setCurrentCaptions: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) -RCT_EXTERN_METHOD(setCurrentCaptions:(nonnull NSNumber *)reactTag index:(nonnull NSNumber *)index) +RCT_EXTERN_METHOD(setCurrentCaptions: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) -RCT_EXTERN_METHOD(setLicenseKey:(nonnull NSNumber *)reactTag license:(nonnull NSString *)license) +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 playlist:(nonnull NSArray *)playlist) +RCT_EXTERN_METHOD(loadPlaylist: (nonnull NSNumber *)reactTag: (nonnull NSArray *)playlist) -RCT_EXTERN_METHOD(setFullscreen:(nonnull NSNumber *)reactTag fullscreen:(BOOL)fullscreen) +RCT_EXTERN_METHOD(setFullscreen: (nonnull NSNumber *)reactTag: (BOOL)fullscreen) @end diff --git a/ios/RNJWPlayer/RNJWPlayerViewManager.swift b/ios/RNJWPlayer/RNJWPlayerViewManager.swift index 2013d3ce..4dc62692 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.swift +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.swift @@ -6,22 +6,17 @@ import JWPlayerKit class RNJWPlayerViewManager: RCTViewManager { override func view() -> UIView { - return RNJWPlayerView(eventDispatcher: bridge.eventDispatcher() as? RCTEventDispatcher) + return RNJWPlayerView() } - - func methodQueue() -> DispatchQueue { - return bridge.uiManager.methodQueue - } - - @objc(state:resolver:rejecter:) - func state(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - bridge.uiManager.addUIBlock { (_, viewRegistry) in + + @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 { @@ -33,14 +28,13 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc(pause:) - func pause(reactTag: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @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() @@ -50,14 +44,13 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc(play:) - func play(reactTag: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @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 { @@ -65,15 +58,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(stop:) - func stop(reactTag: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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() @@ -82,16 +74,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(position:resolver:rejecter:) - func position(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 { @@ -102,15 +93,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(toggleSpeed:) - func toggleSpeed(reactTag: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 @@ -126,15 +116,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(setSpeed:speed:) - func setSpeed(reactTag: NSNumber, speed: Double) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 { @@ -142,15 +131,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(setPlaylistIndex:index:) - func setPlaylistIndex(reactTag: NSNumber, index: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 { @@ -158,15 +146,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(seekTo:time:) - func seekTo(reactTag: NSNumber, time: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 { @@ -175,14 +162,13 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc(setVolume:volume:) - func setVolume(reactTag: NSNumber, volume: Double) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @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 { @@ -190,15 +176,15 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(togglePIP:) - func togglePIP(reactTag: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + + @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() @@ -208,45 +194,42 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(setUpCastController:) - func setUpCastController(reactTag: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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(presentCastDialog:) - func presentCastDialog(reactTag: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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(connectedDevice:resolver:rejecter:) - func connectedDevice(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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)) @@ -259,26 +242,25 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(availableDevices:resolver:rejecter:) - func availableDevices(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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)) @@ -291,31 +273,29 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(castState:resolver:rejecter:) - func castState(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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(getAudioTracks:resolver:rejecter:) - func getAudioTracks(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 { @@ -340,15 +320,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc(getCurrentAudioTrack:resolver:rejecter:) - func getCurrentAudioTrack(reactTag: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @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 { @@ -359,15 +338,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(setCurrentAudioTrack:index:) - func setCurrentAudioTrack(reactTag: NSNumber, index: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 { @@ -376,56 +354,52 @@ class RNJWPlayerViewManager: RCTViewManager { } } - @objc(setControls:show:) - func setControls(reactTag: NSNumber, show: Bool) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + @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(setVisibility:visibility:controls:) - func setVisibility(reactTag: NSNumber, visibility: Bool, controls: [String]) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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(setLockScreenControls:show:) - func setLockScreenControls(reactTag: NSNumber, show: Bool) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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(setCurrentCaptions:index:) - func setCurrentCaptions(reactTag: NSNumber, index: NSNumber) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 { @@ -433,22 +407,20 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(setLicenseKey:license:) - func setLicenseKey(reactTag: NSNumber, license: String) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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(quite) - func quite() { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 { @@ -462,10 +434,9 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(reset) - func reset() { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @objc func reset() { + self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in for (_, view) in viewRegistry ?? [:] { if let rnjwView = view as? RNJWPlayerView { rnjwView.startDeinitProcess() @@ -473,23 +444,22 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(loadPlaylist:playlist:) - func loadPlaylist(reactTag: NSNumber, playlist: [Any]) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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 { @@ -497,15 +467,14 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - - @objc(setFullscreen:fullscreen:) - func setFullscreen(reactTag: NSNumber, fullscreen: Bool) { - bridge.uiManager.addUIBlock { uiManager, viewRegistry in + + @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) @@ -517,5 +486,5 @@ class RNJWPlayerViewManager: RCTViewManager { } } } - + } From de660980e53d1b0e659d9918eaf878ef554aae89 Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Thu, 21 Dec 2023 15:42:15 +0200 Subject: [PATCH 04/11] typo fix --- .../src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java index 45ec30ab..515db458 100755 --- a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java +++ b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java @@ -1336,7 +1336,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); } From 7a02249ffc264fba2e051418346583b101ad11b1 Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Thu, 21 Dec 2023 17:16:32 +0200 Subject: [PATCH 05/11] missing event register --- .../src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java index 515db458..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, From 8f7d4d120db34122014eed23078dc53878774a06 Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Thu, 21 Dec 2023 18:23:00 +0200 Subject: [PATCH 06/11] types fix --- index.d.ts | 88 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 24 deletions(-) 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; } From af13a46bbf8fb54f1e8979eec5f796fb7289e3a2 Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Sun, 24 Dec 2023 15:41:59 +0200 Subject: [PATCH 07/11] init merge latest changes --- README.md | 42 ++- RNJWPlayer.podspec | 18 +- android/build.gradle | 38 +- android/src/main/AndroidManifest.xml | 4 +- .../appgoalz/rnjwplayer/RNJWPlayerAds.java | 239 ++++++++++++ .../appgoalz/rnjwplayer/RNJWPlayerModule.java | 20 +- .../appgoalz/rnjwplayer/RNJWPlayerView.java | 343 +++++++++++++----- .../rnjwplayer/RNJWPlayerViewManager.java | 20 + .../java/com/appgoalz/rnjwplayer/Util.java | 31 ++ .../src/main/xml/cast_enabled_manifest.xml | 35 ++ index.d.ts | 148 ++++++-- index.js | 140 ++++++- package.json | 2 +- 13 files changed, 930 insertions(+), 150 deletions(-) create mode 100644 android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerAds.java create mode 100644 android/src/main/xml/cast_enabled_manifest.xml diff --git a/README.md b/README.md index 9f7872e7..69262394 100644 --- a/README.md +++ b/README.md @@ -224,8 +224,8 @@ Running the example project: | --------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | | **`mediaId`** | The JW media id. | `Int` | | **`startTime`** | the player should start from a certain second. | `Int` | -| **`adVmap`** | The url of ads VMAP xml. | `String` | -| **`adSchedule`** | Array of tags and and offsets for ads. | `{tag: String, offset: String}` | +| **`adVmap`** | The url of ads VMAP xml. (iOS only) | `String` | +| **`adSchedule`** | Array of tags and and offsets for ads. (iOS only) | `{tag: String, offset: String}` | | **`description`** | Description of the track. | `String` | | **`file`** | The url of the file to play. | `String` | | **`tracks`** | Array of caption tracks. | `{file: String, label: String}` | @@ -381,12 +381,26 @@ Checkout the DRMExample in the Example app. (The DRMExample cannot be run in the ##### Advertising -| Prop | Description | Type | -| -------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------- | -| **`adVmap`** | The url of ads VMAP xml. | `String` | -| **`adSchedule`** | Array of tags and and offsets for ads. | `{tag: String, offset: String}` | -| **`openBrowserOnAdClick`** | Should the player open the browser when clicking on an ad. | `Boolean` | -| **`adClient`** | The ad client. One of [JWPlayerAdClients](#JWPlayerAdClients), defaults to JWAdClientVast | `'vast', 'ima", 'ima_dai'` | +### Important +When using an **IMA** ad client you need to do some additional setup. + +- **iOS**: Add `$RNJWPlayerUseGoogleIMA = true` to your Podfile, this will add `GoogleAds-IMA-iOS-SDK` pod. + +- **Android**: Add `RNJWPlayerUseGoogleIMA = true` in your *app/build.gradle* `ext {}` this will add `'com.google.ads.interactivemedia.v3:interactivemedia:3.29.0'` + and `'com.google.android.gms:play-services-ads-identifier:18.0.1'`. + +| Prop | Description | Type | Availability | +|-------------------------------|----------------------------------------------------------------------------------------------------------|-----------------------------------------------|-------------------------| +| **`adVmap`** | The URL of the ads VMAP XML. | `String` | All Clients (iOS only) | +| **`adSchedule`** | Array of tags and offsets for ads. | `{tag: String, offset: String}[]` | All Clients | +| **`openBrowserOnAdClick`** | Should the player open the browser when clicking on an ad. | `Boolean` | All Clients | +| **`adClient`** | The ad client. One of `vast`, `ima`, or `ima_dai`, check out [JWPlayerAdClients](#JWPlayerAdClients), defaults to `vast`. | `'vast'`, `'ima'`, `'ima_dai'` | All Clients | +| **`adRules`** | Ad rules for VAST client. | `{startOn: Number, frequency: Number, timeBetweenAds: Number, startOnSeek: 'none' \| 'pre'}` | VAST only | +| **`imaSettings`** | Settings specific to Google IMA SDK. | `{locale: String, ppid: String, maxRedirects: Number, sessionID: String, debugMode: Boolean}` | IMA and IMA DAI | +| **`companionAdSlots`** | Array of objects representing companion ad slots. | `{viewId: String, size?: {width: Number, height: Number}}[]` | IMA only | +| **`friendlyObstructions`** | Array of objects representing friendly obstructions for viewability measurement. | `{viewId: String, purpose: 'mediaControls' \| 'closeAd' \| 'notVisible' \| 'other', reason?: String}[]` | IMA and IMA DAI | +| **`googleDAIStream`** | Stream configuration for Google DAI (Dynamic Ad Insertion). | `{videoID?: String, cmsID?: String, assetKey?: String, apiKey?: String, adTagParameters?: {[key: string]: string}}` | IMA DAI only | +| **`tag`** | Vast xml URL. | `String` | Vast only (iOS only) | ##### Related @@ -511,6 +525,7 @@ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Conf | **`onPause`** | Player paused playing. | `none` | | **`onSeek`** | Seek event requested from user. | `{position: Double, offset: Double}` | | **`onSeeked`** | Player finished seeking to a new position. | On **iOS** `none`, On **Android** `{position: Double}` | +| **`onRateChanged`** | Player speed was changed by the user from the settings menu. | On **iOS** `{rate: Double, at: Double}`, On **Android** `{rate: Double, at: Double}` | | **`onSetupPlayerError`** | Player faced and error while setting up the player. | `{error: String}` | | **`onPlayerError`** | Player faced an error after setting up the player but when attempting to start playing. | `{error: String}` | | **`onBuffer`** | The player is buffering. | `none` | @@ -540,9 +555,11 @@ JWPlayer enables casting by default with a casting button (if you pass the `view ###### iOS -Follow the instruction [here](https://developer.jwplayer.com/jwplayer/docs/ios-enable-casting-to-chromecast-devices) on the official JWPlayer site. +1: Follow the instruction [here](https://developer.jwplayer.com/jwplayer/docs/ios-enable-casting-to-chromecast-devices) on the official JWPlayer site. + +2: Add `$RNJWPlayerUseGoogleCast = true` to your Podfile, this will install `google-cast-sdk` pod. -Edit your `Info.plist` with the following values: +3: Edit your `Info.plist` with the following values: ``` 'NSBluetoothAlwaysUsageDescription' => 'We will use your Bluetooth for media casting.', @@ -552,7 +569,10 @@ Edit your `Info.plist` with the following values: 'NSMicrophoneUsageDescription' => 'We will use your Microphone for media casting.' ``` -Enable _Access WiFi Information_ capability under `Signing & Capabilities` +4: Enable _Access WiFi Information_ capability under `Signing & Capabilities` + +###### Android +1: Add `RNJWPlayerUseGoogleCast = true` to your *app/build.gradle* in `ext {}`, this will add `com.google.android.gms:play-services-cast-framework:21.3.0`. #### Available methods diff --git a/RNJWPlayer.podspec b/RNJWPlayer.podspec index 5fa417bd..df0990f8 100644 --- a/RNJWPlayer.podspec +++ b/RNJWPlayer.podspec @@ -13,8 +13,8 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/chaimPaneth/react-native-jw-media-player.git", :tag => "v#{s.version}" } s.source_files = "ios/RNJWPlayer/*.{h,m,swift}" s.dependency 'JWPlayerKit', '~> 4.17.0' - s.dependency 'google-cast-sdk', '~> 4.8' 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.', @@ -22,9 +22,23 @@ Pod::Spec.new do |s| '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.static_framework = true s.xcconfig = { 'OTHER_LDFLAGS': '-ObjC', } + + if defined?($RNJWPlayerUseGoogleCast) + Pod::UI.puts "RNJWPlayer: enable Google Cast" + s.dependency 'google-cast-sdk', '~> 4.5.3' + s.pod_target_xcconfig = { + 'OTHER_SWIFT_FLAGS' => '$(inherited) -D USE_GOOGLE_CAST' + } + end + if defined?($RNJWPlayerUseGoogleIMA) + Pod::UI.puts "RNJWPlayer: enable IMA SDK" + s.dependency 'GoogleAds-IMA-iOS-SDK', '~>3.19.1' + s.pod_target_xcconfig = { + 'OTHER_SWIFT_FLAGS' => '$(inherited) -D USE_GOOGLE_IMA' + } + end end diff --git a/android/build.gradle b/android/build.gradle index f9c64788..6ed020d0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -15,6 +15,9 @@ def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } +def useIMA = safeExtGet("RNJWPlayerUseGoogleIMA", "")?.toBoolean() ?: false +def useCast = safeExtGet("RNJWPlayerUseGoogleCast", "")?.toBoolean() ?: false + android { compileSdkVersion safeExtGet('compileSdkVersion', 28) buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') @@ -28,6 +31,27 @@ android { abiFilters "armeabi-v7a", "x86" } } + sourceSets { + main { + if (useCast) { + manifest.srcFile 'src/main/xml/cast_enabled_manifest.xml' + } + } + } + buildTypes { + debug { + // Set the build config fields for the debug build type + buildConfigField "boolean", "USE_IMA", useIMA.toString() + buildConfigField "boolean", "USE_CAST", useCast.toString() + // ... other debug configurations + } + release { + // Set the build config fields for the release build type + buildConfigField "boolean", "USE_IMA", useIMA.toString() + buildConfigField "boolean", "USE_CAST", useCast.toString() + // ... other release configurations + } + } lintOptions { warning 'InvalidPackage' } @@ -58,13 +82,19 @@ dependencies { // JWPlayer SDK implementation "com.jwplayer:jwplayer-core:${safeExtGet('jwPlayerVersion', jwPlayerVersion)}" implementation "com.jwplayer:jwplayer-common:${safeExtGet('jwPlayerVersion', jwPlayerVersion)}" - implementation "com.jwplayer:jwplayer-chromecast:${safeExtGet('jwPlayerVersion', jwPlayerVersion)}" implementation "com.jwplayer:jwplayer-ima:${safeExtGet('jwPlayerVersion', jwPlayerVersion)}" + implementation "com.jwplayer:jwplayer-chromecast:${safeExtGet('jwPlayerVersion', jwPlayerVersion)}" // Ad dependencies - implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.29.0' - implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' - implementation 'com.google.android.gms:play-services-cast-framework:21.3.0' + if (useIMA) { + implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.29.0' + implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' + } + + // Cast dependencies + if (useCast) { + implementation 'com.google.android.gms:play-services-cast-framework:21.3.0' + } // ExoPlayer dependencies implementation "com.google.android.exoplayer:exoplayer-core:${safeExtGet('exoplayerVersion', exoplayerVersion)}" diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 35b51fa6..15636e26 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + - diff --git a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerAds.java b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerAds.java new file mode 100644 index 00000000..ebe18d4f --- /dev/null +++ b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerAds.java @@ -0,0 +1,239 @@ +package com.appgoalz.rnjwplayer; + +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.google.ads.interactivemedia.v3.api.FriendlyObstruction; +import com.google.ads.interactivemedia.v3.api.ImaSdkFactory; +import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; +import com.jwplayer.pub.api.configuration.ads.AdRules; +import com.jwplayer.pub.api.configuration.ads.AdvertisingConfig; +import com.jwplayer.pub.api.configuration.ads.VastAdvertisingConfig; +import com.jwplayer.pub.api.configuration.ads.dai.ImaDaiAdvertisingConfig; +import com.jwplayer.pub.api.configuration.ads.ima.ImaAdvertisingConfig; +import com.jwplayer.pub.api.media.ads.AdBreak; +import com.jwplayer.pub.api.media.ads.dai.ImaDaiSettings; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class RNJWPlayerAds { + + // Get advertising config based on the ad client type + public static AdvertisingConfig getAdvertisingConfig(ReadableMap ads) { + if (ads == null) { + return null; + } + + String adClientType = ads.getString("adClient"); + switch (adClientType) { + case "ima": + try { + return configureImaAdvertising(ads); + } catch (Exception e) { + throw new RuntimeException(e); + } + case "ima_dai": + try { + return configureImaDaiAdvertising(ads); + } catch (Exception e) { + throw new RuntimeException(e); + } + default: // Defaulting to VAST + return configureVastAdvertising(ads); + } + } + + // Configure IMA Advertising + private static ImaAdvertisingConfig configureImaAdvertising(ReadableMap ads) throws Exception { + if (!BuildConfig.USE_IMA) { + throw new Exception("Error: Google ads services is not installed. Add RNJWPlayerUseGoogleIMA = true to your app/build.gradle ext {}"); + } + + ImaAdvertisingConfig.Builder builder = new ImaAdvertisingConfig.Builder(); + + List adScheduleList = getAdSchedule(ads); + builder.schedule(adScheduleList); + + if (ads.hasKey("imaSettings")) { + builder.imaSdkSettings(getImaSettings(Objects.requireNonNull(ads.getMap("imaSettings")))); + } + + // companionSlots + + return builder.build(); + } + + // Configure IMA DAI Advertising + private static ImaDaiAdvertisingConfig configureImaDaiAdvertising(ReadableMap ads) throws Exception { + if (!BuildConfig.USE_IMA) { + throw new Exception("Error: Google ads services is not installed. Add RNJWPlayerUseGoogleIMA = true to your app/build.gradle ext {}"); + } + + ImaDaiAdvertisingConfig.Builder builder = new ImaDaiAdvertisingConfig.Builder(); + + if (ads.hasKey("imaSettings")) { + builder.imaSdkSettings(getImaSettings(Objects.requireNonNull(ads.getMap("imaSettings")))); + } + + if (ads.hasKey("imaDaiSettings")) { + builder.imaDaiSettings(getImaDaiSettings(Objects.requireNonNull(ads.getMap("imaDaiSettings")))); + } + + return builder.build(); + } + + // You'll need to implement this method based on how you pass ImaDaiSettings from React Native + private static ImaDaiSettings getImaDaiSettings(ReadableMap imaDaiSettingsMap) { + String videoId = imaDaiSettingsMap.hasKey("videoId") ? imaDaiSettingsMap.getString("videoId") : null; + String cmsId = imaDaiSettingsMap.hasKey("cmsId") ? imaDaiSettingsMap.getString("cmsId") : null; + String assetKey = imaDaiSettingsMap.hasKey("assetKey") ? imaDaiSettingsMap.getString("assetKey") : null; + String apiKey = imaDaiSettingsMap.hasKey("apiKey") ? imaDaiSettingsMap.getString("apiKey") : null; + + // Extracting adTagParameters from imaDaiSettingsMap if present + Map adTagParameters = null; + if (imaDaiSettingsMap.hasKey("adTagParameters") && imaDaiSettingsMap.getMap("adTagParameters") != null) { + adTagParameters = new HashMap<>(); + ReadableMap adTagParamsMap = imaDaiSettingsMap.getMap("adTagParameters"); + for (Map.Entry entry : adTagParamsMap.toHashMap().entrySet()) { + if (entry.getValue() instanceof String) { + adTagParameters.put(entry.getKey(), (String) entry.getValue()); + } + } + } + + // Handling streamType + ImaDaiSettings.StreamType streamType = ImaDaiSettings.StreamType.HLS; // Default to HLS + if (imaDaiSettingsMap.hasKey("streamType")) { + String streamTypeStr = imaDaiSettingsMap.getString("streamType"); + if ("DASH".equalsIgnoreCase(streamTypeStr)) { + streamType = ImaDaiSettings.StreamType.DASH; + } + } + // Create ImaDaiSettings based on the provided values + ImaDaiSettings imaDaiSettings = (assetKey != null) ? + new ImaDaiSettings(assetKey, streamType, apiKey) : + new ImaDaiSettings(videoId, cmsId, streamType, apiKey); + + if (adTagParameters != null) { + imaDaiSettings.setAdTagParameters(adTagParameters); + } + + return imaDaiSettings; + } + + // Configure VAST Advertising + private static VastAdvertisingConfig configureVastAdvertising(ReadableMap ads) { + VastAdvertisingConfig.Builder builder = new VastAdvertisingConfig.Builder(); + + List adScheduleList = getAdSchedule(ads); + builder.schedule(adScheduleList); + + if (ads.hasKey("skipText")) { + builder.skipText(ads.getString("skipText")); + } + if (ads.hasKey("skipMessage")) { + builder.skipMessage(ads.getString("skipMessage")); + } + // ... Add other VAST specific settings from ads ReadableMap + + // Example: Handling VPAID controls + if (ads.hasKey("vpaidControls")) { + builder.vpaidControls(ads.getBoolean("vpaidControls")); + } + + if (ads.hasKey("adRules")) { + AdRules adRules = getAdRules(Objects.requireNonNull(ads.getMap("adRules"))); + builder.adRules(adRules); + } + + return builder.build(); + } + + private static List getAdSchedule(ReadableMap ads) { + List adScheduleList = new ArrayList<>(); + ReadableArray adSchedule = ads.getArray("adSchedule"); + for (int i = 0; i < adSchedule.size(); i++) { + ReadableMap adBreakProp = adSchedule.getMap(i); + String offset = adBreakProp.hasKey("offset") ? adBreakProp.getString("offset") : "pre"; + if (adBreakProp.hasKey("tag")) { + AdBreak adBreak = new AdBreak.Builder() + .offset(offset) + .tag(adBreakProp.getString("tag")) + .build(); + adScheduleList.add(adBreak); + } + } + return adScheduleList; + } + + public static AdRules getAdRules(ReadableMap adRulesMap) { + AdRules.Builder builder = new AdRules.Builder(); + + if (adRulesMap.hasKey("startOn")) { + Integer startOn = adRulesMap.getInt("startOn"); + builder.startOn(startOn); + } + if (adRulesMap.hasKey("frequency")) { + Integer frequency = adRulesMap.getInt("frequency"); + builder.frequency(frequency); + } + if (adRulesMap.hasKey("timeBetweenAds")) { + Integer timeBetweenAds = adRulesMap.getInt("timeBetweenAds"); + builder.timeBetweenAds(timeBetweenAds); + } + if (adRulesMap.hasKey("startOnSeek")) { + String startOnSeek = adRulesMap.getString("startOnSeek"); + // Mapping the string to the corresponding constant in AdRules + String mappedStartOnSeek = mapStartOnSeek(startOnSeek); + builder.startOnSeek(mappedStartOnSeek); + } + + return builder.build(); + } + + private static String mapStartOnSeek(String startOnSeek) { + if ("pre".equals(startOnSeek)) { + return AdRules.RULES_START_ON_SEEK_PRE; + } + // Default to "none" if not "pre" + return AdRules.RULES_START_ON_SEEK_NONE; + } + +// public static List getFriendlyObstructions(ReadableArray obstructionsArray) { +// List obstructions = new ArrayList<>(); +// // Example: Parse and create FriendlyObstruction objects from obstructionsArray +// return obstructions; +// } + + public static ImaSdkSettings getImaSettings(ReadableMap imaSettingsMap) { + ImaSdkSettings settings = ImaSdkFactory.getInstance().createImaSdkSettings(); + + if (imaSettingsMap.hasKey("maxRedirects")) { + settings.setMaxRedirects(imaSettingsMap.getInt("maxRedirects")); + } + if (imaSettingsMap.hasKey("language")) { + settings.setLanguage(imaSettingsMap.getString("language")); + } + if (imaSettingsMap.hasKey("ppid")) { + settings.setPpid(imaSettingsMap.getString("ppid")); + } + if (imaSettingsMap.hasKey("playerType")) { + settings.setPlayerType(imaSettingsMap.getString("playerType")); + } + if (imaSettingsMap.hasKey("playerVersion")) { + settings.setPlayerVersion(imaSettingsMap.getString("playerVersion")); + } + if (imaSettingsMap.hasKey("sessionId")) { + settings.setSessionId(imaSettingsMap.getString("sessionId")); + } + if (imaSettingsMap.hasKey("debugMode")) { + settings.setDebugMode(imaSettingsMap.getBoolean("debugMode")); + } + // Add other settings as needed + + return settings; + } +} diff --git a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerModule.java b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerModule.java index 8f6868e4..732c9284 100644 --- a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerModule.java +++ b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerModule.java @@ -426,15 +426,17 @@ public void execute (NativeViewHierarchyManager nvhm) { if (playerView != null && playerView.mPlayer != null) { List audioTrackList = playerView.mPlayer.getAudioTracks(); WritableArray audioTracks = Arguments.createArray(); - for (int i = 0; i < audioTrackList.size(); i++) { - WritableMap audioTrack = Arguments.createMap(); - AudioTrack track = audioTrackList.get(i); - audioTrack.putString("name", track.getName()); - audioTrack.putString("language", track.getLanguage()); - audioTrack.putString("groupId", track.getGroupId()); - audioTrack.putBoolean("defaultTrack", track.isDefaultTrack()); - audioTrack.putBoolean("autoSelect", track.isAutoSelect()); - audioTracks.pushMap(audioTrack); + if (audioTrackList != null) { + for (int i = 0; i < audioTrackList.size(); i++) { + WritableMap audioTrack = Arguments.createMap(); + AudioTrack track = audioTrackList.get(i); + audioTrack.putString("name", track.getName()); + audioTrack.putString("language", track.getLanguage()); + audioTrack.putString("groupId", track.getGroupId()); + audioTrack.putBoolean("defaultTrack", track.isDefaultTrack()); + audioTrack.putBoolean("autoSelect", track.isAutoSelect()); + audioTracks.pushMap(audioTrack); + } } promise.resolve(audioTracks); } else { diff --git a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java index 8706d4de..aadc49ff 100755 --- a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java +++ b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerView.java @@ -40,12 +40,26 @@ import com.jwplayer.pub.api.configuration.PlayerConfig; import com.jwplayer.pub.api.configuration.UiConfig; import com.jwplayer.pub.api.configuration.ads.AdvertisingConfig; -import com.jwplayer.pub.api.configuration.ads.VastAdvertisingConfig; -import com.jwplayer.pub.api.configuration.ads.VmapAdvertisingConfig; -import com.jwplayer.pub.api.configuration.ads.dai.ImaDaiAdvertisingConfig; -import com.jwplayer.pub.api.configuration.ads.ima.ImaAdvertisingConfig; +import com.jwplayer.pub.api.events.AdBreakEndEvent; +import com.jwplayer.pub.api.events.AdBreakIgnoredEvent; +import com.jwplayer.pub.api.events.AdBreakStartEvent; +import com.jwplayer.pub.api.events.AdClickEvent; +import com.jwplayer.pub.api.events.AdCompanionsEvent; +import com.jwplayer.pub.api.events.AdCompleteEvent; +import com.jwplayer.pub.api.events.AdErrorEvent; +import com.jwplayer.pub.api.events.AdImpressionEvent; +import com.jwplayer.pub.api.events.AdLoadedEvent; +import com.jwplayer.pub.api.events.AdLoadedXmlEvent; +import com.jwplayer.pub.api.events.AdMetaEvent; import com.jwplayer.pub.api.events.AdPauseEvent; import com.jwplayer.pub.api.events.AdPlayEvent; +import com.jwplayer.pub.api.events.AdRequestEvent; +import com.jwplayer.pub.api.events.AdScheduleEvent; +import com.jwplayer.pub.api.events.AdSkippedEvent; +import com.jwplayer.pub.api.events.AdStartedEvent; +import com.jwplayer.pub.api.events.AdTimeEvent; +import com.jwplayer.pub.api.events.AdViewableImpressionEvent; +import com.jwplayer.pub.api.events.AdWarningEvent; import com.jwplayer.pub.api.events.AudioTrackChangedEvent; import com.jwplayer.pub.api.events.AudioTracksEvent; import com.jwplayer.pub.api.events.BeforeCompleteEvent; @@ -68,6 +82,7 @@ import com.jwplayer.pub.api.events.PipCloseEvent; import com.jwplayer.pub.api.events.PipOpenEvent; import com.jwplayer.pub.api.events.PlayEvent; +import com.jwplayer.pub.api.events.PlaybackRateChangedEvent; import com.jwplayer.pub.api.events.PlaylistCompleteEvent; import com.jwplayer.pub.api.events.PlaylistEvent; import com.jwplayer.pub.api.events.PlaylistItemEvent; @@ -82,11 +97,6 @@ import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents; import com.jwplayer.pub.api.fullscreen.FullscreenHandler; import com.jwplayer.pub.api.license.LicenseUtil; -import com.jwplayer.pub.api.media.ads.AdBreak; -import com.jwplayer.pub.api.media.ads.AdClient; -import com.jwplayer.pub.api.media.captions.Caption; -import com.jwplayer.pub.api.media.captions.CaptionType; -import com.jwplayer.pub.api.media.playlists.MediaSource; import com.jwplayer.pub.api.media.playlists.PlaylistItem; import com.jwplayer.ui.views.CueMarkerSeekbar; @@ -118,34 +128,38 @@ public class RNJWPlayerView extends RelativeLayout implements VideoPlayerEvents.OnFirstFrameListener, VideoPlayerEvents.OnSeekListener, VideoPlayerEvents.OnSeekedListener, + VideoPlayerEvents.OnPlaybackRateChangedListener, VideoPlayerEvents.OnCaptionsListListener, VideoPlayerEvents.OnCaptionsChangedListener, VideoPlayerEvents.OnMetaListener, - AdvertisingEvents.OnBeforePlayListener, - AdvertisingEvents.OnBeforeCompleteListener, - AdvertisingEvents.OnAdPauseListener, - AdvertisingEvents.OnAdPlayListener, - CastingEvents.OnCastListener, PipPluginEvents.OnPipCloseListener, PipPluginEvents.OnPipOpenListener, -// AdvertisingEvents.OnAdRequestListener, -// AdvertisingEvents.OnAdScheduleListener, -// AdvertisingEvents.OnAdStartedListener, -// AdvertisingEvents.OnAdBreakStartListener, -// AdvertisingEvents.OnAdBreakEndListener, -// AdvertisingEvents.OnAdClickListener, -// AdvertisingEvents.OnAdCompleteListener, -// AdvertisingEvents.OnAdCompanionsListener, -// AdvertisingEvents.OnAdErrorListener, -// AdvertisingEvents.OnAdImpressionListener, -// AdvertisingEvents.OnAdMetaListener, -// AdvertisingEvents.OnAdSkippedListener, -// AdvertisingEvents.OnAdTimeListener, -// AdvertisingEvents.OnAdViewableImpressionListener, + AdvertisingEvents.OnBeforePlayListener, + AdvertisingEvents.OnBeforeCompleteListener, + AdvertisingEvents.OnAdPauseListener, + AdvertisingEvents.OnAdPlayListener, + AdvertisingEvents.OnAdRequestListener, + AdvertisingEvents.OnAdScheduleListener, + AdvertisingEvents.OnAdStartedListener, + AdvertisingEvents.OnAdBreakStartListener, + AdvertisingEvents.OnAdBreakEndListener, + AdvertisingEvents.OnAdClickListener, + AdvertisingEvents.OnAdCompleteListener, + AdvertisingEvents.OnAdCompanionsListener, + AdvertisingEvents.OnAdErrorListener, + AdvertisingEvents.OnAdImpressionListener, + AdvertisingEvents.OnAdMetaListener, + AdvertisingEvents.OnAdSkippedListener, + AdvertisingEvents.OnAdTimeListener, + AdvertisingEvents.OnAdViewableImpressionListener, + AdvertisingEvents.OnAdBreakIgnoredListener, + AdvertisingEvents.OnAdWarningListener, + AdvertisingEvents.OnAdLoadedListener, + AdvertisingEvents.OnAdLoadedXmlListener, AudioManager.OnAudioFocusChangeListener, @@ -297,14 +311,34 @@ public void destroyPlayer() { EventType.FULLSCREEN, EventType.SEEK, EventType.SEEKED, + EventType.PLAYBACK_RATE_CHANGED, EventType.CAPTIONS_LIST, EventType.CAPTIONS_CHANGED, EventType.META, + // Ad events EventType.BEFORE_PLAY, EventType.BEFORE_COMPLETE, - EventType.AD_PLAY, + EventType.AD_BREAK_START, + EventType.AD_BREAK_END, + EventType.AD_BREAK_IGNORED, + EventType.AD_CLICK, + EventType.AD_COMPANIONS, + EventType.AD_COMPLETE, + EventType.AD_ERROR, + EventType.AD_IMPRESSION, + EventType.AD_WARNING, + EventType.AD_LOADED, + EventType.AD_LOADED_XML, + EventType.AD_META, EventType.AD_PAUSE, + EventType.AD_PLAY, + EventType.AD_REQUEST, + EventType.AD_SCHEDULE, + EventType.AD_SKIPPED, + EventType.AD_STARTED, + EventType.AD_TIME, + EventType.AD_VIEWABLE_IMPRESSION, // Cast event EventType.CAST, // Pip events @@ -357,14 +391,33 @@ public void setupPlayerView(Boolean backgroundAudioEnabled) { EventType.FULLSCREEN, EventType.SEEK, EventType.SEEKED, + EventType.PLAYBACK_RATE_CHANGED, EventType.CAPTIONS_LIST, EventType.CAPTIONS_CHANGED, EventType.META, // Ad events EventType.BEFORE_PLAY, EventType.BEFORE_COMPLETE, - EventType.AD_PLAY, + EventType.AD_BREAK_START, + EventType.AD_BREAK_END, + EventType.AD_BREAK_IGNORED, + EventType.AD_CLICK, + EventType.AD_COMPANIONS, + EventType.AD_COMPLETE, + EventType.AD_ERROR, + EventType.AD_IMPRESSION, + EventType.AD_WARNING, + EventType.AD_LOADED, + EventType.AD_LOADED_XML, + EventType.AD_META, EventType.AD_PAUSE, + EventType.AD_PLAY, + EventType.AD_REQUEST, + EventType.AD_SCHEDULE, + EventType.AD_SKIPPED, + EventType.AD_STARTED, + EventType.AD_TIME, + EventType.AD_VIEWABLE_IMPRESSION, // Cast event EventType.CAST, // Pip events @@ -619,55 +672,10 @@ private void setupPlayer(ReadableMap prop) { } } - List adScheduleList = new ArrayList<>(); - AdClient client; - AdvertisingConfig advertisingConfig; - if (prop.hasKey("advertising")) { ReadableMap ads = prop.getMap("advertising"); - if (ads != null && ads.hasKey("adSchedule")) { - ReadableArray adSchedule = ads.getArray("adSchedule"); - for(int i = 0; i < adSchedule.size(); i++){ - ReadableMap adBreakProp = adSchedule.getMap(i); - String offset = adBreakProp.hasKey("offset") ? adBreakProp.getString("offset") : "pre"; - if(adBreakProp.hasKey("tag")){ - AdBreak adBreak = new AdBreak.Builder().offset(offset).tag(adBreakProp.getString("tag")).build(); - adScheduleList.add(adBreak); - } - } - - if (ads.hasKey("adClient") && - ads.getString("adClient") != null && - CLIENT_TYPES.get(ads.getString("adClient")) != null) { - Integer clientType = CLIENT_TYPES.get(ads.getString("adClient")); - switch (clientType) { - case 1: - client = AdClient.IMA; - advertisingConfig = new ImaAdvertisingConfig.Builder().schedule(adScheduleList).build(); - break; - case 2: - client = AdClient.IMA_DAI; - advertisingConfig = new ImaDaiAdvertisingConfig.Builder().build(); - break; - default: - client = AdClient.VAST; - advertisingConfig = new VastAdvertisingConfig.Builder() - .schedule(adScheduleList) - .build(); - break; - } - } else { - client = AdClient.VAST; - advertisingConfig = new VastAdvertisingConfig.Builder() - .schedule(adScheduleList) - .build(); - } - - configBuilder.advertisingConfig(advertisingConfig); - } else if (ads != null && ads.hasKey("adVmap")) { - String adVmap = ads.getString("adVmap"); - advertisingConfig = new VmapAdvertisingConfig.Builder().tag(adVmap).build(); - + AdvertisingConfig advertisingConfig = RNJWPlayerAds.getAdvertisingConfig(ads); + if (advertisingConfig != null) { configBuilder.advertisingConfig(advertisingConfig); } } @@ -943,20 +951,180 @@ private void updateWakeLock(boolean enable) { } } - // AdEvents - + // Ad events + + @Override + public void onAdLoaded(AdLoadedEvent adLoadedEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adLoadedEvent.getClient().toString()); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdLoadedXml(AdLoadedXmlEvent adLoadedXmlEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adLoadedXmlEvent.getClient().toString()); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + @Override public void onAdPause(AdPauseEvent adPauseEvent) { WritableMap event = Arguments.createMap(); - event.putString("message", "onAdPause"); - getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdPause", event); + event.putString("message", "onAdEvent"); + event.putString("reason", adPauseEvent.getAdPauseReason().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypePause)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); } @Override public void onAdPlay(AdPlayEvent adPlayEvent) { WritableMap event = Arguments.createMap(); - event.putString("message", "onAdPlay"); - getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdPlay", event); + event.putString("message", "onAdEvent"); + event.putString("reason", adPlayEvent.getAdPlayReason().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypePlay)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdBreakEnd(AdBreakEndEvent adBreakEndEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adBreakEndEvent.getClient().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeAdBreakEnd)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdBreakStart(AdBreakStartEvent adBreakStartEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adBreakStartEvent.getClient().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeAdBreakStart)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdBreakIgnored(AdBreakIgnoredEvent adBreakIgnoredEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adBreakIgnoredEvent.getClient().toString()); + // missing type code + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdClick(AdClickEvent adClickEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adClickEvent.getClient().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeClicked)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdCompanions(AdCompanionsEvent adCompanionsEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeCompanion)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdComplete(AdCompleteEvent adCompleteEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adCompleteEvent.getClient().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeComplete)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdError(AdErrorEvent adErrorEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onPlayerAdError"); + event.putInt("code", adErrorEvent.getCode()); + event.putInt("adErrorCode", adErrorEvent.getAdErrorCode()); + event.putString("error", adErrorEvent.getMessage()); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topPlayerAdError", event); + } + + @Override + public void onAdWarning(AdWarningEvent adWarningEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onPlayerAdWarning"); + event.putInt("code", adWarningEvent.getCode()); + event.putInt("adErrorCode", adWarningEvent.getAdErrorCode()); + event.putString("error", adWarningEvent.getMessage()); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topPlayerAdWarning", event); + } + + @Override + public void onAdImpression(AdImpressionEvent adImpressionEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adImpressionEvent.getClient().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeImpression)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdMeta(AdMetaEvent adMetaEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adMetaEvent.getClient().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeMeta)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdRequest(AdRequestEvent adRequestEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adRequestEvent.getClient().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeRequest)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdSchedule(AdScheduleEvent adScheduleEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adScheduleEvent.getClient().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeSchedule)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdSkipped(AdSkippedEvent adSkippedEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putString("client", adSkippedEvent.getClient().toString()); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeSkipped)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdStarted(AdStartedEvent adStartedEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdEvent"); + event.putInt("type", Util.getEventTypeValue(Util.AdEventType.JWAdEventTypeStarted)); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdEvent", event); + } + + @Override + public void onAdTime(AdTimeEvent adTimeEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onAdTime"); + event.putDouble("position", adTimeEvent.getPosition()); + event.putDouble("duration", adTimeEvent.getDuration()); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topAdTime", event); + } + + @Override + public void onAdViewableImpression(AdViewableImpressionEvent adViewableImpressionEvent) { + // send everything? } @Override @@ -965,7 +1133,6 @@ public void onBeforeComplete(BeforeCompleteEvent beforeCompleteEvent) { event.putString("message", "onBeforeComplete"); getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topBeforeComplete", event); - updateWakeLock(false); } @@ -1168,6 +1335,14 @@ public void onSeeked(SeekedEvent seekedEvent) { getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topSeeked", event); } + @Override + public void onPlaybackRateChanged(PlaybackRateChangedEvent playbackRateChangedEvent) { + WritableMap event = Arguments.createMap(); + event.putString("message", "onRateChanged"); + event.putDouble("rate", playbackRateChangedEvent.getPlaybackRate()); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topRateChanged", event); + } + @Override public void onSetupError(SetupErrorEvent setupErrorEvent) { WritableMap event = Arguments.createMap(); diff --git a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerViewManager.java b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerViewManager.java index 2b0b8b3d..34ed3b7c 100644 --- a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerViewManager.java +++ b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerViewManager.java @@ -55,6 +55,22 @@ public Map getExportedCustomBubblingEventTypeConstants() { MapBuilder.of( "phasedRegistrationNames", MapBuilder.of("bubbled", "onSetupPlayerError"))) + .put("topPlayerAdError", + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", "onPlayerAdError"))) + .put("topPlayerAdWarning", + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", "onPlayerAdWarning"))) + .put("topAdEvent", + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", "onAdEvent"))) + .put("topAdTime", + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", "onAdTime"))) .put("topTime", MapBuilder.of( "phasedRegistrationNames", @@ -107,6 +123,10 @@ public Map getExportedCustomBubblingEventTypeConstants() { MapBuilder.of( "phasedRegistrationNames", MapBuilder.of("bubbled", "onSeeked"))) + .put("topRateChanged", + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", "onRateChanged"))) .put("topControlBarVisible", MapBuilder.of( "phasedRegistrationNames", diff --git a/android/src/main/java/com/appgoalz/rnjwplayer/Util.java b/android/src/main/java/com/appgoalz/rnjwplayer/Util.java index 6cff8a03..fefe40f2 100644 --- a/android/src/main/java/com/appgoalz/rnjwplayer/Util.java +++ b/android/src/main/java/com/appgoalz/rnjwplayer/Util.java @@ -185,4 +185,35 @@ public static PlaylistItem getPlaylistItem (ReadableMap playlistItem) { return itemBuilder.build(); } + + public enum AdEventType { + JWAdEventTypeAdBreakEnd(0), + JWAdEventTypeAdBreakStart(1), + JWAdEventTypeClicked(2), + JWAdEventTypeComplete(3), + JWAdEventTypeImpression(4), + JWAdEventTypeMeta(5), + JWAdEventTypePause(6), + JWAdEventTypePlay(7), + JWAdEventTypeRequest(8), + JWAdEventTypeSchedule(9), + JWAdEventTypeSkipped(10), + JWAdEventTypeStarted(11), + JWAdEventTypeCompanion(12); + + private final int value; + + AdEventType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + // Method to get the event type value + public static int getEventTypeValue(AdEventType eventType) { + return eventType.getValue(); + } } \ No newline at end of file diff --git a/android/src/main/xml/cast_enabled_manifest.xml b/android/src/main/xml/cast_enabled_manifest.xml new file mode 100644 index 00000000..2bbcbcf6 --- /dev/null +++ b/android/src/main/xml/cast_enabled_manifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index 9ebe6af9..42f020b6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -33,18 +33,70 @@ declare module "react-native-jw-media-player" { label: string; default?: boolean; } + interface JWAdSettings { + allowsBackgroundPlayback?: boolean; + // Add other ad settings properties as needed + } + interface IMASettings { + locale?: string; + ppid?: string; + maxRedirects?: number; + sessionID?: string; + debugMode?: boolean; + } interface AdSchedule { tag: string; offset: string; } + // interface CompanionAdSlot { + // viewId: string; // Reference to a UIView in the application + // size?: { width: number; height: number }; + // } + interface GoogleDAIStream { + videoID?: string; + cmsID?: string; + assetKey?: string; + apiKey?: string; + adTagParameters?: { [key: string]: string }; + } + interface AdRule { + startOn: number; + frequency: number; + timeBetweenAds: number; + startOnSeek: 'none' | 'pre'; // Mapped from JWAdShownOnSeek + } + // interface FriendlyObstruction { + // viewId: string; + // purpose: 'mediaControls' | 'closeAd' | 'notVisible' | 'other'; // Mapped from JWFriendlyObstructionPurpose + // reason?: string; + // } type ClientTypes = "vast" | "ima" | "ima_dai"; - interface Advertising { + interface VASTAdvertising { adSchedule?: AdSchedule[]; adVmap?: string; - tag?: string; + tag?: string; // Vast xml url openBrowserOnAdClick?: boolean; - adClient?: ClientTypes; + adClient: "vast"; + adRules?: AdRule; + adSettings?: JWAdSettings; } + interface IMAAdvertising { + adSchedule?: AdSchedule[]; + adVmap?: string; + tag?: string; // Vast xml url + adClient: "ima"; + adRules?: AdRule; + imaSettings?: IMASettings; + // companionAdSlots?: CompanionAdSlot[]; + // friendlyObstructions?: FriendlyObstruction[]; + } + interface IMA_DAIAdvertising { + adClient: "ima_dai"; + imaSettings?: IMASettings; + // friendlyObstructions?: FriendlyObstruction[]; + googleDAIStream?: GoogleDAIStream; + } + type Advertising = VASTAdvertising | IMAAdvertising | IMA_DAIAdvertising; interface PlaylistItem { file: string; sources?: Source[]; @@ -180,31 +232,79 @@ declare module "react-native-jw-media-player" { enableLockScreenControls: boolean; pipEnabled: boolean; } + 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; - onSetupPlayerError?: (setupPlayerError: { error: string }) => void; - onPlayerError?: (playerError: { error: string }) => 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; - onPlaylistItem?: (playlistItem: PlaylistItem) => void; - onControlBarVisible?: (event: any) => void; - onPlaylistComplete?: (event: any) => void; - onAudioTracks?: (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: 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 4800c68a..99a02c78 100644 --- a/index.js +++ b/index.js @@ -38,6 +38,35 @@ const JWPlayerStateAndroid = { JWPlayerStateError: null, }; +export const JWPlayerAdEvents = { + /// This event is reported when the ad break has come to an end. + JWAdEventTypeAdBreakEnd: 0, + /// This event is reported when the ad break has begun. + JWAdEventTypeAdBreakStart: 1, + /// This event is reported when the user taps the ad. + JWAdEventTypeClicked: 2, + /// This event is reported when the ad is done playing. + JWAdEventTypeComplete: 3, + /// This event is used to report the ad impression, supplying additional detailed information about the ad. + JWAdEventTypeImpression: 4, + /// This event reports meta data information associated with the ad. + JWAdEventTypeMeta: 5, + /// The event is reported when the ad pauses. + JWAdEventTypePause: 6, + /// This event is reported when the ad begins playing, even in the middle of the stream after it was paused. + JWAdEventTypePlay: 7, + /// The event reports data about the ad request, when the ad is about to be loaded. + JWAdEventTypeRequest: 8, + /// This event reports the schedule of ads across the currently playing content. + JWAdEventTypeSchedule: 9, + /// This event is reported when the user skips the ad. + JWAdEventTypeSkipped: 10, + /// This event is reported when the ad begins. + JWAdEventTypeStarted: 11, + /// This event relays information about ad companions. + JWAdEventTypeCompanion: 12, +} + export const JWPlayerState = Platform.OS === 'ios' ? JWPlayerStateIOS : JWPlayerStateAndroid; @@ -48,6 +77,98 @@ export const JWPlayerAdClients = { JWAdClientUnknown: 3, }; +// Common PropTypes for imaSettings and adRules +const imaSettingsPropTypes = PropTypes.shape({ + locale: PropTypes.string, + ppid: PropTypes.string, + maxRedirects: PropTypes.number, + sessionID: PropTypes.string, + debugMode: PropTypes.bool, +}); + +const adRulesPropTypes = PropTypes.shape({ + startOn: PropTypes.number, + frequency: PropTypes.number, + timeBetweenAds: PropTypes.number, + startOnSeek: PropTypes.oneOf(['none', 'pre']), +}); + +const adSettingsPropTypes = PropTypes.shape({ + allowsBackgroundPlayback: PropTypes.bool, + // Include other ad settings properties here +}); + +const adSchedulePropTypes = PropTypes.arrayOf( + PropTypes.shape({ + tag: PropTypes.string, + offset: PropTypes.string, + }) +); + +// Define PropTypes for each ad client type +const vastAdvertisingPropTypes = { + adClient: PropTypes.oneOf(['vast']), + adSchedule: adSchedulePropTypes, + adVmap: PropTypes.string, + tag: PropTypes.string, + openBrowserOnAdClick: PropTypes.bool, + adRules: adRulesPropTypes, + adSettings: adSettingsPropTypes, + // Add other VAST-specific properties here +}; + +const imaAdvertisingPropTypes = { + adClient: PropTypes.oneOf(['ima']), + adSchedule: adSchedulePropTypes, + adVmap: PropTypes.string, + tag: PropTypes.string, + imaSettings: imaSettingsPropTypes, + adRules: adRulesPropTypes, + // companionAdSlots: PropTypes.arrayOf( + // PropTypes.shape({ + // viewId: PropTypes.string, + // size: PropTypes.shape({ + // width: PropTypes.number, + // height: PropTypes.number, + // }), + // }) + // ), + // friendlyObstructions: PropTypes.arrayOf( + // PropTypes.shape({ + // viewId: PropTypes.string, + // purpose: PropTypes.oneOf(['mediaControls', 'closeAd', 'notVisible', 'other']), + // reason: PropTypes.string, + // }) + // ), + // Add other IMA-specific properties here +}; + +const imaDaiAdvertisingPropTypes = { + adClient: PropTypes.oneOf(['ima_dai']), + imaSettings: imaSettingsPropTypes, + googleDAIStream: PropTypes.shape({ + videoID: PropTypes.string, + cmsID: PropTypes.string, + assetKey: PropTypes.string, + apiKey: PropTypes.string, + adTagParameters: PropTypes.object, + }), + // friendlyObstructions: PropTypes.arrayOf( + // PropTypes.shape({ + // viewId: PropTypes.string, + // purpose: PropTypes.oneOf(['mediaControls', 'closeAd', 'notVisible', 'other']), + // reason: PropTypes.string, + // }) + // ), + // Add other IMA DAI-specific properties here +}; + +const advertisingPropTypes = PropTypes.oneOfType([ + PropTypes.shape(vastAdvertisingPropTypes), + PropTypes.shape(imaAdvertisingPropTypes), + PropTypes.shape(imaDaiAdvertisingPropTypes), +]); + export default class JWPlayer extends Component { static propTypes = { config: PropTypes.shape({ @@ -122,18 +243,7 @@ export default class JWPlayer extends Component { startTime: PropTypes.number, }) ), - advertising: PropTypes.shape({ - adClient: PropTypes.string, - adSchedule: PropTypes.arrayOf( - PropTypes.shape({ - tag: PropTypes.string, - offset: PropTypes.string, - }) - ), - adVmap: PropTypes.string, - tag: PropTypes.string, - openBrowserOnAdClick: PropTypes.bool, - }), + advertising: advertisingPropTypes, // controller only interfaceBehavior: PropTypes.oneOf([ @@ -231,6 +341,11 @@ export default class JWPlayer extends Component { onPause: PropTypes.func, onSetupPlayerError: PropTypes.func, onPlayerError: PropTypes.func, + onPlayerWarning: PropTypes.func, + onPlayerAdError: PropTypes.func, + onPlayerAdWarning: PropTypes.func, + onAdEvent: PropTypes.func, + onAdTime: PropTypes.func, onBuffer: PropTypes.func, onTime: PropTypes.func, onComplete: PropTypes.func, @@ -240,6 +355,7 @@ export default class JWPlayer extends Component { onFullScreenExit: PropTypes.func, onSeek: PropTypes.func, onSeeked: PropTypes.func, + onRateChanged: PropTypes.func, onPlaylistItem: PropTypes.func, onControlBarVisible: PropTypes.func, onPlaylistComplete: PropTypes.func, diff --git a/package.json b/package.json index 2a623e34..7317d8b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-jw-media-player", - "version": "0.2.40", + "version": "0.2.41", "description": "React-native Android/iOS plugin for JWPlayer SDK (https://www.jwplayer.com/)", "main": "index.js", "types": "./index.d.ts", From 7349198887ed8879947ab5acdee20ffcfc4dbf2e Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Sun, 24 Dec 2023 22:38:14 +0200 Subject: [PATCH 08/11] swift ads support --- Example/ios/Podfile | 3 + Example/ios/Podfile.lock | 25 +- ios/RNJWPlayer.xcodeproj/project.pbxproj | 4 + ios/RNJWPlayer/RNJWPlayerAds.swift | 260 ++++++++++++++++++ ios/RNJWPlayer/RNJWPlayerView.swift | 100 +++---- ios/RNJWPlayer/RNJWPlayerViewController.swift | 24 +- 6 files changed, 340 insertions(+), 76 deletions(-) create mode 100644 ios/RNJWPlayer/RNJWPlayerAds.swift diff --git a/Example/ios/Podfile b/Example/ios/Podfile index e977e1e1..24323885 100644 --- a/Example/ios/Podfile +++ b/Example/ios/Podfile @@ -4,6 +4,9 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ 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 d34707d9..43a0ff97 100644 --- a/Example/ios/Podfile.lock +++ b/Example/ios/Podfile.lock @@ -73,11 +73,15 @@ PODS: - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - - google-cast-sdk (4.8.0): - - google-cast-sdk/Core (= 4.8.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.8.0): + - google-cast-sdk/Core (4.5.3): + - GTMSessionFetcher/Core (~> 1.1) - Protobuf (~> 3.13) + - 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) @@ -367,9 +371,10 @@ PODS: - RNGestureHandler (2.12.1): - React-Core - RNJWPlayer (0.2.39): - - google-cast-sdk (~> 4.8) + - google-cast-sdk (~> 4.5.3) + - GoogleAds-IMA-iOS-SDK (~> 3.19.1) - JWPlayerKit (~> 4.17.0) - - React + - React-Core - RNScreens (3.25.0): - React-Core - React-RCTImage @@ -460,6 +465,8 @@ SPEC REPOS: - FlipperKit - fmt - google-cast-sdk + - GoogleAds-IMA-iOS-SDK + - GTMSessionFetcher - JWPlayerKit - libevent - OpenSSL-Universal @@ -566,7 +573,9 @@ SPEC CHECKSUMS: FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 476ee3e89abb49e07f822b48323c51c57124b572 - google-cast-sdk: afeb1aac0744b1bc4f70bc3db8468e33fabbff38 + google-cast-sdk: f94c5df87564f71d4342400b8487665e9bed27c6 + GoogleAds-IMA-iOS-SDK: 0e817c05ab26f1b9285c80f4a75e1350a916d50b + GTMSessionFetcher: 5595ec75acf5be50814f81e9189490412bad82ba JWPlayerKit: b0fd3f2abf99f40f8cf8292d550864be4b5df3f9 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c @@ -602,13 +611,13 @@ SPEC CHECKSUMS: RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0 RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df RNGestureHandler: c0d04458598fcb26052494ae23dda8f8f5162b13 - RNJWPlayer: b1d2e429f23fb84c1ee12cf876479381fc3107e1 + RNJWPlayer: fd86a3c64d0ba00d4c0ab783df7fb432443fa33b RNScreens: 85d3880b52d34db7b8eeebe2f1a0e807c05e69fa RNVectorIcons: 8b5bb0fa61d54cd2020af4f24a51841ce365c7e9 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: 17cd9a50243093b547c1e539c749928dd68152da YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 0312f3bc93221dbcdcb28d35875a0e2e57051f6a +PODFILE CHECKSUM: 0aa82ae79746645e542b17a02524e6691f65694a COCOAPODS: 1.14.3 diff --git a/ios/RNJWPlayer.xcodeproj/project.pbxproj b/ios/RNJWPlayer.xcodeproj/project.pbxproj index a0902218..4a28d982 100644 --- a/ios/RNJWPlayer.xcodeproj/project.pbxproj +++ b/ios/RNJWPlayer.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 2AA52BE726C144B200AD26AE /* RNJWPlayerViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */; }; 3BC75FCC1E43B1DB0011FBAA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC75FCB1E43B1DB0011FBAA /* UIKit.framework */; }; 3BC75FD11E43B3090011FBAA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC75FD01E43B3090011FBAA /* Foundation.framework */; }; + 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 */; }; @@ -36,6 +37,7 @@ 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNJWPlayerViewManager.m; path = RNJWPlayer/RNJWPlayerViewManager.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; }; + 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 = ""; }; @@ -79,6 +81,7 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( + 860F49F92B38AD1D00D0FCC4 /* RNJWPlayerAds.swift */, 86182A322B2AFA660040739A /* RNJWPlayer-Bridging-Header.h */, 2AA52BE226C144B200AD26AE /* RNJWPlayerViewManager.m */, 8650D61B2B1D1E1E00DD1C7E /* RNJWPlayerViewManager.swift */, @@ -154,6 +157,7 @@ 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; 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/RNJWPlayerView.swift b/ios/RNJWPlayer/RNJWPlayerView.swift index b024424a..62b65097 100644 --- a/ios/RNJWPlayer/RNJWPlayerView.swift +++ b/ios/RNJWPlayer/RNJWPlayerView.swift @@ -210,6 +210,15 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele 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 @@ -260,7 +269,12 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele } } - func setNewConfig(config: [String : Any]) { + 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 myModel = try? Config(config) + currentConfig = config if !settingConfig { @@ -594,23 +608,23 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele // 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 tagURL = URL(string: tag), + 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) } @@ -667,64 +681,38 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele configBuilder.related(relatedContent) } - let ads = config["advertising"] as? [String: Any] -// if let adClient = ads?["adClient"] as? Int { -// var jwAdClient: JWAdClient = .unknown -// -// switch adClient { -// case 0: -// jwAdClient = .JWPlayer -// case 1: -// jwAdClient = .GoogleIMA -// case 2: -// jwAdClient = .GoogleIMADAI -// default: -// break -// } -// -// -// } - - let adConfigBuilder = JWAdsAdvertisingConfigBuilder() - - if let schedule = ads?["adSchedule"] as? [[String: Any]] { - _ = schedule.compactMap { item -> JWAdBreak? in - guard let offsetString = item["offset"] as? String, - let tag = item["tag"] as? String, - let tagUrl = URL(string: tag), - let offset = JWAdOffset.from(string: offsetString) else { - return nil - } + var error: Error? - let adBreakBuilder = JWAdBreakBuilder() - adBreakBuilder.offset(offset) - adBreakBuilder.tags([tagUrl]) + if let ads = config["advertising"] as? [String: Any] { + var advertisingConfig: JWAdvertisingConfig? - do { - return try adBreakBuilder.build() - } catch { - // Handle the error here, log it, print it, or take appropriate action - print("Error building ad break: \(error)") - return nil - } + var jwAdClient = JWAdClient.unknown + if let adClientString = ads["adClient"] as? String { + jwAdClient = RCTConvert.JWAdClient(adClientString) } - } - if let tag = ads?["tag"] as? String { - adConfigBuilder.tag(URL(string: tag)!) - } + 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 + } - if let adVmap = ads?["adVmap"] as? String { - adConfigBuilder.vmapURL(URL(string: adVmap)!) - } + // Handle error if any + if let error = error { + print("Error configuring ads: \(error.localizedDescription)") + } - if let openBrowserOnAdClick = ads?["openBrowserOnAdClick"] as? Bool { - adConfigBuilder.openBrowserOnAdClick(openBrowserOnAdClick) + if (advertisingConfig != nil) { + configBuilder.advertising(advertisingConfig!) + } } - let advertising = try adConfigBuilder.build() - configBuilder.advertising(advertising) - let playerConfig = try configBuilder.build() return playerConfig diff --git a/ios/RNJWPlayer/RNJWPlayerViewController.swift b/ios/RNJWPlayer/RNJWPlayerViewController.swift index 91df58af..2a621e4c 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewController.swift +++ b/ios/RNJWPlayer/RNJWPlayerViewController.swift @@ -153,6 +153,18 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD 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 @@ -188,18 +200,6 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD task.resume() } - 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) { - - } - func contentKeyWithSPCData(_ spcData: Data, completionHandler handler: @escaping (Data?, Date?, String?) -> Void) { if parentView.processSpcUrl == nil { return From 19b7c1dbaa1c86e063191a8d0085cd45fb986b60 Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Tue, 26 Dec 2023 10:52:56 +0200 Subject: [PATCH 09/11] added required methods --- ios/RNJWPlayer/RNJWPlayerViewManager.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ios/RNJWPlayer/RNJWPlayerViewManager.swift b/ios/RNJWPlayer/RNJWPlayerViewManager.swift index 4dc62692..cc853ebc 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.swift +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.swift @@ -8,6 +8,14 @@ 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 From f5feb2955990d2401e30098ab6abd83f07e33d72 Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Tue, 26 Dec 2023 10:57:24 +0200 Subject: [PATCH 10/11] prepare release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7317d8b6..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", From 24b4d58258aae61947d7330c3caef4013f60ba9a Mon Sep 17 00:00:00 2001 From: chaimPaneth Date: Tue, 26 Dec 2023 11:08:27 +0200 Subject: [PATCH 11/11] update example --- Example/ios/Podfile.lock | 4 ++-- Example/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Example/ios/Podfile.lock b/Example/ios/Podfile.lock index 43a0ff97..a8e2f64a 100644 --- a/Example/ios/Podfile.lock +++ b/Example/ios/Podfile.lock @@ -370,7 +370,7 @@ PODS: - React - RNGestureHandler (2.12.1): - React-Core - - RNJWPlayer (0.2.39): + - RNJWPlayer (0.2.42): - google-cast-sdk (~> 4.5.3) - GoogleAds-IMA-iOS-SDK (~> 3.19.1) - JWPlayerKit (~> 4.17.0) @@ -611,7 +611,7 @@ SPEC CHECKSUMS: RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0 RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df RNGestureHandler: c0d04458598fcb26052494ae23dda8f8f5162b13 - RNJWPlayer: fd86a3c64d0ba00d4c0ab783df7fb432443fa33b + RNJWPlayer: 78ba87f0d9c4f4b39c8bbc03fe04d73587acd7e8 RNScreens: 85d3880b52d34db7b8eeebe2f1a0e807c05e69fa RNVectorIcons: 8b5bb0fa61d54cd2020af4f24a51841ce365c7e9 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 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",