react-native-kookit 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,13 @@
1
1
  import ExpoModulesCore
2
+ import UIKit
3
+ import MediaPlayer
4
+ import AVFoundation
2
5
 
3
6
  public class ReactNativeKookitModule: Module {
7
+ private var volumeView: MPVolumeView?
8
+ private var volumeObserver: NSKeyValueObservation?
9
+ private var isVolumeKeyInterceptionEnabled = false
10
+ private var previousVolume: Float = 0.0
4
11
  // Each module class must implement the definition function. The definition consists of components
5
12
  // that describes the module's functionality and behavior.
6
13
  // See https://docs.expo.dev/modules/module-api for more details about available components.
@@ -16,7 +23,7 @@ public class ReactNativeKookitModule: Module {
16
23
  ])
17
24
 
18
25
  // Defines event names that the module can send to JavaScript.
19
- Events("onChange")
26
+ Events("onChange", "onVolumeButtonPressed")
20
27
 
21
28
  // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
22
29
  Function("hello") {
@@ -32,6 +39,16 @@ public class ReactNativeKookitModule: Module {
32
39
  ])
33
40
  }
34
41
 
42
+ // Enables volume key interception
43
+ Function("enableVolumeKeyInterception") {
44
+ self.enableVolumeKeyInterception()
45
+ }
46
+
47
+ // Disables volume key interception
48
+ Function("disableVolumeKeyInterception") {
49
+ self.disableVolumeKeyInterception()
50
+ }
51
+
35
52
  // Enables the module to be used as a native view. Definition components that are accepted as part of the
36
53
  // view definition: Prop, Events.
37
54
  View(ReactNativeKookitView.self) {
@@ -45,4 +62,84 @@ public class ReactNativeKookitModule: Module {
45
62
  Events("onLoad")
46
63
  }
47
64
  }
65
+
66
+ private func enableVolumeKeyInterception() {
67
+ guard !isVolumeKeyInterceptionEnabled else { return }
68
+
69
+ isVolumeKeyInterceptionEnabled = true
70
+
71
+ DispatchQueue.main.async {
72
+ // Store initial volume
73
+ let audioSession = AVAudioSession.sharedInstance()
74
+ self.previousVolume = audioSession.outputVolume
75
+
76
+ // Configure audio session to allow volume button interception
77
+ do {
78
+ try AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default, options: [.mixWithOthers])
79
+ try AVAudioSession.sharedInstance().setActive(true)
80
+ } catch {
81
+ print("Failed to configure audio session: \(error)")
82
+ }
83
+
84
+ // Create a hidden volume view to prevent system volume HUD from showing
85
+ self.volumeView = MPVolumeView(frame: CGRect(x: -1000, y: -1000, width: 1, height: 1))
86
+ self.volumeView?.clipsToBounds = true
87
+ self.volumeView?.alpha = 0.01 // Make it nearly invisible but still functional
88
+ self.volumeView?.isUserInteractionEnabled = false
89
+
90
+ // Add volume view to the key window
91
+ if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
92
+ let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }) {
93
+ keyWindow.addSubview(self.volumeView!)
94
+ keyWindow.sendSubviewToBack(self.volumeView!)
95
+ }
96
+
97
+ // Set up volume observation with a small delay to ensure proper setup
98
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
99
+ self.volumeObserver = audioSession.observe(\.outputVolume, options: [.new]) { [weak self] (audioSession, change) in
100
+ guard let self = self, self.isVolumeKeyInterceptionEnabled else { return }
101
+
102
+ if let newVolume = change.newValue {
103
+ DispatchQueue.main.async {
104
+ // Determine if volume was increased or decreased
105
+ let key = newVolume > self.previousVolume ? "up" : "down"
106
+
107
+ // Send event to JavaScript
108
+ self.sendEvent("onVolumeButtonPressed", [
109
+ "key": key
110
+ ])
111
+
112
+ // Reset volume to prevent actual volume change
113
+ if let volumeSlider = self.volumeView?.subviews.compactMap({ $0 as? UISlider }).first {
114
+ volumeSlider.setValue(self.previousVolume, animated: false)
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ private func disableVolumeKeyInterception() {
124
+ guard isVolumeKeyInterceptionEnabled else { return }
125
+
126
+ isVolumeKeyInterceptionEnabled = false
127
+
128
+ DispatchQueue.main.async {
129
+ // Remove volume observer
130
+ self.volumeObserver?.invalidate()
131
+ self.volumeObserver = nil
132
+
133
+ // Remove volume view
134
+ self.volumeView?.removeFromSuperview()
135
+ self.volumeView = nil
136
+
137
+ // Reset audio session
138
+ do {
139
+ try AVAudioSession.sharedInstance().setActive(false)
140
+ } catch {
141
+ print("Failed to deactivate audio session: \(error)")
142
+ }
143
+ }
144
+ }
48
145
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-kookit",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "React Native module for intercepting volume button presses on iOS and Android",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -46,19 +46,17 @@
46
46
  "plugin/package.json",
47
47
  "*.md"
48
48
  ],
49
- "dependencies": {
50
- "@expo/config-plugins": "^8.0.0",
51
- "expo": "~53.0.20",
52
- "react": "19.0.0",
53
- "react-native": "0.79.5"
54
- },
49
+ "dependencies": {},
55
50
  "devDependencies": {
51
+ "@expo/config-plugins": "^8.0.0",
56
52
  "@types/react": "~19.0.0",
57
53
  "expo-module-scripts": "^4.1.10",
58
54
  "expo": "~53.0.0",
55
+ "react": "19.0.0",
59
56
  "react-native": "0.79.1"
60
57
  },
61
58
  "peerDependencies": {
59
+ "@expo/config-plugins": "*",
62
60
  "expo": "*",
63
61
  "react": "*",
64
62
  "react-native": "*"