react-native-unified-player 0.4.1 → 0.5.4
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.
- package/README.md +4 -0
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerModule.kt +71 -233
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerView.kt +50 -207
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerViewManager.kt +3 -37
- package/ios/UnifiedPlayerModule.m +91 -217
- package/ios/UnifiedPlayerUIView.h +2 -5
- package/ios/UnifiedPlayerViewManager.m +115 -121
- package/lib/module/index.js +78 -187
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +96 -81
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/index.tsx +165 -307
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerEventEmitter.kt +0 -66
package/README.md
CHANGED
|
@@ -151,6 +151,10 @@ Control playback using the `UnifiedPlayer` object and the native tag of the `Uni
|
|
|
151
151
|
yarn android # for Android
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
+
## Publishing
|
|
155
|
+
|
|
156
|
+
This package is automatically published to npm when changes are pushed to the `main` or `master` branch. See [PUBLISHING.md](.github/PUBLISHING.md) for setup instructions and details about npm's new authentication system (granular access tokens).
|
|
157
|
+
|
|
154
158
|
## Contributing
|
|
155
159
|
|
|
156
160
|
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
@@ -4,7 +4,6 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
4
4
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
5
|
import com.facebook.react.bridge.ReactMethod
|
|
6
6
|
import com.facebook.react.bridge.Promise
|
|
7
|
-
import com.facebook.react.uimanager.UIManagerModule
|
|
8
7
|
import android.util.Log
|
|
9
8
|
import com.facebook.react.bridge.UiThreadUtil
|
|
10
9
|
import android.view.View
|
|
@@ -15,279 +14,118 @@ class UnifiedPlayerModule(private val reactContext: ReactApplicationContext) : R
|
|
|
15
14
|
companion object {
|
|
16
15
|
private const val TAG = "UnifiedPlayerModule"
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
override fun getName(): String {
|
|
24
|
-
Log.d(TAG, "getName() called, returning 'UnifiedPlayer'")
|
|
25
|
-
return "UnifiedPlayer"
|
|
26
|
-
}
|
|
27
|
-
|
|
17
|
+
|
|
18
|
+
override fun getName(): String = "UnifiedPlayer"
|
|
19
|
+
|
|
28
20
|
private fun getPlayerViewByTag(viewTag: Int): UnifiedPlayerView? {
|
|
29
|
-
try {
|
|
21
|
+
return try {
|
|
30
22
|
val view = reactApplicationContext.currentActivity?.findViewById<View>(viewTag)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Log.e(TAG, "View with tag $viewTag is not a UnifiedPlayerView, it's a ${view.javaClass.simpleName}")
|
|
23
|
+
(view as? UnifiedPlayerView) ?: run {
|
|
24
|
+
if (view != null) {
|
|
25
|
+
Log.e(TAG, "View with tag $viewTag is not a UnifiedPlayerView")
|
|
26
|
+
}
|
|
27
|
+
null
|
|
37
28
|
}
|
|
38
29
|
} catch (e: Exception) {
|
|
39
|
-
Log.e(TAG, "Error finding view
|
|
30
|
+
Log.e(TAG, "Error finding view: ${e.message}")
|
|
31
|
+
null
|
|
40
32
|
}
|
|
41
|
-
return null
|
|
42
33
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Helper function to execute operations on the player view.
|
|
37
|
+
* Eliminates repetitive boilerplate across all methods.
|
|
38
|
+
*/
|
|
39
|
+
private inline fun <T> executeOnPlayerView(
|
|
40
|
+
viewTag: Int,
|
|
41
|
+
promise: Promise,
|
|
42
|
+
errorCode: String,
|
|
43
|
+
crossinline action: (UnifiedPlayerView) -> T
|
|
44
|
+
) {
|
|
47
45
|
try {
|
|
48
46
|
val playerView = getPlayerViewByTag(viewTag)
|
|
49
47
|
if (playerView != null) {
|
|
50
48
|
UiThreadUtil.runOnUiThread {
|
|
51
49
|
try {
|
|
52
|
-
playerView
|
|
53
|
-
|
|
54
|
-
promise.resolve(true)
|
|
50
|
+
val result = action(playerView)
|
|
51
|
+
promise.resolve(result)
|
|
55
52
|
} catch (e: Exception) {
|
|
56
|
-
Log.e(TAG, "Error during
|
|
57
|
-
promise.reject(
|
|
53
|
+
Log.e(TAG, "Error during $errorCode: ${e.message}")
|
|
54
|
+
promise.reject(errorCode, e.message, e)
|
|
58
55
|
}
|
|
59
56
|
}
|
|
60
57
|
} else {
|
|
61
|
-
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
62
58
|
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
63
59
|
}
|
|
64
60
|
} catch (e: Exception) {
|
|
65
|
-
Log.e(TAG, "Error in
|
|
66
|
-
promise.reject(
|
|
61
|
+
Log.e(TAG, "Error in $errorCode: ${e.message}")
|
|
62
|
+
promise.reject(errorCode, e.message, e)
|
|
67
63
|
}
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
@ReactMethod
|
|
71
|
-
fun
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
val playerView = getPlayerViewByTag(viewTag)
|
|
75
|
-
if (playerView != null) {
|
|
76
|
-
UiThreadUtil.runOnUiThread {
|
|
77
|
-
try {
|
|
78
|
-
playerView.pause()
|
|
79
|
-
Log.d(TAG, "Pause command executed successfully")
|
|
80
|
-
promise.resolve(true)
|
|
81
|
-
} catch (e: Exception) {
|
|
82
|
-
Log.e(TAG, "Error during pause: ${e.message}", e)
|
|
83
|
-
promise.reject("PAUSE_ERROR", "Error during pause: ${e.message}", e)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
} else {
|
|
87
|
-
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
88
|
-
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
89
|
-
}
|
|
90
|
-
} catch (e: Exception) {
|
|
91
|
-
Log.e(TAG, "Error in pause method: ${e.message}", e)
|
|
92
|
-
promise.reject("PAUSE_ERROR", "Error in pause method: ${e.message}", e)
|
|
93
|
-
}
|
|
67
|
+
fun play(viewTag: Int, promise: Promise) = executeOnPlayerView(viewTag, promise, "PLAY_ERROR") {
|
|
68
|
+
it.play()
|
|
69
|
+
true
|
|
94
70
|
}
|
|
95
71
|
|
|
96
72
|
@ReactMethod
|
|
97
|
-
fun
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
val playerView = getPlayerViewByTag(viewTag)
|
|
101
|
-
if (playerView != null) {
|
|
102
|
-
UiThreadUtil.runOnUiThread {
|
|
103
|
-
try {
|
|
104
|
-
playerView.seekTo(seconds)
|
|
105
|
-
Log.d(TAG, "SeekTo command executed successfully to $seconds seconds")
|
|
106
|
-
promise.resolve(true)
|
|
107
|
-
} catch (e: Exception) {
|
|
108
|
-
Log.e(TAG, "Error during seekTo: ${e.message}", e)
|
|
109
|
-
promise.reject("SEEK_ERROR", "Error during seekTo: ${e.message}", e)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
114
|
-
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
115
|
-
}
|
|
116
|
-
} catch (e: Exception) {
|
|
117
|
-
Log.e(TAG, "Error in seekTo method: ${e.message}", e)
|
|
118
|
-
promise.reject("SEEK_ERROR", "Error in seekTo method: ${e.message}", e)
|
|
119
|
-
}
|
|
73
|
+
fun pause(viewTag: Int, promise: Promise) = executeOnPlayerView(viewTag, promise, "PAUSE_ERROR") {
|
|
74
|
+
it.pause()
|
|
75
|
+
true
|
|
120
76
|
}
|
|
121
77
|
|
|
122
78
|
@ReactMethod
|
|
123
|
-
fun
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
val playerView = getPlayerViewByTag(viewTag)
|
|
127
|
-
if (playerView != null) {
|
|
128
|
-
UiThreadUtil.runOnUiThread {
|
|
129
|
-
try {
|
|
130
|
-
val currentTime = playerView.getCurrentTime()
|
|
131
|
-
Log.d(TAG, "getCurrentTime executed successfully, current time: $currentTime")
|
|
132
|
-
promise.resolve(currentTime)
|
|
133
|
-
} catch (e: Exception) {
|
|
134
|
-
Log.e(TAG, "Error getting current time: ${e.message}", e)
|
|
135
|
-
promise.reject("GET_TIME_ERROR", "Error getting current time: ${e.message}", e)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
140
|
-
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
141
|
-
}
|
|
142
|
-
} catch (e: Exception) {
|
|
143
|
-
Log.e(TAG, "Error in getCurrentTime method: ${e.message}", e)
|
|
144
|
-
promise.reject("GET_TIME_ERROR", "Error in getCurrentTime method: ${e.message}", e)
|
|
145
|
-
}
|
|
79
|
+
fun seekTo(viewTag: Int, seconds: Float, promise: Promise) = executeOnPlayerView(viewTag, promise, "SEEK_ERROR") {
|
|
80
|
+
it.seekTo(seconds)
|
|
81
|
+
true
|
|
146
82
|
}
|
|
147
83
|
|
|
148
84
|
@ReactMethod
|
|
149
|
-
fun
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
val playerView = getPlayerViewByTag(viewTag)
|
|
153
|
-
if (playerView != null) {
|
|
154
|
-
UiThreadUtil.runOnUiThread {
|
|
155
|
-
try {
|
|
156
|
-
val duration = playerView.getDuration()
|
|
157
|
-
Log.d(TAG, "getDuration executed successfully, duration: $duration")
|
|
158
|
-
promise.resolve(duration)
|
|
159
|
-
} catch (e: Exception) {
|
|
160
|
-
Log.e(TAG, "Error getting duration: ${e.message}", e)
|
|
161
|
-
promise.reject("GET_DURATION_ERROR", "Error getting duration: ${e.message}", e)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
166
|
-
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
167
|
-
}
|
|
168
|
-
} catch (e: Exception) {
|
|
169
|
-
Log.e(TAG, "Error in getDuration method: ${e.message}", e)
|
|
170
|
-
promise.reject("GET_DURATION_ERROR", "Error in getDuration method: ${e.message}", e)
|
|
171
|
-
}
|
|
85
|
+
fun getCurrentTime(viewTag: Int, promise: Promise) = executeOnPlayerView(viewTag, promise, "GET_TIME_ERROR") {
|
|
86
|
+
it.getCurrentTime()
|
|
172
87
|
}
|
|
173
88
|
|
|
174
89
|
@ReactMethod
|
|
175
|
-
fun
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
val playerView = getPlayerViewByTag(viewTag)
|
|
179
|
-
if (playerView != null) {
|
|
180
|
-
UiThreadUtil.runOnUiThread {
|
|
181
|
-
try {
|
|
182
|
-
// Assuming playerView has a method called capture() that returns a String
|
|
183
|
-
val captureResult = playerView.capture()
|
|
184
|
-
Log.d(TAG, "Capture command executed successfully, result: $captureResult")
|
|
185
|
-
promise.resolve(captureResult)
|
|
186
|
-
} catch (e: Exception) {
|
|
187
|
-
Log.e(TAG, "Error during capture: ${e.message}", e)
|
|
188
|
-
promise.reject("CAPTURE_ERROR", "Error during capture: ${e.message}", e)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
192
|
-
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
193
|
-
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
194
|
-
}
|
|
195
|
-
} catch (e: Exception) {
|
|
196
|
-
Log.e(TAG, "Error in capture method: ${e.message}", e)
|
|
197
|
-
promise.reject("CAPTURE_ERROR", "Error in capture method: ${e.message}", e)
|
|
198
|
-
}
|
|
90
|
+
fun getDuration(viewTag: Int, promise: Promise) = executeOnPlayerView(viewTag, promise, "GET_DURATION_ERROR") {
|
|
91
|
+
it.getDuration()
|
|
199
92
|
}
|
|
200
|
-
|
|
93
|
+
|
|
201
94
|
@ReactMethod
|
|
202
|
-
fun
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
val playerView = getPlayerViewByTag(viewTag)
|
|
206
|
-
if (playerView != null) {
|
|
207
|
-
UiThreadUtil.runOnUiThread {
|
|
208
|
-
try {
|
|
209
|
-
// Determine the output path
|
|
210
|
-
val finalOutputPath = if (outputPath.isNullOrEmpty()) {
|
|
211
|
-
// Use app-specific storage for Android 10+ (API level 29+)
|
|
212
|
-
val moviesDir = File(reactApplicationContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES), "recordings")
|
|
213
|
-
if (!moviesDir.exists()) {
|
|
214
|
-
moviesDir.mkdirs()
|
|
215
|
-
}
|
|
216
|
-
val timestamp = System.currentTimeMillis()
|
|
217
|
-
File(moviesDir, "recording_$timestamp.mp4").absolutePath
|
|
218
|
-
} else {
|
|
219
|
-
outputPath
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Start recording
|
|
223
|
-
val result = playerView.startRecording(finalOutputPath)
|
|
224
|
-
Log.d(TAG, "Start recording command executed successfully, result: $result")
|
|
225
|
-
promise.resolve(result)
|
|
226
|
-
} catch (e: Exception) {
|
|
227
|
-
Log.e(TAG, "Error during startRecording: ${e.message}", e)
|
|
228
|
-
promise.reject("RECORDING_ERROR", "Error during startRecording: ${e.message}", e)
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
} else {
|
|
232
|
-
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
233
|
-
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
234
|
-
}
|
|
235
|
-
} catch (e: Exception) {
|
|
236
|
-
Log.e(TAG, "Error in startRecording method: ${e.message}", e)
|
|
237
|
-
promise.reject("RECORDING_ERROR", "Error in startRecording method: ${e.message}", e)
|
|
238
|
-
}
|
|
95
|
+
fun capture(viewTag: Int, promise: Promise) = executeOnPlayerView(viewTag, promise, "CAPTURE_ERROR") {
|
|
96
|
+
it.capture()
|
|
239
97
|
}
|
|
240
|
-
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Start recording/downloading the video.
|
|
101
|
+
* Note: This downloads the source video file rather than recording the rendered video.
|
|
102
|
+
*/
|
|
241
103
|
@ReactMethod
|
|
242
|
-
fun
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
playerView.setIsFullscreen(isFullscreen)
|
|
250
|
-
Log.d(TAG, "toggleFullscreen executed successfully")
|
|
251
|
-
promise.resolve(true)
|
|
252
|
-
} catch (e: Exception) {
|
|
253
|
-
Log.e(TAG, "Error toggling fullscreen: ${e.message}", e)
|
|
254
|
-
promise.reject("FULLSCREEN_ERROR", "Error toggling fullscreen: ${e.message}", e)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
} else {
|
|
258
|
-
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
259
|
-
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
260
|
-
}
|
|
261
|
-
} catch (e: Exception) {
|
|
262
|
-
Log.e(TAG, "Error in toggleFullscreen method: ${e.message}", e)
|
|
263
|
-
promise.reject("FULLSCREEN_ERROR", "Error in toggleFullscreen method: ${e.message}", e)
|
|
104
|
+
fun startRecording(viewTag: Int, outputPath: String?, promise: Promise) = executeOnPlayerView(viewTag, promise, "RECORDING_ERROR") {
|
|
105
|
+
val finalOutputPath = if (outputPath.isNullOrEmpty()) {
|
|
106
|
+
val moviesDir = File(reactApplicationContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES), "recordings")
|
|
107
|
+
if (!moviesDir.exists()) moviesDir.mkdirs()
|
|
108
|
+
File(moviesDir, "recording_${System.currentTimeMillis()}.mp4").absolutePath
|
|
109
|
+
} else {
|
|
110
|
+
outputPath
|
|
264
111
|
}
|
|
112
|
+
it.startRecording(finalOutputPath)
|
|
265
113
|
}
|
|
266
114
|
|
|
267
115
|
@ReactMethod
|
|
268
|
-
fun
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
} else {
|
|
285
|
-
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
286
|
-
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
287
|
-
}
|
|
288
|
-
} catch (e: Exception) {
|
|
289
|
-
Log.e(TAG, "Error in stopRecording method: ${e.message}", e)
|
|
290
|
-
promise.reject("RECORDING_ERROR", "Error in stopRecording method: ${e.message}", e)
|
|
291
|
-
}
|
|
116
|
+
fun toggleFullscreen(viewTag: Int, isFullscreen: Boolean, promise: Promise) = executeOnPlayerView(viewTag, promise, "FULLSCREEN_ERROR") {
|
|
117
|
+
it.setIsFullscreen(isFullscreen)
|
|
118
|
+
true
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@ReactMethod
|
|
122
|
+
fun stopRecording(viewTag: Int, promise: Promise) = executeOnPlayerView(viewTag, promise, "RECORDING_ERROR") {
|
|
123
|
+
it.stopRecording()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@ReactMethod
|
|
127
|
+
fun setSpeed(viewTag: Int, speed: Float, promise: Promise) = executeOnPlayerView(viewTag, promise, "SPEED_ERROR") {
|
|
128
|
+
it.setSpeed(speed)
|
|
129
|
+
true
|
|
292
130
|
}
|
|
293
131
|
}
|