react-native-unified-player 0.3.4 → 0.3.6
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/android/src/main/java/com/unifiedplayer/UnifiedPlayerEventEmitter.kt +1 -1
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerModule.kt +135 -124
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerPackage.kt +2 -7
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerView.kt +69 -39
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerViewManager.kt +1 -1
- package/ios/UnifiedPlayerUIView.h +1 -0
- package/ios/UnifiedPlayerViewManager.m +16 -6
- package/package.json +1 -1
|
@@ -1,187 +1,198 @@
|
|
|
1
1
|
package com.unifiedplayer
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import com.facebook.react.bridge
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
|
+
import com.facebook.react.bridge.ReactMethod
|
|
6
|
+
import com.facebook.react.bridge.Promise
|
|
7
7
|
import com.facebook.react.uimanager.UIManagerModule
|
|
8
|
-
import
|
|
9
|
-
import com.facebook.react.
|
|
10
|
-
import
|
|
8
|
+
import android.util.Log
|
|
9
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
10
|
+
import android.view.View
|
|
11
11
|
|
|
12
12
|
class UnifiedPlayerModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
companion object {
|
|
14
|
+
private const val TAG = "UnifiedPlayerModule"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
init {
|
|
18
|
+
Log.d(TAG, "UnifiedPlayerModule initialized")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override fun getName(): String {
|
|
22
|
+
Log.d(TAG, "getName() called, returning 'UnifiedPlayer'")
|
|
23
|
+
return "UnifiedPlayer"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private fun getPlayerViewByTag(viewTag: Int): UnifiedPlayerView? {
|
|
17
27
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
val view = reactApplicationContext.currentActivity?.findViewById<View>(viewTag)
|
|
29
|
+
Log.d(TAG, "Looking for view with tag: $viewTag, found: ${view != null}")
|
|
30
|
+
|
|
31
|
+
if (view is UnifiedPlayerView) {
|
|
32
|
+
return view
|
|
33
|
+
} else if (view != null) {
|
|
34
|
+
Log.e(TAG, "View with tag $viewTag is not a UnifiedPlayerView, it's a ${view.javaClass.simpleName}")
|
|
21
35
|
}
|
|
22
36
|
} catch (e: Exception) {
|
|
23
|
-
Log.e(TAG, "
|
|
37
|
+
Log.e(TAG, "Error finding view with tag $viewTag: ${e.message}", e)
|
|
24
38
|
}
|
|
39
|
+
return null
|
|
25
40
|
}
|
|
26
|
-
|
|
27
|
-
private const val TAG = "UnifiedPlayerModule"
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
override fun getName(): String = "UnifiedPlayer"
|
|
31
|
-
|
|
41
|
+
|
|
32
42
|
@ReactMethod
|
|
33
43
|
fun play(viewTag: Int, promise: Promise) {
|
|
44
|
+
Log.d(TAG, "Native play method called with viewTag: $viewTag")
|
|
34
45
|
try {
|
|
35
|
-
val
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (view != null) {
|
|
42
|
-
view.play()
|
|
46
|
+
val playerView = getPlayerViewByTag(viewTag)
|
|
47
|
+
if (playerView != null) {
|
|
48
|
+
UiThreadUtil.runOnUiThread {
|
|
49
|
+
try {
|
|
50
|
+
playerView.play()
|
|
51
|
+
Log.d(TAG, "Play command executed successfully")
|
|
43
52
|
promise.resolve(true)
|
|
44
|
-
}
|
|
45
|
-
|
|
53
|
+
} catch (e: Exception) {
|
|
54
|
+
Log.e(TAG, "Error during play: ${e.message}", e)
|
|
55
|
+
promise.reject("PLAY_ERROR", "Error during play: ${e.message}", e)
|
|
46
56
|
}
|
|
47
|
-
} catch (e: Exception) {
|
|
48
|
-
Log.e(TAG, "Error in play method", e)
|
|
49
|
-
promise.reject("PLAY_ERROR", "Error in play method", e)
|
|
50
57
|
}
|
|
51
|
-
}
|
|
58
|
+
} else {
|
|
59
|
+
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
60
|
+
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
61
|
+
}
|
|
52
62
|
} catch (e: Exception) {
|
|
53
|
-
Log.e(TAG, "Error in play method", e)
|
|
54
|
-
promise.reject("
|
|
63
|
+
Log.e(TAG, "Error in play method: ${e.message}", e)
|
|
64
|
+
promise.reject("PLAY_ERROR", "Error in play method: ${e.message}", e)
|
|
55
65
|
}
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
@ReactMethod
|
|
59
69
|
fun pause(viewTag: Int, promise: Promise) {
|
|
70
|
+
Log.d(TAG, "Native pause method called with viewTag: $viewTag")
|
|
60
71
|
try {
|
|
61
|
-
val
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (view != null) {
|
|
68
|
-
view.pause()
|
|
72
|
+
val playerView = getPlayerViewByTag(viewTag)
|
|
73
|
+
if (playerView != null) {
|
|
74
|
+
UiThreadUtil.runOnUiThread {
|
|
75
|
+
try {
|
|
76
|
+
playerView.pause()
|
|
77
|
+
Log.d(TAG, "Pause command executed successfully")
|
|
69
78
|
promise.resolve(true)
|
|
70
|
-
}
|
|
71
|
-
|
|
79
|
+
} catch (e: Exception) {
|
|
80
|
+
Log.e(TAG, "Error during pause: ${e.message}", e)
|
|
81
|
+
promise.reject("PAUSE_ERROR", "Error during pause: ${e.message}", e)
|
|
72
82
|
}
|
|
73
|
-
} catch (e: Exception) {
|
|
74
|
-
Log.e(TAG, "Error in pause method", e)
|
|
75
|
-
promise.reject("PAUSE_ERROR", "Error in pause method", e)
|
|
76
83
|
}
|
|
77
|
-
}
|
|
84
|
+
} else {
|
|
85
|
+
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
86
|
+
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
87
|
+
}
|
|
78
88
|
} catch (e: Exception) {
|
|
79
|
-
Log.e(TAG, "Error in pause method", e)
|
|
80
|
-
promise.reject("
|
|
89
|
+
Log.e(TAG, "Error in pause method: ${e.message}", e)
|
|
90
|
+
promise.reject("PAUSE_ERROR", "Error in pause method: ${e.message}", e)
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
@ReactMethod
|
|
85
95
|
fun seekTo(viewTag: Int, seconds: Float, promise: Promise) {
|
|
96
|
+
Log.d(TAG, "Native seekTo method called with viewTag: $viewTag, seconds: $seconds")
|
|
86
97
|
try {
|
|
87
|
-
val
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (view != null) {
|
|
94
|
-
view.seekTo(seconds)
|
|
98
|
+
val playerView = getPlayerViewByTag(viewTag)
|
|
99
|
+
if (playerView != null) {
|
|
100
|
+
UiThreadUtil.runOnUiThread {
|
|
101
|
+
try {
|
|
102
|
+
playerView.seekTo(seconds)
|
|
103
|
+
Log.d(TAG, "SeekTo command executed successfully to $seconds seconds")
|
|
95
104
|
promise.resolve(true)
|
|
96
|
-
}
|
|
97
|
-
|
|
105
|
+
} catch (e: Exception) {
|
|
106
|
+
Log.e(TAG, "Error during seekTo: ${e.message}", e)
|
|
107
|
+
promise.reject("SEEK_ERROR", "Error during seekTo: ${e.message}", e)
|
|
98
108
|
}
|
|
99
|
-
} catch (e: Exception) {
|
|
100
|
-
Log.e(TAG, "Error in seekTo method", e)
|
|
101
|
-
promise.reject("SEEK_ERROR", "Error in seekTo method", e)
|
|
102
109
|
}
|
|
103
|
-
}
|
|
110
|
+
} else {
|
|
111
|
+
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
112
|
+
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
113
|
+
}
|
|
104
114
|
} catch (e: Exception) {
|
|
105
|
-
Log.e(TAG, "Error in seekTo method", e)
|
|
106
|
-
promise.reject("
|
|
115
|
+
Log.e(TAG, "Error in seekTo method: ${e.message}", e)
|
|
116
|
+
promise.reject("SEEK_ERROR", "Error in seekTo method: ${e.message}", e)
|
|
107
117
|
}
|
|
108
118
|
}
|
|
109
119
|
|
|
110
120
|
@ReactMethod
|
|
111
121
|
fun getCurrentTime(viewTag: Int, promise: Promise) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
Log.d(TAG, "Native getCurrentTime method called with viewTag: $viewTag")
|
|
123
|
+
try {
|
|
124
|
+
val playerView = getPlayerViewByTag(viewTag)
|
|
125
|
+
if (playerView != null) {
|
|
126
|
+
UiThreadUtil.runOnUiThread {
|
|
127
|
+
try {
|
|
128
|
+
val currentTime = playerView.getCurrentTime()
|
|
129
|
+
Log.d(TAG, "getCurrentTime executed successfully, current time: $currentTime")
|
|
130
|
+
promise.resolve(currentTime)
|
|
131
|
+
} catch (e: Exception) {
|
|
132
|
+
Log.e(TAG, "Error getting current time: ${e.message}", e)
|
|
133
|
+
promise.reject("GET_TIME_ERROR", "Error getting current time: ${e.message}", e)
|
|
120
134
|
}
|
|
121
|
-
} catch (e: Exception) {
|
|
122
|
-
Log.e(TAG, "Error in getCurrentTime method", e)
|
|
123
|
-
promise.reject("GET_TIME_ERROR", "Error in getCurrentTime method", e)
|
|
124
135
|
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
} else {
|
|
137
|
+
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
138
|
+
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
139
|
+
}
|
|
140
|
+
} catch (e: Exception) {
|
|
141
|
+
Log.e(TAG, "Error in getCurrentTime method: ${e.message}", e)
|
|
142
|
+
promise.reject("GET_TIME_ERROR", "Error in getCurrentTime method: ${e.message}", e)
|
|
128
143
|
}
|
|
129
144
|
}
|
|
130
145
|
|
|
131
146
|
@ReactMethod
|
|
132
147
|
fun getDuration(viewTag: Int, promise: Promise) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
148
|
+
Log.d(TAG, "Native getDuration method called with viewTag: $viewTag")
|
|
149
|
+
try {
|
|
150
|
+
val playerView = getPlayerViewByTag(viewTag)
|
|
151
|
+
if (playerView != null) {
|
|
152
|
+
UiThreadUtil.runOnUiThread {
|
|
153
|
+
try {
|
|
154
|
+
val duration = playerView.getDuration()
|
|
155
|
+
Log.d(TAG, "getDuration executed successfully, duration: $duration")
|
|
156
|
+
promise.resolve(duration)
|
|
157
|
+
} catch (e: Exception) {
|
|
158
|
+
Log.e(TAG, "Error getting duration: ${e.message}", e)
|
|
159
|
+
promise.reject("GET_DURATION_ERROR", "Error getting duration: ${e.message}", e)
|
|
141
160
|
}
|
|
142
|
-
} catch (e: Exception) {
|
|
143
|
-
Log.e(TAG, "Error in getDuration method", e)
|
|
144
|
-
promise.reject("GET_DURATION_ERROR", "Error in getDuration method", e)
|
|
145
161
|
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
162
|
+
} else {
|
|
163
|
+
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
164
|
+
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
165
|
+
}
|
|
166
|
+
} catch (e: Exception) {
|
|
167
|
+
Log.e(TAG, "Error in getDuration method: ${e.message}", e)
|
|
168
|
+
promise.reject("GET_DURATION_ERROR", "Error in getDuration method: ${e.message}", e)
|
|
149
169
|
}
|
|
150
170
|
}
|
|
151
171
|
|
|
152
172
|
@ReactMethod
|
|
153
173
|
fun capture(viewTag: Int, promise: Promise) {
|
|
174
|
+
Log.d(TAG, "Native capture method called with viewTag: $viewTag")
|
|
154
175
|
try {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (bitmap != null) {
|
|
167
|
-
val outputStream = ByteArrayOutputStream()
|
|
168
|
-
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
|
169
|
-
val base64 = Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT)
|
|
170
|
-
promise.resolve(base64)
|
|
171
|
-
} else {
|
|
172
|
-
promise.reject("CAPTURE_ERROR", "Failed to capture frame")
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
|
|
176
|
+
val playerView = getPlayerViewByTag(viewTag)
|
|
177
|
+
if (playerView != null) {
|
|
178
|
+
UiThreadUtil.runOnUiThread {
|
|
179
|
+
try {
|
|
180
|
+
// Assuming playerView has a method called capture() that returns a String
|
|
181
|
+
val captureResult = playerView.capture()
|
|
182
|
+
Log.d(TAG, "Capture command executed successfully, result: $captureResult")
|
|
183
|
+
promise.resolve(captureResult)
|
|
184
|
+
} catch (e: Exception) {
|
|
185
|
+
Log.e(TAG, "Error during capture: ${e.message}", e)
|
|
186
|
+
promise.reject("CAPTURE_ERROR", "Error during capture: ${e.message}", e)
|
|
176
187
|
}
|
|
177
|
-
} catch (e: Exception) {
|
|
178
|
-
Log.e(TAG, "Error in capture method", e)
|
|
179
|
-
promise.reject("CAPTURE_ERROR", "Error in capture method", e)
|
|
180
188
|
}
|
|
181
|
-
}
|
|
189
|
+
} else {
|
|
190
|
+
Log.e(TAG, "Player view not found for tag: $viewTag")
|
|
191
|
+
promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
|
|
192
|
+
}
|
|
182
193
|
} catch (e: Exception) {
|
|
183
|
-
Log.e(TAG, "Error in capture method", e)
|
|
184
|
-
promise.reject("
|
|
194
|
+
Log.e(TAG, "Error in capture method: ${e.message}", e)
|
|
195
|
+
promise.reject("CAPTURE_ERROR", "Error in capture method: ${e.message}", e)
|
|
185
196
|
}
|
|
186
197
|
}
|
|
187
198
|
}
|
|
@@ -12,12 +12,7 @@ class UnifiedPlayerPackage : ReactPackage {
|
|
|
12
12
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
13
13
|
Log.d(TAG, "Creating native modules")
|
|
14
14
|
return listOf(
|
|
15
|
-
UnifiedPlayerModule(reactContext)
|
|
16
|
-
reactContext.runOnUiQueueThread {
|
|
17
|
-
// Initialize module on UI thread
|
|
18
|
-
Log.d(TAG, "Module initialized on UI thread")
|
|
19
|
-
}
|
|
20
|
-
}
|
|
15
|
+
UnifiedPlayerModule(reactContext)
|
|
21
16
|
)
|
|
22
17
|
}
|
|
23
18
|
|
|
@@ -25,4 +20,4 @@ class UnifiedPlayerPackage : ReactPackage {
|
|
|
25
20
|
Log.d(TAG, "Creating view managers")
|
|
26
21
|
return listOf(UnifiedPlayerViewManager())
|
|
27
22
|
}
|
|
28
|
-
}
|
|
23
|
+
}
|
|
@@ -2,14 +2,16 @@ package com.unifiedplayer
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
-
import android.graphics.Color
|
|
6
|
-
import android.util.Log
|
|
7
5
|
import android.graphics.Bitmap
|
|
8
6
|
import android.graphics.Canvas
|
|
7
|
+
import android.graphics.Color
|
|
8
|
+
import android.view.PixelCopy
|
|
9
|
+
import android.util.Base64
|
|
10
|
+
import java.io.ByteArrayOutputStream
|
|
11
|
+
import android.util.Log
|
|
9
12
|
import android.os.Handler
|
|
10
13
|
import android.os.Looper
|
|
11
14
|
import android.view.Gravity
|
|
12
|
-
import android.view.TextureView
|
|
13
15
|
import android.view.View
|
|
14
16
|
import android.widget.FrameLayout
|
|
15
17
|
import com.facebook.react.bridge.Arguments
|
|
@@ -42,6 +44,7 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
42
44
|
private var autoplay: Boolean = true
|
|
43
45
|
private var loop: Boolean = false
|
|
44
46
|
private var playerView: PlayerView
|
|
47
|
+
private var textureView: android.view.TextureView? = null
|
|
45
48
|
private var player: ExoPlayer? = null
|
|
46
49
|
private var currentProgress = 0
|
|
47
50
|
private var isPaused = false
|
|
@@ -80,17 +83,30 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
80
83
|
// Create ExoPlayer
|
|
81
84
|
player = ExoPlayer.Builder(context).build()
|
|
82
85
|
|
|
83
|
-
// Create
|
|
86
|
+
// Create TextureView for video rendering
|
|
87
|
+
textureView = android.view.TextureView(context).apply {
|
|
88
|
+
layoutParams = LayoutParams(
|
|
89
|
+
LayoutParams.MATCH_PARENT,
|
|
90
|
+
LayoutParams.MATCH_PARENT
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create PlayerView without SurfaceView
|
|
84
95
|
playerView = PlayerView(context).apply {
|
|
85
96
|
layoutParams = LayoutParams(
|
|
86
97
|
LayoutParams.MATCH_PARENT,
|
|
87
98
|
LayoutParams.MATCH_PARENT
|
|
88
99
|
)
|
|
89
100
|
setPlayer(player)
|
|
90
|
-
|
|
101
|
+
useController = false // Disable default controls
|
|
91
102
|
}
|
|
92
103
|
|
|
104
|
+
// Add views to hierarchy
|
|
105
|
+
addView(textureView)
|
|
93
106
|
addView(playerView)
|
|
107
|
+
|
|
108
|
+
// Set TextureView as video surface
|
|
109
|
+
player?.setVideoTextureView(textureView)
|
|
94
110
|
// Add logging for playerView dimensions and post play call
|
|
95
111
|
playerView.post {
|
|
96
112
|
Log.d(TAG, "PlayerView dimensions after addView: width=${playerView.width}, height=${playerView.height}")
|
|
@@ -316,41 +332,17 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
316
332
|
}
|
|
317
333
|
}
|
|
318
334
|
|
|
319
|
-
fun getDuration(): Float {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
fun captureFrame(): Bitmap? {
|
|
332
|
-
Log.d(TAG, "CaptureFrame method called")
|
|
333
|
-
try {
|
|
334
|
-
// Create a bitmap with the same dimensions as the player view
|
|
335
|
-
val bitmap = Bitmap.createBitmap(
|
|
336
|
-
playerView.width,
|
|
337
|
-
playerView.height,
|
|
338
|
-
Bitmap.Config.ARGB_8888
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
// Create a canvas with the bitmap
|
|
342
|
-
val canvas = Canvas(bitmap)
|
|
343
|
-
|
|
344
|
-
// Draw the player view onto the canvas
|
|
345
|
-
playerView.draw(canvas)
|
|
346
|
-
|
|
347
|
-
Log.d(TAG, "Successfully captured frame from PlayerView")
|
|
348
|
-
return bitmap
|
|
349
|
-
} catch (e: Exception) {
|
|
350
|
-
Log.e(TAG, "Error capturing frame: ${e.message}", e)
|
|
351
|
-
return null
|
|
335
|
+
fun getDuration(): Float {
|
|
336
|
+
Log.d(TAG, "GetDuration method called")
|
|
337
|
+
return player?.let {
|
|
338
|
+
val duration = it.duration.toFloat() / 1000f
|
|
339
|
+
Log.d(TAG, "Duration: $duration seconds (raw: ${it.duration})")
|
|
340
|
+
if (it.duration > 0) duration else 0f
|
|
341
|
+
} ?: run {
|
|
342
|
+
Log.e(TAG, "Cannot get duration: player is null")
|
|
343
|
+
0f
|
|
344
|
+
}
|
|
352
345
|
}
|
|
353
|
-
}
|
|
354
346
|
|
|
355
347
|
// Add a getter for the ExoPlayer instance
|
|
356
348
|
val exoPlayer: ExoPlayer?
|
|
@@ -415,4 +407,42 @@ fun captureFrame(): Bitmap? {
|
|
|
415
407
|
progressHandler.removeCallbacks(progressRunnable) // Stop progress updates
|
|
416
408
|
player?.release()
|
|
417
409
|
}
|
|
410
|
+
|
|
411
|
+
fun capture(): String {
|
|
412
|
+
Log.d(TAG, "Capture method called")
|
|
413
|
+
return try {
|
|
414
|
+
player?.let { exoPlayer ->
|
|
415
|
+
// Get the video size from the player
|
|
416
|
+
val videoSize = exoPlayer.videoSize
|
|
417
|
+
if (videoSize.width <= 0 || videoSize.height <= 0) {
|
|
418
|
+
Log.e(TAG, "Invalid video dimensions: ${videoSize.width}x${videoSize.height}")
|
|
419
|
+
return ""
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Get bitmap directly from TextureView
|
|
423
|
+
val bitmap = textureView?.bitmap ?: run {
|
|
424
|
+
Log.e(TAG, "Failed to get bitmap from TextureView")
|
|
425
|
+
return ""
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Compress and encode the bitmap
|
|
429
|
+
val byteArrayOutputStream = ByteArrayOutputStream()
|
|
430
|
+
if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)) {
|
|
431
|
+
val byteArray = byteArrayOutputStream.toByteArray()
|
|
432
|
+
val base64EncodedString = Base64.encodeToString(byteArray, Base64.DEFAULT)
|
|
433
|
+
Log.d(TAG, "Capture successful, base64 length: ${base64EncodedString.length}")
|
|
434
|
+
base64EncodedString
|
|
435
|
+
} else {
|
|
436
|
+
Log.e(TAG, "Failed to compress bitmap")
|
|
437
|
+
""
|
|
438
|
+
}
|
|
439
|
+
} ?: run {
|
|
440
|
+
Log.e(TAG, "Cannot capture: player is null")
|
|
441
|
+
""
|
|
442
|
+
}
|
|
443
|
+
} catch (e: Exception) {
|
|
444
|
+
Log.e(TAG, "Error during capture: ${e.message}", e)
|
|
445
|
+
""
|
|
446
|
+
}
|
|
447
|
+
}
|
|
418
448
|
}
|
|
@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
16
16
|
@property (nonatomic, weak) RCTBridge *bridge;
|
|
17
17
|
@property (nonatomic, assign) VLCMediaPlayerState previousState;
|
|
18
18
|
@property (nonatomic, assign) BOOL hasRenderedVideo;
|
|
19
|
+
@property (nonatomic, assign) BOOL readyEventSent;
|
|
19
20
|
|
|
20
21
|
// Event callbacks
|
|
21
22
|
@property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
// Initialize properties
|
|
21
21
|
_hasRenderedVideo = NO;
|
|
22
|
+
_readyEventSent = NO;
|
|
22
23
|
|
|
23
24
|
// Create the player
|
|
24
25
|
_player = [[VLCMediaPlayer alloc] init];
|
|
@@ -182,8 +183,9 @@
|
|
|
182
183
|
self.window ? @"YES" : @"NO",
|
|
183
184
|
self.superview ? @"YES" : @"NO");
|
|
184
185
|
|
|
185
|
-
// Reset the
|
|
186
|
+
// Reset the flags
|
|
186
187
|
_hasRenderedVideo = NO;
|
|
188
|
+
_readyEventSent = NO;
|
|
187
189
|
|
|
188
190
|
// Make sure we're in the main thread
|
|
189
191
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
@@ -432,6 +434,19 @@
|
|
|
432
434
|
VLCMediaPlayerState state = _player.state;
|
|
433
435
|
RCTLogInfo(@"[UnifiedPlayerViewManager] mediaPlayerStateChanged - New State: %d", state); // Added Log
|
|
434
436
|
|
|
437
|
+
// Check if media is ready to play
|
|
438
|
+
if ((state == VLCMediaPlayerStateBuffering || state == VLCMediaPlayerStatePlaying || state == VLCMediaPlayerStatePaused) && !_readyEventSent) {
|
|
439
|
+
// Check if we have video tracks
|
|
440
|
+
NSArray *videoTracks = [_player.media tracksInformation];
|
|
441
|
+
if (videoTracks.count > 0 || _player.hasVideoOut) {
|
|
442
|
+
RCTLogInfo(@"[UnifiedPlayerViewManager] Media is ready to play - Video tracks found: %lu", (unsigned long)videoTracks.count);
|
|
443
|
+
|
|
444
|
+
// Send ready event when media is ready, regardless of autoplay
|
|
445
|
+
[self sendEvent:@"onReadyToPlay" body:@{}];
|
|
446
|
+
_readyEventSent = YES;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
435
450
|
// Debug information for video output
|
|
436
451
|
if (state == VLCMediaPlayerStatePlaying) {
|
|
437
452
|
RCTLogInfo(@"[UnifiedPlayerViewManager] Video size: %@",
|
|
@@ -444,11 +459,6 @@
|
|
|
444
459
|
if (videoTracks.count > 0) {
|
|
445
460
|
RCTLogInfo(@"[UnifiedPlayerViewManager] Video tracks found: %lu", (unsigned long)videoTracks.count);
|
|
446
461
|
|
|
447
|
-
// Send ready event the first time we start playing
|
|
448
|
-
if (!_hasRenderedVideo) {
|
|
449
|
-
[self sendEvent:@"onReadyToPlay" body:@{}];
|
|
450
|
-
}
|
|
451
|
-
|
|
452
462
|
// Send playing event when we actually start playing
|
|
453
463
|
[self sendEvent:@"onPlaying" body:@{}];
|
|
454
464
|
|