react-native-unified-player 0.3.5 → 0.3.7
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/build.gradle
CHANGED
|
@@ -77,6 +77,6 @@ dependencies {
|
|
|
77
77
|
implementation "com.facebook.react:react-android"
|
|
78
78
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
implementation "com.google.android.exoplayer:exoplayer-core:2.19.0"
|
|
81
|
+
implementation "com.google.android.exoplayer:exoplayer-ui:2.19.0"
|
|
82
82
|
}
|
|
@@ -169,4 +169,30 @@ class UnifiedPlayerModule(private val reactContext: ReactApplicationContext) : R
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
@ReactMethod
|
|
173
|
+
fun capture(viewTag: Int, promise: Promise) {
|
|
174
|
+
Log.d(TAG, "Native capture method called with viewTag: $viewTag")
|
|
175
|
+
try {
|
|
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)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
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
|
+
}
|
|
193
|
+
} catch (e: Exception) {
|
|
194
|
+
Log.e(TAG, "Error in capture method: ${e.message}", e)
|
|
195
|
+
promise.reject("CAPTURE_ERROR", "Error in capture method: ${e.message}", e)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -2,11 +2,17 @@ package com.unifiedplayer
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
+
import android.graphics.Bitmap
|
|
6
|
+
import android.graphics.Canvas
|
|
5
7
|
import android.graphics.Color
|
|
8
|
+
import android.view.PixelCopy
|
|
9
|
+
import android.util.Base64
|
|
10
|
+
import java.io.ByteArrayOutputStream
|
|
6
11
|
import android.util.Log
|
|
7
12
|
import android.os.Handler
|
|
8
13
|
import android.os.Looper
|
|
9
14
|
import android.view.Gravity
|
|
15
|
+
import android.view.View
|
|
10
16
|
import android.widget.FrameLayout
|
|
11
17
|
import com.facebook.react.bridge.Arguments
|
|
12
18
|
import com.google.android.exoplayer2.ExoPlayer
|
|
@@ -37,8 +43,8 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
37
43
|
private var videoUrl: String? = null
|
|
38
44
|
private var autoplay: Boolean = true
|
|
39
45
|
private var loop: Boolean = false
|
|
40
|
-
private var
|
|
41
|
-
|
|
46
|
+
private var textureView: android.view.TextureView
|
|
47
|
+
internal var player: ExoPlayer? = null
|
|
42
48
|
private var currentProgress = 0
|
|
43
49
|
private var isPaused = false
|
|
44
50
|
|
|
@@ -76,20 +82,23 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
76
82
|
// Create ExoPlayer
|
|
77
83
|
player = ExoPlayer.Builder(context).build()
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
// Create TextureView for video rendering
|
|
86
|
+
textureView = android.view.TextureView(context).apply {
|
|
87
|
+
layoutParams = LayoutParams(
|
|
88
|
+
LayoutParams.MATCH_PARENT,
|
|
89
|
+
LayoutParams.MATCH_PARENT
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Add TextureView to the view hierarchy
|
|
94
|
+
addView(textureView)
|
|
88
95
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
// We'll set the video surface when the TextureView's surface is available
|
|
97
|
+
// in the onSurfaceTextureAvailable callback
|
|
98
|
+
Log.d(TAG, "TextureView added to view hierarchy")
|
|
99
|
+
// Log dimensions of the view
|
|
100
|
+
post {
|
|
101
|
+
Log.d(TAG, "UnifiedPlayerView dimensions after init: width=${width}, height=${height}")
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
player?.addListener(object : Player.Listener {
|
|
@@ -175,24 +184,7 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
175
184
|
}
|
|
176
185
|
|
|
177
186
|
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
|
178
|
-
//
|
|
179
|
-
Log.d(TAG, "ExoPlayer onVideoSizeChanged: videoSize=$videoSize")
|
|
180
|
-
Log.d(TAG, "Video size changed: width=${videoSize.width}, height=${videoSize.height}")
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
override fun onSurfaceSizeChanged(width: Int, height: Int) {
|
|
184
|
-
// Called when the size of the surface changes.
|
|
185
|
-
Log.d(TAG, "ExoPlayer onSurfaceSizeChanged: width=$width, height=$height")
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
override fun onRenderedFirstFrame() {
|
|
189
|
-
// Called when the first frame is rendered.
|
|
190
|
-
Log.d(TAG, "ExoPlayer onRenderedFirstFrame")
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
override fun onSkipSilenceEnabledChanged(skipSilenceEnabled: Boolean) {
|
|
194
|
-
// Called when skip silence is enabled or disabled.
|
|
195
|
-
Log.d(TAG, "ExoPlayer onSkipSilenceEnabledChanged: skipSilenceEnabled=$skipSilenceEnabled")
|
|
187
|
+
// Handle video size changes if needed
|
|
196
188
|
}
|
|
197
189
|
})
|
|
198
190
|
}
|
|
@@ -253,14 +245,9 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
253
245
|
}
|
|
254
246
|
}
|
|
255
247
|
|
|
256
|
-
fun setAuthToken(token: String?) {
|
|
257
|
-
// Removed as per request
|
|
258
|
-
Log.d(TAG, "Auth token handling removed")
|
|
259
|
-
}
|
|
260
|
-
|
|
261
248
|
fun setAutoplay(value: Boolean) {
|
|
262
249
|
autoplay = value
|
|
263
|
-
player?.playWhenReady = value
|
|
250
|
+
player?.playWhenReady = value
|
|
264
251
|
}
|
|
265
252
|
|
|
266
253
|
fun setLoop(value: Boolean) {
|
|
@@ -296,7 +283,7 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
296
283
|
it.seekTo(milliseconds)
|
|
297
284
|
|
|
298
285
|
// Force a progress update after seeking
|
|
299
|
-
|
|
286
|
+
progressRunnable.run()
|
|
300
287
|
} ?: Log.e(TAG, "Cannot seek: player is null")
|
|
301
288
|
}
|
|
302
289
|
|
|
@@ -370,14 +357,39 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
370
357
|
val width = right - left
|
|
371
358
|
val height = bottom - top
|
|
372
359
|
Log.d(TAG, "UnifiedPlayerView onLayout: width=$width, height=$height")
|
|
373
|
-
// Ensure
|
|
374
|
-
|
|
360
|
+
// Ensure textureView gets laid out properly
|
|
361
|
+
textureView.layout(0, 0, width, height)
|
|
375
362
|
}
|
|
376
363
|
|
|
377
364
|
override fun onAttachedToWindow() {
|
|
378
365
|
super.onAttachedToWindow()
|
|
379
366
|
Log.d(TAG, "UnifiedPlayerView onAttachedToWindow")
|
|
380
|
-
|
|
367
|
+
textureView.surfaceTextureListener = object : android.view.TextureView.SurfaceTextureListener {
|
|
368
|
+
override fun onSurfaceTextureAvailable(surface: android.graphics.SurfaceTexture, width: Int, height: Int) {
|
|
369
|
+
Log.d(TAG, "TextureView onSurfaceTextureAvailable: width=$width, height=$height")
|
|
370
|
+
// Create a Surface from the SurfaceTexture and set it on the player
|
|
371
|
+
val videoSurface = android.view.Surface(surface)
|
|
372
|
+
player?.setVideoSurface(videoSurface)
|
|
373
|
+
Log.d(TAG, "Set video surface from TextureView's SurfaceTexture")
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
override fun onSurfaceTextureSizeChanged(surface: android.graphics.SurfaceTexture, width: Int, height: Int) {
|
|
377
|
+
Log.d(TAG, "TextureView onSurfaceTextureSizeChanged: width=$width, height=$height")
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
override fun onSurfaceTextureDestroyed(surface: android.graphics.SurfaceTexture): Boolean {
|
|
381
|
+
Log.d(TAG, "TextureView onSurfaceTextureDestroyed")
|
|
382
|
+
// Set the player's surface to null to release it
|
|
383
|
+
player?.setVideoSurface(null)
|
|
384
|
+
Log.d(TAG, "Cleared video surface from player")
|
|
385
|
+
return true
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
override fun onSurfaceTextureUpdated(surface: android.graphics.SurfaceTexture) {
|
|
389
|
+
// This is called very frequently, so we'll comment out this log
|
|
390
|
+
// Log.d(TAG, "TextureView onSurfaceTextureUpdated")
|
|
391
|
+
}
|
|
392
|
+
}
|
|
381
393
|
startProgressUpdates() // Use the new method to start progress updates
|
|
382
394
|
}
|
|
383
395
|
|
|
@@ -387,4 +399,62 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
387
399
|
progressHandler.removeCallbacks(progressRunnable) // Stop progress updates
|
|
388
400
|
player?.release()
|
|
389
401
|
}
|
|
390
|
-
|
|
402
|
+
|
|
403
|
+
fun capture(): String {
|
|
404
|
+
Log.d(TAG, "Capture method called")
|
|
405
|
+
return try {
|
|
406
|
+
player?.let { exoPlayer ->
|
|
407
|
+
// Get the video size from the player
|
|
408
|
+
val videoSize = exoPlayer.videoSize
|
|
409
|
+
if (videoSize.width <= 0 || videoSize.height <= 0) {
|
|
410
|
+
Log.e(TAG, "Invalid video dimensions: ${videoSize.width}x${videoSize.height}")
|
|
411
|
+
return ""
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Get bitmap directly from TextureView
|
|
415
|
+
val bitmap = textureView.bitmap ?: run {
|
|
416
|
+
Log.e(TAG, "Failed to get bitmap from TextureView")
|
|
417
|
+
return ""
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Debugging: Log the dimensions of the bitmap
|
|
421
|
+
bitmap.let {
|
|
422
|
+
Log.d(TAG, "Bitmap dimensions: width=${it.width}, height=${it.height}")
|
|
423
|
+
|
|
424
|
+
// Check if bitmap is empty (all black)
|
|
425
|
+
var hasNonBlackPixel = false
|
|
426
|
+
for (x in 0 until it.width) {
|
|
427
|
+
for (y in 0 until it.height) {
|
|
428
|
+
if (it.getPixel(x, y) != Color.BLACK) {
|
|
429
|
+
hasNonBlackPixel = true
|
|
430
|
+
break
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (hasNonBlackPixel) break
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!hasNonBlackPixel) {
|
|
437
|
+
Log.w(TAG, "Bitmap appears to be all black")
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Compress and encode the bitmap
|
|
441
|
+
val byteArrayOutputStream = ByteArrayOutputStream()
|
|
442
|
+
if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)) {
|
|
443
|
+
val byteArray = byteArrayOutputStream.toByteArray()
|
|
444
|
+
val base64EncodedString = Base64.encodeToString(byteArray, Base64.DEFAULT)
|
|
445
|
+
Log.d(TAG, "Capture successful, base64 length: ${base64EncodedString.length}")
|
|
446
|
+
base64EncodedString
|
|
447
|
+
} else {
|
|
448
|
+
Log.e(TAG, "Failed to compress bitmap")
|
|
449
|
+
""
|
|
450
|
+
}
|
|
451
|
+
} ?: run {
|
|
452
|
+
Log.e(TAG, "Cannot capture: player is null")
|
|
453
|
+
""
|
|
454
|
+
}
|
|
455
|
+
} catch (e: Exception) {
|
|
456
|
+
Log.e(TAG, "Error during capture: ${e.message}", e)
|
|
457
|
+
""
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|