react-native-unified-player 0.3.2 → 0.3.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 +0 -2
- package/UnifiedPlayer.podspec +1 -1
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerEventEmitter.kt +4 -1
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerModule.kt +130 -119
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerPackage.kt +6 -1
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerView.kt +82 -30
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerViewManager.kt +5 -1
- package/ios/UnifiedPlayerModule.h +5 -1
- package/ios/UnifiedPlayerModule.m +219 -0
- package/ios/UnifiedPlayerUIView.h +42 -0
- package/ios/UnifiedPlayerViewManager.m +123 -182
- package/lib/module/index.js +54 -7
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +21 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +95 -9
package/README.md
CHANGED
|
@@ -65,7 +65,6 @@ const MyPlayerComponent = () => {
|
|
|
65
65
|
videoUrl="YOUR_VIDEO_URL_HERE" // Replace with your video URL
|
|
66
66
|
autoplay={false} // Optional: set to true to autoplay
|
|
67
67
|
loop={false} // Optional: set to true to loop
|
|
68
|
-
// authToken="YOUR_AUTH_TOKEN" // Optional: for protected streams
|
|
69
68
|
// You can also use direct view props instead of or in addition to event listeners:
|
|
70
69
|
// onReadyToPlay={() => console.log('View prop: Ready to play')}
|
|
71
70
|
// onError={(e) => console.log('View prop: Error', e)}
|
|
@@ -87,7 +86,6 @@ export default MyPlayerComponent;
|
|
|
87
86
|
| `style` | `ViewStyle` | Yes | Apply custom styling |
|
|
88
87
|
| `autoplay` | `boolean` | No | Autoplay video when loaded |
|
|
89
88
|
| `loop` | `boolean` | No | Should video loop when finished |
|
|
90
|
-
| `authToken` | `string` | No | Optional auth token for protected streams |
|
|
91
89
|
| `onReadyToPlay` | `() => void` | No | Callback when video is ready to play |
|
|
92
90
|
| `onError` | `(error: any) => void` | No | Callback when an error occurs |
|
|
93
91
|
| `onPlaybackComplete` | `() => void` | No | Callback when video playback finishes |
|
package/UnifiedPlayer.podspec
CHANGED
|
@@ -10,7 +10,7 @@ Pod::Spec.new do |s|
|
|
|
10
10
|
s.license = package["license"]
|
|
11
11
|
s.authors = package["author"]
|
|
12
12
|
|
|
13
|
-
s.platforms = { :ios =>
|
|
13
|
+
s.platforms = { :ios => "13.0" }
|
|
14
14
|
s.source = { :git => "https://github.com/blueromans/react-native-unified-player.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
16
|
s.source_files = "ios/**/*.{h,m,mm}"
|
|
@@ -12,12 +12,15 @@ class UnifiedPlayerEventEmitter(private val reactContext: ReactApplicationContex
|
|
|
12
12
|
private const val TAG = "UnifiedPlayerEventEmitter"
|
|
13
13
|
|
|
14
14
|
// Define all possible event types
|
|
15
|
+
const val EVENT_LOAD_START = "onLoadStart"
|
|
15
16
|
const val EVENT_READY = "onReadyToPlay"
|
|
16
17
|
const val EVENT_ERROR = "onError"
|
|
17
18
|
const val EVENT_PROGRESS = "onProgress"
|
|
18
19
|
const val EVENT_COMPLETE = "onPlaybackComplete"
|
|
19
20
|
const val EVENT_STALLED = "onPlaybackStalled"
|
|
20
21
|
const val EVENT_RESUMED = "onPlaybackResumed"
|
|
22
|
+
const val EVENT_PLAYING = "onPlaying"
|
|
23
|
+
const val EVENT_PAUSED = "onPaused"
|
|
21
24
|
|
|
22
25
|
// Singleton instance for access from other classes
|
|
23
26
|
private var instance: UnifiedPlayerEventEmitter? = null
|
|
@@ -59,4 +62,4 @@ class UnifiedPlayerEventEmitter(private val reactContext: ReactApplicationContex
|
|
|
59
62
|
super.onCatalystInstanceDestroy()
|
|
60
63
|
instance = null
|
|
61
64
|
}
|
|
62
|
-
}
|
|
65
|
+
}
|
|
@@ -1,176 +1,187 @@
|
|
|
1
1
|
package com.unifiedplayer
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import com.facebook.react.bridge.ReactMethod
|
|
6
|
-
import com.facebook.react.bridge.Promise
|
|
7
|
-
import com.facebook.react.uimanager.UIManagerModule
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.util.Base64
|
|
8
5
|
import android.util.Log
|
|
9
|
-
import com.facebook.react.bridge
|
|
10
|
-
import
|
|
6
|
+
import com.facebook.react.bridge.*
|
|
7
|
+
import com.facebook.react.uimanager.UIManagerModule
|
|
8
|
+
import com.facebook.react.uimanager.UIBlock
|
|
9
|
+
import com.facebook.react.uimanager.NativeViewHierarchyManager
|
|
10
|
+
import java.io.ByteArrayOutputStream
|
|
11
11
|
|
|
12
12
|
class UnifiedPlayerModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
13
|
-
|
|
14
|
-
|
|
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? {
|
|
13
|
+
private var isModuleReady = false
|
|
14
|
+
|
|
15
|
+
override fun initialize() {
|
|
16
|
+
super.initialize()
|
|
27
17
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
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}")
|
|
18
|
+
reactContext.getNativeModule(UIManagerModule::class.java)?.let {
|
|
19
|
+
isModuleReady = true
|
|
20
|
+
Log.d(TAG, "Module successfully initialized")
|
|
35
21
|
}
|
|
36
22
|
} catch (e: Exception) {
|
|
37
|
-
Log.e(TAG, "
|
|
23
|
+
Log.e(TAG, "Initialization failed", e)
|
|
38
24
|
}
|
|
39
|
-
return null
|
|
40
25
|
}
|
|
41
|
-
|
|
26
|
+
companion object {
|
|
27
|
+
private const val TAG = "UnifiedPlayerModule"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override fun getName(): String = "UnifiedPlayer"
|
|
31
|
+
|
|
42
32
|
@ReactMethod
|
|
43
33
|
fun play(viewTag: Int, promise: Promise) {
|
|
44
|
-
Log.d(TAG, "Native play method called with viewTag: $viewTag")
|
|
45
34
|
try {
|
|
46
|
-
val
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
35
|
+
val uiManager = reactApplicationContext.getNativeModule(UIManagerModule::class.java)
|
|
36
|
+
?: throw IllegalStateException("UIManagerModule not available")
|
|
37
|
+
|
|
38
|
+
uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
|
|
39
|
+
try {
|
|
40
|
+
val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
|
|
41
|
+
if (view != null) {
|
|
42
|
+
view.play()
|
|
52
43
|
promise.resolve(true)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
promise.reject("PLAY_ERROR", "Error during play: ${e.message}", e)
|
|
44
|
+
} else {
|
|
45
|
+
promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
|
|
56
46
|
}
|
|
47
|
+
} catch (e: Exception) {
|
|
48
|
+
Log.e(TAG, "Error in play method", e)
|
|
49
|
+
promise.reject("PLAY_ERROR", "Error in play method", e)
|
|
57
50
|
}
|
|
58
|
-
}
|
|
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
|
-
}
|
|
51
|
+
})
|
|
62
52
|
} catch (e: Exception) {
|
|
63
|
-
Log.e(TAG, "Error in play method
|
|
64
|
-
promise.reject("
|
|
53
|
+
Log.e(TAG, "Error in play method", e)
|
|
54
|
+
promise.reject("UIMANAGER_ERROR", "UIManagerModule not available", e)
|
|
65
55
|
}
|
|
66
56
|
}
|
|
67
57
|
|
|
68
58
|
@ReactMethod
|
|
69
59
|
fun pause(viewTag: Int, promise: Promise) {
|
|
70
|
-
Log.d(TAG, "Native pause method called with viewTag: $viewTag")
|
|
71
60
|
try {
|
|
72
|
-
val
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
val uiManager = reactApplicationContext.getNativeModule(UIManagerModule::class.java)
|
|
62
|
+
?: throw IllegalStateException("UIManagerModule not available")
|
|
63
|
+
|
|
64
|
+
uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
|
|
65
|
+
try {
|
|
66
|
+
val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
|
|
67
|
+
if (view != null) {
|
|
68
|
+
view.pause()
|
|
78
69
|
promise.resolve(true)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
promise.reject("PAUSE_ERROR", "Error during pause: ${e.message}", e)
|
|
70
|
+
} else {
|
|
71
|
+
promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
|
|
82
72
|
}
|
|
73
|
+
} catch (e: Exception) {
|
|
74
|
+
Log.e(TAG, "Error in pause method", e)
|
|
75
|
+
promise.reject("PAUSE_ERROR", "Error in pause method", e)
|
|
83
76
|
}
|
|
84
|
-
}
|
|
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
|
-
}
|
|
77
|
+
})
|
|
88
78
|
} catch (e: Exception) {
|
|
89
|
-
Log.e(TAG, "Error in pause method
|
|
90
|
-
promise.reject("
|
|
79
|
+
Log.e(TAG, "Error in pause method", e)
|
|
80
|
+
promise.reject("UIMANAGER_ERROR", "UIManagerModule not available", e)
|
|
91
81
|
}
|
|
92
82
|
}
|
|
93
83
|
|
|
94
84
|
@ReactMethod
|
|
95
85
|
fun seekTo(viewTag: Int, seconds: Float, promise: Promise) {
|
|
96
|
-
Log.d(TAG, "Native seekTo method called with viewTag: $viewTag, seconds: $seconds")
|
|
97
86
|
try {
|
|
98
|
-
val
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
87
|
+
val uiManager = reactApplicationContext.getNativeModule(UIManagerModule::class.java)
|
|
88
|
+
?: throw IllegalStateException("UIManagerModule not available")
|
|
89
|
+
|
|
90
|
+
uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
|
|
91
|
+
try {
|
|
92
|
+
val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
|
|
93
|
+
if (view != null) {
|
|
94
|
+
view.seekTo(seconds)
|
|
104
95
|
promise.resolve(true)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
promise.reject("SEEK_ERROR", "Error during seekTo: ${e.message}", e)
|
|
96
|
+
} else {
|
|
97
|
+
promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
|
|
108
98
|
}
|
|
99
|
+
} catch (e: Exception) {
|
|
100
|
+
Log.e(TAG, "Error in seekTo method", e)
|
|
101
|
+
promise.reject("SEEK_ERROR", "Error in seekTo method", e)
|
|
109
102
|
}
|
|
110
|
-
}
|
|
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
|
-
}
|
|
103
|
+
})
|
|
114
104
|
} catch (e: Exception) {
|
|
115
|
-
Log.e(TAG, "Error in seekTo method
|
|
116
|
-
promise.reject("
|
|
105
|
+
Log.e(TAG, "Error in seekTo method", e)
|
|
106
|
+
promise.reject("UIMANAGER_ERROR", "UIManagerModule not available", e)
|
|
117
107
|
}
|
|
118
108
|
}
|
|
119
109
|
|
|
120
110
|
@ReactMethod
|
|
121
111
|
fun getCurrentTime(viewTag: Int, promise: Promise) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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)
|
|
112
|
+
reactApplicationContext.getNativeModule(UIManagerModule::class.java)?.let { uiManager ->
|
|
113
|
+
uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
|
|
114
|
+
try {
|
|
115
|
+
val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
|
|
116
|
+
if (view != null) {
|
|
117
|
+
promise.resolve(view.getCurrentTime())
|
|
118
|
+
} else {
|
|
119
|
+
promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
|
|
134
120
|
}
|
|
121
|
+
} catch (e: Exception) {
|
|
122
|
+
Log.e(TAG, "Error in getCurrentTime method", e)
|
|
123
|
+
promise.reject("GET_TIME_ERROR", "Error in getCurrentTime method", e)
|
|
135
124
|
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
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)
|
|
125
|
+
})
|
|
126
|
+
} ?: run {
|
|
127
|
+
promise.reject("ERROR", "UIManagerModule not available")
|
|
143
128
|
}
|
|
144
129
|
}
|
|
145
130
|
|
|
146
131
|
@ReactMethod
|
|
147
132
|
fun getDuration(viewTag: Int, promise: Promise) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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)
|
|
133
|
+
reactApplicationContext.getNativeModule(UIManagerModule::class.java)?.let { uiManager ->
|
|
134
|
+
uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
|
|
135
|
+
try {
|
|
136
|
+
val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
|
|
137
|
+
if (view != null) {
|
|
138
|
+
promise.resolve(view.getDuration())
|
|
139
|
+
} else {
|
|
140
|
+
promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
|
|
160
141
|
}
|
|
142
|
+
} catch (e: Exception) {
|
|
143
|
+
Log.e(TAG, "Error in getDuration method", e)
|
|
144
|
+
promise.reject("GET_DURATION_ERROR", "Error in getDuration method", e)
|
|
161
145
|
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
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)
|
|
146
|
+
})
|
|
147
|
+
} ?: run {
|
|
148
|
+
promise.reject("ERROR", "UIManagerModule not available")
|
|
169
149
|
}
|
|
170
150
|
}
|
|
171
151
|
|
|
172
152
|
@ReactMethod
|
|
173
|
-
fun
|
|
174
|
-
|
|
153
|
+
fun capture(viewTag: Int, promise: Promise) {
|
|
154
|
+
try {
|
|
155
|
+
if (!isModuleReady) {
|
|
156
|
+
throw IllegalStateException("Module not ready. Ensure React Native bridge is initialized.")
|
|
157
|
+
}
|
|
158
|
+
val uiManager = reactApplicationContext.getNativeModule(UIManagerModule::class.java)
|
|
159
|
+
?: throw IllegalStateException("UIManagerModule not available. Is the bridge active?")
|
|
160
|
+
|
|
161
|
+
uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
|
|
162
|
+
try {
|
|
163
|
+
val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
|
|
164
|
+
if (view != null) {
|
|
165
|
+
val bitmap = view.captureFrame()
|
|
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
|
+
}
|
|
177
|
+
} catch (e: Exception) {
|
|
178
|
+
Log.e(TAG, "Error in capture method", e)
|
|
179
|
+
promise.reject("CAPTURE_ERROR", "Error in capture method", e)
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
} catch (e: Exception) {
|
|
183
|
+
Log.e(TAG, "Error in capture method", e)
|
|
184
|
+
promise.reject("UIMANAGER_ERROR", "UIManagerModule not available", e)
|
|
185
|
+
}
|
|
175
186
|
}
|
|
176
187
|
}
|
|
@@ -12,7 +12,12 @@ 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)
|
|
15
|
+
UnifiedPlayerModule(reactContext).apply {
|
|
16
|
+
reactContext.runOnUiQueueThread {
|
|
17
|
+
// Initialize module on UI thread
|
|
18
|
+
Log.d(TAG, "Module initialized on UI thread")
|
|
19
|
+
}
|
|
20
|
+
}
|
|
16
21
|
)
|
|
17
22
|
}
|
|
18
23
|
|
|
@@ -4,9 +4,13 @@ import android.annotation.SuppressLint
|
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.graphics.Color
|
|
6
6
|
import android.util.Log
|
|
7
|
+
import android.graphics.Bitmap
|
|
8
|
+
import android.graphics.Canvas
|
|
7
9
|
import android.os.Handler
|
|
8
10
|
import android.os.Looper
|
|
9
11
|
import android.view.Gravity
|
|
12
|
+
import android.view.TextureView
|
|
13
|
+
import android.view.View
|
|
10
14
|
import android.widget.FrameLayout
|
|
11
15
|
import com.facebook.react.bridge.Arguments
|
|
12
16
|
import com.google.android.exoplayer2.ExoPlayer
|
|
@@ -19,6 +23,15 @@ import com.google.android.exoplayer2.video.VideoSize
|
|
|
19
23
|
import com.facebook.react.bridge.WritableMap
|
|
20
24
|
import com.facebook.react.bridge.ReactContext
|
|
21
25
|
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
26
|
+
import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_COMPLETE
|
|
27
|
+
import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_ERROR
|
|
28
|
+
import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_LOAD_START
|
|
29
|
+
import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_PAUSED
|
|
30
|
+
import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_PLAYING
|
|
31
|
+
import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_PROGRESS
|
|
32
|
+
import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_READY
|
|
33
|
+
import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_RESUMED
|
|
34
|
+
import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_STALLED
|
|
22
35
|
|
|
23
36
|
class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
24
37
|
companion object {
|
|
@@ -50,7 +63,7 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
50
63
|
event.putDouble("duration", duration.toDouble())
|
|
51
64
|
|
|
52
65
|
Log.d(TAG, "Sending progress event: currentTime=$currentTime, duration=$duration")
|
|
53
|
-
sendEvent(
|
|
66
|
+
sendEvent(EVENT_PROGRESS, event)
|
|
54
67
|
} else {
|
|
55
68
|
Log.d(TAG, "Not sending progress event because duration is $duration (raw: ${it.duration})")
|
|
56
69
|
}
|
|
@@ -89,14 +102,15 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
89
102
|
when (playbackState) {
|
|
90
103
|
Player.STATE_READY -> {
|
|
91
104
|
Log.d(TAG, "ExoPlayer STATE_READY")
|
|
92
|
-
sendEvent(
|
|
105
|
+
sendEvent(EVENT_READY, Arguments.createMap())
|
|
93
106
|
}
|
|
94
107
|
Player.STATE_ENDED -> {
|
|
95
108
|
Log.d(TAG, "ExoPlayer STATE_ENDED")
|
|
96
|
-
sendEvent(
|
|
109
|
+
sendEvent(EVENT_COMPLETE, Arguments.createMap())
|
|
97
110
|
}
|
|
98
111
|
Player.STATE_BUFFERING -> {
|
|
99
112
|
Log.d(TAG, "ExoPlayer STATE_BUFFERING")
|
|
113
|
+
sendEvent(EVENT_STALLED, Arguments.createMap())
|
|
100
114
|
}
|
|
101
115
|
Player.STATE_IDLE -> {
|
|
102
116
|
Log.d(TAG, "ExoPlayer STATE_IDLE")
|
|
@@ -105,29 +119,28 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
108
|
-
Log.d(TAG, "onIsPlayingChanged: $isPlaying")
|
|
122
|
+
Log.d(TAG, "onIsPlayingChanged: $isPlaying") // Added log
|
|
109
123
|
if (isPlaying) {
|
|
110
|
-
Log.d(TAG, "ExoPlayer
|
|
111
|
-
|
|
112
|
-
sendEvent(
|
|
124
|
+
Log.d(TAG, "ExoPlayer is now playing")
|
|
125
|
+
sendEvent(EVENT_RESUMED, Arguments.createMap())
|
|
126
|
+
sendEvent(EVENT_PLAYING, Arguments.createMap())
|
|
113
127
|
} else {
|
|
114
|
-
Log.d(TAG, "ExoPlayer
|
|
115
|
-
|
|
116
|
-
sendEvent("topPlaybackPaused", Arguments.createMap())
|
|
128
|
+
Log.d(TAG, "ExoPlayer is now paused")
|
|
129
|
+
sendEvent(EVENT_PAUSED, Arguments.createMap())
|
|
117
130
|
}
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
override fun onPlayerError(error: PlaybackException) {
|
|
121
|
-
Log.
|
|
122
|
-
val event = Arguments.createMap()
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
sendEvent(
|
|
134
|
+
Log.e(TAG, "ExoPlayer error: $error")
|
|
135
|
+
val event = Arguments.createMap()
|
|
136
|
+
event.putString("code", "PLAYBACK_ERROR")
|
|
137
|
+
event.putString("message", error.message ?: "Unknown playback error")
|
|
138
|
+
sendEvent(EVENT_ERROR, event)
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
|
129
|
-
Log.d(TAG, "
|
|
130
|
-
sendEvent(
|
|
142
|
+
Log.d(TAG, "onMediaItemTransition with reason: $reason")
|
|
143
|
+
sendEvent(EVENT_LOAD_START, Arguments.createMap())
|
|
131
144
|
}
|
|
132
145
|
|
|
133
146
|
override fun onPlaybackSuppressionReasonChanged(playbackSuppressionReason: Int) {
|
|
@@ -238,8 +251,9 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
238
251
|
|
|
239
252
|
// Send error event
|
|
240
253
|
val event = Arguments.createMap()
|
|
241
|
-
event.putString("
|
|
242
|
-
|
|
254
|
+
event.putString("code", "SOURCE_ERROR")
|
|
255
|
+
event.putString("message", "Failed to load video source: $url")
|
|
256
|
+
sendEvent(EVENT_ERROR, event)
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
259
|
|
|
@@ -302,17 +316,41 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
302
316
|
}
|
|
303
317
|
}
|
|
304
318
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
319
|
+
fun getDuration(): Float {
|
|
320
|
+
Log.d(TAG, "GetDuration method called")
|
|
321
|
+
return player?.let {
|
|
322
|
+
val duration = it.duration.toFloat() / 1000f
|
|
323
|
+
Log.d(TAG, "Duration: $duration seconds (raw: ${it.duration})")
|
|
324
|
+
if (it.duration > 0) duration else 0f
|
|
325
|
+
} ?: run {
|
|
326
|
+
Log.e(TAG, "Cannot get duration: player is null")
|
|
327
|
+
0f
|
|
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
|
|
315
352
|
}
|
|
353
|
+
}
|
|
316
354
|
|
|
317
355
|
// Add a getter for the ExoPlayer instance
|
|
318
356
|
val exoPlayer: ExoPlayer?
|
|
@@ -323,10 +361,24 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
323
361
|
// Log the event for debugging
|
|
324
362
|
Log.d(TAG, "Sending direct event: $eventName with params: $params")
|
|
325
363
|
|
|
364
|
+
// Map event names to their corresponding top event names
|
|
365
|
+
val topEventName = when (eventName) {
|
|
366
|
+
EVENT_READY -> "topReadyToPlay"
|
|
367
|
+
EVENT_ERROR -> "topError"
|
|
368
|
+
EVENT_PROGRESS -> "topProgress"
|
|
369
|
+
EVENT_COMPLETE -> "topPlaybackComplete"
|
|
370
|
+
EVENT_STALLED -> "topPlaybackStalled"
|
|
371
|
+
EVENT_RESUMED -> "topPlaybackResumed"
|
|
372
|
+
EVENT_PLAYING -> "topPlaying"
|
|
373
|
+
EVENT_PAUSED -> "topPlaybackPaused"
|
|
374
|
+
EVENT_LOAD_START -> "topLoadStart"
|
|
375
|
+
else -> "top${eventName.substring(2)}" // Fallback for any other events
|
|
376
|
+
}
|
|
377
|
+
|
|
326
378
|
// Use the ReactContext to dispatch the event directly to the view
|
|
327
379
|
val reactContext = context as ReactContext
|
|
328
380
|
reactContext.getJSModule(RCTEventEmitter::class.java)
|
|
329
|
-
.receiveEvent(id,
|
|
381
|
+
.receiveEvent(id, topEventName, params)
|
|
330
382
|
} catch (e: Exception) {
|
|
331
383
|
Log.e(TAG, "Error sending event $eventName: ${e.message}", e)
|
|
332
384
|
}
|
|
@@ -37,6 +37,8 @@ class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
|
|
|
37
37
|
fun setIsPaused(view: UnifiedPlayerView, isPaused: Boolean) {
|
|
38
38
|
view.setIsPaused(isPaused)
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
|
|
40
42
|
|
|
41
43
|
// Register direct events
|
|
42
44
|
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
|
|
@@ -49,7 +51,9 @@ class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
|
|
|
49
51
|
.put("topProgress", MapBuilder.of("registrationName", "onProgress"))
|
|
50
52
|
.put("topPlaybackComplete", MapBuilder.of("registrationName", "onPlaybackComplete"))
|
|
51
53
|
.put("topPlaybackResumed", MapBuilder.of("registrationName", "onPlaybackResumed"))
|
|
52
|
-
.put("
|
|
54
|
+
.put("topPlaybackStalled", MapBuilder.of("registrationName", "onPlaybackStalled"))
|
|
55
|
+
.put("topPlaybackPaused", MapBuilder.of("registrationName", "onPaused"))
|
|
56
|
+
.put("topPlaying", MapBuilder.of("registrationName", "onPlaying"))
|
|
53
57
|
.put("topLoadStart", MapBuilder.of("registrationName", "onLoadStart"))
|
|
54
58
|
.build()
|
|
55
59
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
#import <React/RCTEventEmitter.h>
|
|
2
2
|
#import <React/RCTBridgeModule.h>
|
|
3
|
+
#import <MobileVLCKit/MobileVLCKit.h> // Import MobileVLCKit
|
|
4
|
+
|
|
5
|
+
// Forward declaration for UnifiedPlayerUIView
|
|
6
|
+
@class UnifiedPlayerUIView;
|
|
3
7
|
|
|
4
8
|
@interface UnifiedPlayerModule : RCTEventEmitter <RCTBridgeModule>
|
|
5
|
-
@end
|
|
9
|
+
@end
|