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
|
|
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": "*"
|