react-native-unified-player 0.2.4 → 0.3.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.
- package/android/build.gradle +2 -1
- package/android/gradle.properties +1 -0
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerModule.kt +125 -42
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerPackage.kt +6 -3
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerView.kt +163 -62
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerViewManager.kt +28 -5
- package/ios/UnifiedPlayerViewManager.m +22 -0
- package/lib/module/index.js +77 -53
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +33 -18
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/index.tsx +92 -62
package/android/build.gradle
CHANGED
|
@@ -9,7 +9,7 @@ buildscript {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
dependencies {
|
|
12
|
-
classpath "com.android.tools.build:gradle:
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.1.0"
|
|
13
13
|
// noinspection DifferentKotlinGradleVersion
|
|
14
14
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0"
|
|
15
15
|
}
|
|
@@ -68,6 +68,7 @@ android {
|
|
|
68
68
|
repositories {
|
|
69
69
|
mavenCentral()
|
|
70
70
|
google()
|
|
71
|
+
maven { url "$rootDir/../node_modules/react-native/android" }
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
@@ -6,88 +6,171 @@ import com.facebook.react.bridge.ReactMethod
|
|
|
6
6
|
import com.facebook.react.bridge.Promise
|
|
7
7
|
import com.facebook.react.uimanager.UIManagerModule
|
|
8
8
|
import android.util.Log
|
|
9
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
10
|
+
import android.view.View
|
|
9
11
|
|
|
10
12
|
class UnifiedPlayerModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
11
13
|
companion object {
|
|
12
14
|
private const val TAG = "UnifiedPlayerModule"
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
init {
|
|
18
|
+
Log.d(TAG, "UnifiedPlayerModule initialized")
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
override fun getName(): String {
|
|
22
|
+
Log.d(TAG, "getName() called, returning 'UnifiedPlayer'")
|
|
16
23
|
return "UnifiedPlayer"
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
private fun
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
private fun getPlayerViewByTag(viewTag: Int): UnifiedPlayerView? {
|
|
27
|
+
try {
|
|
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}")
|
|
35
|
+
}
|
|
36
|
+
} catch (e: Exception) {
|
|
37
|
+
Log.e(TAG, "Error finding view with tag $viewTag: ${e.message}", e)
|
|
25
38
|
}
|
|
26
|
-
|
|
27
|
-
return view
|
|
39
|
+
return null
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
@ReactMethod
|
|
31
|
-
fun play(
|
|
43
|
+
fun play(viewTag: Int, promise: Promise) {
|
|
44
|
+
Log.d(TAG, "Native play method called with viewTag: $viewTag")
|
|
32
45
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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")
|
|
52
|
+
promise.resolve(true)
|
|
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)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
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")
|
|
36
61
|
}
|
|
37
62
|
} catch (e: Exception) {
|
|
38
|
-
Log.e(TAG, "Error
|
|
63
|
+
Log.e(TAG, "Error in play method: ${e.message}", e)
|
|
64
|
+
promise.reject("PLAY_ERROR", "Error in play method: ${e.message}", e)
|
|
39
65
|
}
|
|
40
66
|
}
|
|
41
|
-
|
|
67
|
+
|
|
42
68
|
@ReactMethod
|
|
43
|
-
fun pause(
|
|
69
|
+
fun pause(viewTag: Int, promise: Promise) {
|
|
70
|
+
Log.d(TAG, "Native pause method called with viewTag: $viewTag")
|
|
44
71
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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")
|
|
78
|
+
promise.resolve(true)
|
|
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)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
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")
|
|
48
87
|
}
|
|
49
88
|
} catch (e: Exception) {
|
|
50
|
-
Log.e(TAG, "Error
|
|
89
|
+
Log.e(TAG, "Error in pause method: ${e.message}", e)
|
|
90
|
+
promise.reject("PAUSE_ERROR", "Error in pause method: ${e.message}", e)
|
|
51
91
|
}
|
|
52
92
|
}
|
|
53
|
-
|
|
93
|
+
|
|
54
94
|
@ReactMethod
|
|
55
|
-
fun seekTo(
|
|
95
|
+
fun seekTo(viewTag: Int, seconds: Float, promise: Promise) {
|
|
96
|
+
Log.d(TAG, "Native seekTo method called with viewTag: $viewTag, seconds: $seconds")
|
|
56
97
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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")
|
|
104
|
+
promise.resolve(true)
|
|
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)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
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")
|
|
60
113
|
}
|
|
61
114
|
} catch (e: Exception) {
|
|
62
|
-
Log.e(TAG, "Error
|
|
115
|
+
Log.e(TAG, "Error in seekTo method: ${e.message}", e)
|
|
116
|
+
promise.reject("SEEK_ERROR", "Error in seekTo method: ${e.message}", e)
|
|
63
117
|
}
|
|
64
118
|
}
|
|
65
|
-
|
|
119
|
+
|
|
66
120
|
@ReactMethod
|
|
67
|
-
fun getCurrentTime(
|
|
121
|
+
fun getCurrentTime(viewTag: Int, promise: Promise) {
|
|
122
|
+
Log.d(TAG, "Native getCurrentTime method called with viewTag: $viewTag")
|
|
68
123
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
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")
|
|
73
139
|
}
|
|
74
140
|
} catch (e: Exception) {
|
|
75
|
-
Log.e(TAG, "Error
|
|
76
|
-
promise.reject("
|
|
141
|
+
Log.e(TAG, "Error in getCurrentTime method: ${e.message}", e)
|
|
142
|
+
promise.reject("GET_TIME_ERROR", "Error in getCurrentTime method: ${e.message}", e)
|
|
77
143
|
}
|
|
78
144
|
}
|
|
79
|
-
|
|
145
|
+
|
|
80
146
|
@ReactMethod
|
|
81
|
-
fun getDuration(
|
|
147
|
+
fun getDuration(viewTag: Int, promise: Promise) {
|
|
148
|
+
Log.d(TAG, "Native getDuration method called with viewTag: $viewTag")
|
|
82
149
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
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")
|
|
87
165
|
}
|
|
88
166
|
} catch (e: Exception) {
|
|
89
|
-
Log.e(TAG, "Error
|
|
90
|
-
promise.reject("
|
|
167
|
+
Log.e(TAG, "Error in getDuration method: ${e.message}", e)
|
|
168
|
+
promise.reject("GET_DURATION_ERROR", "Error in getDuration method: ${e.message}", e)
|
|
91
169
|
}
|
|
92
170
|
}
|
|
93
|
-
|
|
171
|
+
|
|
172
|
+
@ReactMethod
|
|
173
|
+
fun testMethod(message: String) {
|
|
174
|
+
Log.d(TAG, "Test method called with message: $message")
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -4,17 +4,20 @@ import com.facebook.react.ReactPackage
|
|
|
4
4
|
import com.facebook.react.bridge.NativeModule
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
6
|
import com.facebook.react.uimanager.ViewManager
|
|
7
|
-
|
|
7
|
+
import android.util.Log
|
|
8
8
|
|
|
9
9
|
class UnifiedPlayerPackage : ReactPackage {
|
|
10
|
+
private val TAG = "UnifiedPlayerPackage"
|
|
11
|
+
|
|
10
12
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
13
|
+
Log.d(TAG, "Creating native modules")
|
|
11
14
|
return listOf(
|
|
12
|
-
UnifiedPlayerModule(reactContext)
|
|
13
|
-
UnifiedPlayerEventEmitter(reactContext)
|
|
15
|
+
UnifiedPlayerModule(reactContext)
|
|
14
16
|
)
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
20
|
+
Log.d(TAG, "Creating view managers")
|
|
18
21
|
return listOf(UnifiedPlayerViewManager())
|
|
19
22
|
}
|
|
20
23
|
}
|
|
@@ -4,6 +4,8 @@ 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.os.Handler
|
|
8
|
+
import android.os.Looper
|
|
7
9
|
import android.view.Gravity
|
|
8
10
|
import android.widget.FrameLayout
|
|
9
11
|
import com.facebook.react.bridge.Arguments
|
|
@@ -11,11 +13,12 @@ import com.google.android.exoplayer2.ExoPlayer
|
|
|
11
13
|
import com.google.android.exoplayer2.MediaItem
|
|
12
14
|
import com.google.android.exoplayer2.Player
|
|
13
15
|
import com.google.android.exoplayer2.PlaybackException
|
|
14
|
-
import com.google.android.exoplayer2.Timeline
|
|
15
16
|
import com.google.android.exoplayer2.Tracks
|
|
16
17
|
import com.google.android.exoplayer2.ui.PlayerView
|
|
17
18
|
import com.google.android.exoplayer2.video.VideoSize
|
|
18
19
|
import com.facebook.react.bridge.WritableMap
|
|
20
|
+
import com.facebook.react.bridge.ReactContext
|
|
21
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
19
22
|
|
|
20
23
|
class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
21
24
|
companion object {
|
|
@@ -23,13 +26,40 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
private var videoUrl: String? = null
|
|
26
|
-
private var authToken: String? = null
|
|
27
29
|
private var autoplay: Boolean = true
|
|
28
30
|
private var loop: Boolean = false
|
|
29
31
|
private var playerView: PlayerView
|
|
30
32
|
private var player: ExoPlayer? = null
|
|
31
|
-
private var isPlaying = false
|
|
32
33
|
private var currentProgress = 0
|
|
34
|
+
private var isPaused = false
|
|
35
|
+
|
|
36
|
+
private val progressHandler = Handler(Looper.getMainLooper())
|
|
37
|
+
private val progressRunnable: Runnable = object : Runnable {
|
|
38
|
+
override fun run() {
|
|
39
|
+
player?.let {
|
|
40
|
+
val currentTime = it.currentPosition.toFloat() / 1000f
|
|
41
|
+
val duration = it.duration.toFloat() / 1000f
|
|
42
|
+
|
|
43
|
+
// Log the actual values for debugging
|
|
44
|
+
Log.d(TAG, "Progress values - currentTime: $currentTime, duration: $duration, raw duration: ${it.duration}")
|
|
45
|
+
|
|
46
|
+
// Only send valid duration values
|
|
47
|
+
if (it.duration > 0) {
|
|
48
|
+
val event = Arguments.createMap()
|
|
49
|
+
event.putDouble("currentTime", currentTime.toDouble())
|
|
50
|
+
event.putDouble("duration", duration.toDouble())
|
|
51
|
+
|
|
52
|
+
Log.d(TAG, "Sending progress event: currentTime=$currentTime, duration=$duration")
|
|
53
|
+
sendEvent("topProgress", event)
|
|
54
|
+
} else {
|
|
55
|
+
Log.d(TAG, "Not sending progress event because duration is $duration (raw: ${it.duration})")
|
|
56
|
+
}
|
|
57
|
+
} ?: Log.e(TAG, "Cannot send progress event: player is null")
|
|
58
|
+
|
|
59
|
+
// Schedule the next update
|
|
60
|
+
progressHandler.postDelayed(this, 250) // Update every 250ms
|
|
61
|
+
}
|
|
62
|
+
}
|
|
33
63
|
|
|
34
64
|
init {
|
|
35
65
|
setBackgroundColor(Color.BLACK)
|
|
@@ -51,12 +81,6 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
51
81
|
// Add logging for playerView dimensions and post play call
|
|
52
82
|
playerView.post {
|
|
53
83
|
Log.d(TAG, "PlayerView dimensions after addView: width=${playerView.width}, height=${playerView.height}")
|
|
54
|
-
// Post play call to ensure PlayerView is laid out
|
|
55
|
-
if (autoplay) {
|
|
56
|
-
player?.play()
|
|
57
|
-
isPlaying = true
|
|
58
|
-
Log.d(TAG, "Autoplay: player?.play() called after layout")
|
|
59
|
-
}
|
|
60
84
|
}
|
|
61
85
|
|
|
62
86
|
player?.addListener(object : Player.Listener {
|
|
@@ -65,12 +89,11 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
65
89
|
when (playbackState) {
|
|
66
90
|
Player.STATE_READY -> {
|
|
67
91
|
Log.d(TAG, "ExoPlayer STATE_READY")
|
|
68
|
-
sendEvent("
|
|
69
|
-
// Removed custom autoplay logic, relying on player?.playWhenReady
|
|
92
|
+
sendEvent("topReadyToPlay", Arguments.createMap())
|
|
70
93
|
}
|
|
71
94
|
Player.STATE_ENDED -> {
|
|
72
95
|
Log.d(TAG, "ExoPlayer STATE_ENDED")
|
|
73
|
-
sendEvent("
|
|
96
|
+
sendEvent("topPlaybackComplete", Arguments.createMap())
|
|
74
97
|
}
|
|
75
98
|
Player.STATE_BUFFERING -> {
|
|
76
99
|
Log.d(TAG, "ExoPlayer STATE_BUFFERING")
|
|
@@ -78,34 +101,33 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
78
101
|
Player.STATE_IDLE -> {
|
|
79
102
|
Log.d(TAG, "ExoPlayer STATE_IDLE")
|
|
80
103
|
}
|
|
81
|
-
Player.STATE_BUFFERING -> {
|
|
82
|
-
Log.d(TAG, "ExoPlayer STATE_BUFFERING")
|
|
83
|
-
}
|
|
84
104
|
}
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
108
|
+
Log.d(TAG, "onIsPlayingChanged: $isPlaying")
|
|
88
109
|
if (isPlaying) {
|
|
89
110
|
Log.d(TAG, "ExoPlayer isPlaying")
|
|
90
|
-
|
|
111
|
+
// Use the defined event constant for playback resumed
|
|
112
|
+
sendEvent("topPlaybackResumed", Arguments.createMap())
|
|
91
113
|
} else {
|
|
92
114
|
Log.d(TAG, "ExoPlayer isPaused")
|
|
93
|
-
|
|
115
|
+
// Add event emission for pause state
|
|
116
|
+
sendEvent("topPlaybackPaused", Arguments.createMap())
|
|
94
117
|
}
|
|
95
118
|
}
|
|
96
119
|
|
|
97
120
|
override fun onPlayerError(error: PlaybackException) {
|
|
98
|
-
Log.
|
|
121
|
+
Log.d(TAG, "ExoPlayer onPlayerError: ${error.message}")
|
|
99
122
|
val event = Arguments.createMap().apply {
|
|
100
|
-
|
|
101
|
-
putString("message", error.message)
|
|
123
|
+
putString("error", error.message)
|
|
102
124
|
}
|
|
103
|
-
sendEvent("
|
|
125
|
+
sendEvent("topError", event)
|
|
104
126
|
}
|
|
105
127
|
|
|
106
128
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
|
107
129
|
Log.d(TAG, "ExoPlayer onMediaItemTransition")
|
|
108
|
-
sendEvent("
|
|
130
|
+
sendEvent("topLoadStart", Arguments.createMap())
|
|
109
131
|
}
|
|
110
132
|
|
|
111
133
|
override fun onPlaybackSuppressionReasonChanged(playbackSuppressionReason: Int) {
|
|
@@ -163,39 +185,67 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
163
185
|
// Called when skip silence is enabled or disabled.
|
|
164
186
|
Log.d(TAG, "ExoPlayer onSkipSilenceEnabledChanged: skipSilenceEnabled=$skipSilenceEnabled")
|
|
165
187
|
}
|
|
166
|
-
|
|
167
|
-
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
|
|
168
|
-
// Called when the timeline changes, like when a new media source is loaded or an ad break starts or ends.
|
|
169
|
-
Log.d(TAG, "ExoPlayer onTimelineChanged: timeline=$timeline, reason=$reason")
|
|
170
|
-
}
|
|
171
188
|
})
|
|
172
189
|
}
|
|
173
190
|
|
|
174
191
|
fun setVideoUrl(url: String?) {
|
|
175
192
|
Log.d(TAG, "Setting video URL: $url")
|
|
176
|
-
|
|
177
|
-
|
|
193
|
+
|
|
194
|
+
if (url == null || url.isEmpty()) {
|
|
195
|
+
Log.e(TAG, "Empty or null URL provided")
|
|
178
196
|
return
|
|
179
197
|
}
|
|
180
|
-
|
|
198
|
+
|
|
181
199
|
videoUrl = url
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// Create a MediaItem
|
|
203
|
+
val mediaItem = MediaItem.fromUri(url)
|
|
204
|
+
|
|
205
|
+
// Reset the player to ensure clean state
|
|
206
|
+
player?.stop()
|
|
207
|
+
player?.clearMediaItems()
|
|
208
|
+
|
|
209
|
+
// Set the media item
|
|
210
|
+
player?.setMediaItem(mediaItem)
|
|
211
|
+
|
|
212
|
+
// Prepare the player (this will start loading the media)
|
|
213
|
+
player?.prepare()
|
|
214
|
+
|
|
215
|
+
// Set playWhenReady based on autoplay setting
|
|
216
|
+
player?.playWhenReady = autoplay && !isPaused
|
|
217
|
+
|
|
218
|
+
// Set repeat mode based on loop setting
|
|
219
|
+
player?.repeatMode = if (loop) Player.REPEAT_MODE_ALL else Player.REPEAT_MODE_OFF
|
|
220
|
+
|
|
221
|
+
// Log that we've set up the player
|
|
222
|
+
Log.d(TAG, "ExoPlayer configured with URL: $url, autoplay: $autoplay, loop: $loop")
|
|
223
|
+
|
|
224
|
+
// Add a listener to check when the player is ready and has duration
|
|
225
|
+
player?.addListener(object : Player.Listener {
|
|
226
|
+
override fun onPlaybackStateChanged(state: Int) {
|
|
227
|
+
if (state == Player.STATE_READY) {
|
|
228
|
+
val duration = player?.duration ?: 0
|
|
229
|
+
Log.d(TAG, "Player ready with duration: ${duration / 1000f} seconds")
|
|
230
|
+
|
|
231
|
+
// Force a progress update immediately
|
|
232
|
+
progressRunnable.run()
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
} catch (e: Exception) {
|
|
237
|
+
Log.e(TAG, "Error setting video URL: ${e.message}", e)
|
|
238
|
+
|
|
239
|
+
// Send error event
|
|
240
|
+
val event = Arguments.createMap()
|
|
241
|
+
event.putString("error", "Failed to load video: ${e.message}")
|
|
242
|
+
sendEvent("topError", event)
|
|
243
|
+
}
|
|
191
244
|
}
|
|
192
245
|
|
|
193
246
|
fun setAuthToken(token: String?) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (!videoUrl.isNullOrEmpty()) {
|
|
197
|
-
setVideoUrl(videoUrl) // reload video
|
|
198
|
-
}
|
|
247
|
+
// Removed as per request
|
|
248
|
+
Log.d(TAG, "Auth token handling removed")
|
|
199
249
|
}
|
|
200
250
|
|
|
201
251
|
fun setAutoplay(value: Boolean) {
|
|
@@ -208,33 +258,87 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
208
258
|
player?.repeatMode = if (loop) Player.REPEAT_MODE_ALL else Player.REPEAT_MODE_OFF
|
|
209
259
|
}
|
|
210
260
|
|
|
261
|
+
fun setIsPaused(isPaused: Boolean) {
|
|
262
|
+
Log.d(TAG, "setIsPaused called with value: $isPaused")
|
|
263
|
+
this.isPaused = isPaused
|
|
264
|
+
if (isPaused) {
|
|
265
|
+
player?.pause()
|
|
266
|
+
} else {
|
|
267
|
+
player?.play()
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
211
271
|
fun play() {
|
|
212
|
-
Log.d(TAG, "Play called")
|
|
213
|
-
player?.
|
|
214
|
-
isPlaying = true
|
|
272
|
+
Log.d(TAG, "Play method called")
|
|
273
|
+
player?.playWhenReady = true
|
|
215
274
|
}
|
|
216
275
|
|
|
217
276
|
fun pause() {
|
|
218
|
-
Log.d(TAG, "Pause called")
|
|
219
|
-
player?.
|
|
220
|
-
isPlaying = false
|
|
277
|
+
Log.d(TAG, "Pause method called")
|
|
278
|
+
player?.playWhenReady = false
|
|
221
279
|
}
|
|
222
280
|
|
|
223
|
-
fun seekTo(
|
|
224
|
-
Log.d(TAG, "
|
|
225
|
-
player?.
|
|
281
|
+
fun seekTo(seconds: Float) {
|
|
282
|
+
Log.d(TAG, "SeekTo method called with seconds: $seconds")
|
|
283
|
+
player?.let {
|
|
284
|
+
val milliseconds = (seconds * 1000).toLong()
|
|
285
|
+
Log.d(TAG, "Seeking to $milliseconds ms")
|
|
286
|
+
it.seekTo(milliseconds)
|
|
287
|
+
|
|
288
|
+
// Force a progress update after seeking
|
|
289
|
+
progressRunnable.run()
|
|
290
|
+
} ?: Log.e(TAG, "Cannot seek: player is null")
|
|
226
291
|
}
|
|
227
292
|
|
|
228
293
|
fun getCurrentTime(): Float {
|
|
229
|
-
|
|
294
|
+
Log.d(TAG, "GetCurrentTime method called")
|
|
295
|
+
return player?.let {
|
|
296
|
+
val currentTime = it.currentPosition.toFloat() / 1000f
|
|
297
|
+
Log.d(TAG, "Current time: $currentTime seconds")
|
|
298
|
+
currentTime
|
|
299
|
+
} ?: run {
|
|
300
|
+
Log.e(TAG, "Cannot get current time: player is null")
|
|
301
|
+
0f
|
|
302
|
+
}
|
|
230
303
|
}
|
|
231
304
|
|
|
232
305
|
fun getDuration(): Float {
|
|
233
|
-
|
|
306
|
+
Log.d(TAG, "GetDuration method called")
|
|
307
|
+
return player?.let {
|
|
308
|
+
val duration = it.duration.toFloat() / 1000f
|
|
309
|
+
Log.d(TAG, "Duration: $duration seconds (raw: ${it.duration})")
|
|
310
|
+
if (it.duration > 0) duration else 0f
|
|
311
|
+
} ?: run {
|
|
312
|
+
Log.e(TAG, "Cannot get duration: player is null")
|
|
313
|
+
0f
|
|
314
|
+
}
|
|
234
315
|
}
|
|
235
316
|
|
|
317
|
+
// Add a getter for the ExoPlayer instance
|
|
318
|
+
val exoPlayer: ExoPlayer?
|
|
319
|
+
get() = this.player
|
|
320
|
+
|
|
236
321
|
private fun sendEvent(eventName: String, params: WritableMap) {
|
|
237
|
-
|
|
322
|
+
try {
|
|
323
|
+
// Log the event for debugging
|
|
324
|
+
Log.d(TAG, "Sending direct event: $eventName with params: $params")
|
|
325
|
+
|
|
326
|
+
// Use the ReactContext to dispatch the event directly to the view
|
|
327
|
+
val reactContext = context as ReactContext
|
|
328
|
+
reactContext.getJSModule(RCTEventEmitter::class.java)
|
|
329
|
+
.receiveEvent(id, eventName, params)
|
|
330
|
+
} catch (e: Exception) {
|
|
331
|
+
Log.e(TAG, "Error sending event $eventName: ${e.message}", e)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Add a method to explicitly start progress updates
|
|
336
|
+
private fun startProgressUpdates() {
|
|
337
|
+
Log.d(TAG, "Starting progress updates")
|
|
338
|
+
// Remove any existing callbacks to avoid duplicates
|
|
339
|
+
progressHandler.removeCallbacks(progressRunnable)
|
|
340
|
+
// Post the runnable to start updates
|
|
341
|
+
progressHandler.post(progressRunnable)
|
|
238
342
|
}
|
|
239
343
|
|
|
240
344
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
@@ -250,16 +354,13 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
250
354
|
super.onAttachedToWindow()
|
|
251
355
|
Log.d(TAG, "UnifiedPlayerView onAttachedToWindow")
|
|
252
356
|
playerView.setPlayer(player)
|
|
253
|
-
//
|
|
254
|
-
if (autoplay) {
|
|
255
|
-
player?.play()
|
|
256
|
-
isPlaying = true
|
|
257
|
-
Log.d(TAG, "Autoplay: player?.play() called in onAttachedToWindow")
|
|
258
|
-
}
|
|
357
|
+
startProgressUpdates() // Use the new method to start progress updates
|
|
259
358
|
}
|
|
260
359
|
|
|
261
360
|
override fun onDetachedFromWindow() {
|
|
262
361
|
super.onDetachedFromWindow()
|
|
362
|
+
Log.d(TAG, "UnifiedPlayerView onDetachedFromWindow")
|
|
363
|
+
progressHandler.removeCallbacks(progressRunnable) // Stop progress updates
|
|
263
364
|
player?.release()
|
|
264
365
|
}
|
|
265
366
|
}
|
|
@@ -3,11 +3,18 @@ package com.unifiedplayer
|
|
|
3
3
|
import com.facebook.react.uimanager.SimpleViewManager
|
|
4
4
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
5
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
6
|
+
import com.facebook.react.common.MapBuilder
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import com.facebook.react.bridge.ReadableArray
|
|
9
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
6
10
|
|
|
7
11
|
class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
|
|
12
|
+
private val TAG = "UnifiedPlayerViewManager"
|
|
13
|
+
|
|
8
14
|
override fun getName() = "UnifiedPlayerView"
|
|
9
15
|
|
|
10
16
|
override fun createViewInstance(reactContext: ThemedReactContext): UnifiedPlayerView {
|
|
17
|
+
Log.d(TAG, "Creating UnifiedPlayerView instance")
|
|
11
18
|
return UnifiedPlayerView(reactContext)
|
|
12
19
|
}
|
|
13
20
|
|
|
@@ -16,11 +23,6 @@ class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
|
|
|
16
23
|
view.setVideoUrl(url)
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
@ReactProp(name = "authToken")
|
|
20
|
-
fun setAuthToken(view: UnifiedPlayerView, token: String?) {
|
|
21
|
-
view.setAuthToken(token)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
26
|
@ReactProp(name = "autoplay")
|
|
25
27
|
fun setAutoplay(view: UnifiedPlayerView, autoplay: Boolean) {
|
|
26
28
|
view.setAutoplay(autoplay)
|
|
@@ -30,4 +32,25 @@ class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
|
|
|
30
32
|
fun setLoop(view: UnifiedPlayerView, loop: Boolean) {
|
|
31
33
|
view.setLoop(loop)
|
|
32
34
|
}
|
|
35
|
+
|
|
36
|
+
@ReactProp(name = "isPaused")
|
|
37
|
+
fun setIsPaused(view: UnifiedPlayerView, isPaused: Boolean) {
|
|
38
|
+
view.setIsPaused(isPaused)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Register direct events
|
|
42
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
|
|
43
|
+
Log.d(TAG, "Registering direct events")
|
|
44
|
+
|
|
45
|
+
// Create a map of event names to their registration names
|
|
46
|
+
return MapBuilder.builder<String, Any>()
|
|
47
|
+
.put("topReadyToPlay", MapBuilder.of("registrationName", "onReadyToPlay"))
|
|
48
|
+
.put("topError", MapBuilder.of("registrationName", "onError"))
|
|
49
|
+
.put("topProgress", MapBuilder.of("registrationName", "onProgress"))
|
|
50
|
+
.put("topPlaybackComplete", MapBuilder.of("registrationName", "onPlaybackComplete"))
|
|
51
|
+
.put("topPlaybackResumed", MapBuilder.of("registrationName", "onPlaybackResumed"))
|
|
52
|
+
.put("topPlaybackPaused", MapBuilder.of("registrationName", "onPlaybackPaused"))
|
|
53
|
+
.put("topLoadStart", MapBuilder.of("registrationName", "onLoadStart"))
|
|
54
|
+
.build()
|
|
55
|
+
}
|
|
33
56
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
@property (nonatomic, copy) NSString *authToken;
|
|
15
15
|
@property (nonatomic, assign) BOOL autoplay;
|
|
16
16
|
@property (nonatomic, assign) BOOL loop;
|
|
17
|
+
@property (nonatomic, assign) BOOL isPaused; // Add isPaused property
|
|
17
18
|
@property (nonatomic, strong) NSArray *mediaOptions;
|
|
18
19
|
@property (nonatomic, weak) RCTBridge *bridge;
|
|
19
20
|
@property (nonatomic, assign) VLCMediaPlayerState previousState;
|
|
@@ -446,6 +447,21 @@ static UnifiedPlayerModule *eventEmitter = nil;
|
|
|
446
447
|
_loop = loop;
|
|
447
448
|
}
|
|
448
449
|
|
|
450
|
+
- (void)setIsPaused:(BOOL)isPaused {
|
|
451
|
+
if (_isPaused != isPaused) {
|
|
452
|
+
_isPaused = isPaused;
|
|
453
|
+
if (_player) {
|
|
454
|
+
if (_isPaused && _player.isPlaying) {
|
|
455
|
+
[_player pause];
|
|
456
|
+
RCTLogInfo(@"[UnifiedPlayerViewManager] Paused via isPaused prop");
|
|
457
|
+
} else if (!_isPaused && !_player.isPlaying) {
|
|
458
|
+
[_player play];
|
|
459
|
+
RCTLogInfo(@"[UnifiedPlayerViewManager] Played via isPaused prop");
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
449
465
|
- (void)setAuthToken:(NSString *)authToken {
|
|
450
466
|
if (_authToken != authToken) {
|
|
451
467
|
_authToken = [authToken copy];
|
|
@@ -667,4 +683,10 @@ RCT_CUSTOM_VIEW_PROPERTY(mediaOptions, NSArray, UnifiedPlayerUIView)
|
|
|
667
683
|
view.mediaOptions = [RCTConvert NSArray:json];
|
|
668
684
|
}
|
|
669
685
|
|
|
686
|
+
// isPaused property
|
|
687
|
+
RCT_CUSTOM_VIEW_PROPERTY(isPaused, BOOL, UnifiedPlayerUIView)
|
|
688
|
+
{
|
|
689
|
+
view.isPaused = [RCTConvert BOOL:json];
|
|
690
|
+
}
|
|
691
|
+
|
|
670
692
|
@end
|
package/lib/module/index.js
CHANGED
|
@@ -1,97 +1,121 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { forwardRef } from 'react'; // Import from 'react'
|
|
4
|
+
import { requireNativeComponent, UIManager, NativeModules, Platform } from 'react-native';
|
|
5
|
+
|
|
6
|
+
// Check if the native module is available
|
|
4
7
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
8
|
const LINKING_ERROR = `The package 'react-native-unified-player' doesn't seem to be linked. Make sure: \n\n` + Platform.select({
|
|
6
9
|
ios: "- You have run 'pod install'\n",
|
|
7
10
|
default: ''
|
|
8
11
|
}) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// Verify the native module exists
|
|
14
|
+
if (!UIManager.getViewManagerConfig('UnifiedPlayerView') && !NativeModules.UnifiedPlayer) {
|
|
15
|
+
throw new Error(LINKING_ERROR);
|
|
16
|
+
}
|
|
13
17
|
|
|
14
|
-
//
|
|
15
|
-
const ComponentName = 'UnifiedPlayerView';
|
|
18
|
+
// Define the props for the UnifiedPlayerView component
|
|
16
19
|
|
|
17
|
-
// Native component
|
|
18
|
-
const NativeUnifiedPlayerView =
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
// Native component registration
|
|
21
|
+
const NativeUnifiedPlayerView = requireNativeComponent('UnifiedPlayerView');
|
|
22
|
+
|
|
23
|
+
// Newline added here
|
|
21
24
|
|
|
22
|
-
// Native module for
|
|
23
|
-
const UnifiedPlayerModule = NativeModules.
|
|
25
|
+
// Native module for player control methods
|
|
26
|
+
const UnifiedPlayerModule = NativeModules.UnifiedPlayer;
|
|
27
|
+
|
|
28
|
+
// Export event types for reference
|
|
29
|
+
export const UnifiedPlayerEventTypes = {
|
|
30
|
+
READY: 'onReadyToPlay',
|
|
31
|
+
ERROR: 'onError',
|
|
32
|
+
PROGRESS: 'onProgress',
|
|
33
|
+
COMPLETE: 'onPlaybackComplete',
|
|
34
|
+
STALLED: 'onPlaybackStalled',
|
|
35
|
+
RESUMED: 'onPlaybackResumed'
|
|
36
|
+
};
|
|
24
37
|
|
|
25
38
|
/**
|
|
26
39
|
* UnifiedPlayerView component for video playback
|
|
27
40
|
*/
|
|
28
|
-
export const UnifiedPlayerView = props => {
|
|
41
|
+
export const UnifiedPlayerView = /*#__PURE__*/forwardRef((props, ref) => {
|
|
29
42
|
return /*#__PURE__*/_jsx(NativeUnifiedPlayerView, {
|
|
30
|
-
...props
|
|
43
|
+
...props,
|
|
44
|
+
ref: ref
|
|
31
45
|
});
|
|
32
|
-
};
|
|
46
|
+
});
|
|
33
47
|
|
|
34
48
|
/**
|
|
35
49
|
* API methods for controlling playback
|
|
36
50
|
*/
|
|
37
51
|
export const UnifiedPlayer = {
|
|
38
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Start playback
|
|
54
|
+
* @param viewTag - The tag of the player view
|
|
55
|
+
*/
|
|
39
56
|
play: viewTag => {
|
|
40
|
-
|
|
57
|
+
try {
|
|
58
|
+
console.log('UnifiedPlayer.play called with viewTag:', viewTag);
|
|
41
59
|
UnifiedPlayerModule.play(viewTag);
|
|
60
|
+
console.log('Native play method called successfully');
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error calling play:', error);
|
|
42
63
|
}
|
|
43
64
|
},
|
|
44
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Pause playback
|
|
67
|
+
* @param viewTag - The tag of the player view
|
|
68
|
+
*/
|
|
45
69
|
pause: viewTag => {
|
|
46
|
-
|
|
70
|
+
try {
|
|
71
|
+
console.log('UnifiedPlayer.pause called with viewTag:', viewTag);
|
|
47
72
|
UnifiedPlayerModule.pause(viewTag);
|
|
73
|
+
console.log('Native pause method called successfully');
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Error calling pause:', error);
|
|
48
76
|
}
|
|
49
77
|
},
|
|
50
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Seek to a specific time
|
|
80
|
+
* @param viewTag - The tag of the player view
|
|
81
|
+
* @param time - Time in seconds to seek to
|
|
82
|
+
*/
|
|
51
83
|
seekTo: (viewTag, time) => {
|
|
52
|
-
|
|
84
|
+
try {
|
|
85
|
+
console.log('UnifiedPlayer.seekTo called with viewTag:', viewTag, 'time:', time);
|
|
53
86
|
UnifiedPlayerModule.seekTo(viewTag, time);
|
|
87
|
+
console.log('Native seekTo method called successfully');
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Error calling seekTo:', error);
|
|
54
90
|
}
|
|
55
91
|
},
|
|
56
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Get current playback time
|
|
94
|
+
* @param viewTag - The tag of the player view
|
|
95
|
+
* @returns Promise resolving to current time in seconds
|
|
96
|
+
*/
|
|
57
97
|
getCurrentTime: viewTag => {
|
|
58
|
-
|
|
98
|
+
try {
|
|
99
|
+
console.log('UnifiedPlayer.getCurrentTime called with viewTag:', viewTag);
|
|
59
100
|
return UnifiedPlayerModule.getCurrentTime(viewTag);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Error calling getCurrentTime:', error);
|
|
103
|
+
return Promise.reject(error);
|
|
60
104
|
}
|
|
61
|
-
return Promise.resolve(0);
|
|
62
105
|
},
|
|
63
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Get video duration
|
|
108
|
+
* @param viewTag - The tag of the player view
|
|
109
|
+
* @returns Promise resolving to duration in seconds
|
|
110
|
+
*/
|
|
64
111
|
getDuration: viewTag => {
|
|
65
|
-
|
|
112
|
+
try {
|
|
113
|
+
console.log('UnifiedPlayer.getDuration called with viewTag:', viewTag);
|
|
66
114
|
return UnifiedPlayerModule.getDuration(viewTag);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('Error calling getDuration:', error);
|
|
117
|
+
return Promise.reject(error);
|
|
67
118
|
}
|
|
68
|
-
return Promise.resolve(0);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Events emitter for native events
|
|
73
|
-
let eventEmitter = null;
|
|
74
|
-
if (UnifiedPlayerModule) {
|
|
75
|
-
eventEmitter = new NativeEventEmitter(UnifiedPlayerModule);
|
|
76
|
-
}
|
|
77
|
-
export const UnifiedPlayerEvents = {
|
|
78
|
-
addListener: (eventType, listener) => {
|
|
79
|
-
if (eventEmitter) {
|
|
80
|
-
return eventEmitter.addListener(eventType, listener);
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
remove: () => {}
|
|
84
|
-
};
|
|
85
119
|
}
|
|
86
120
|
};
|
|
87
|
-
|
|
88
|
-
// Event names
|
|
89
|
-
export const UnifiedPlayerEventTypes = {
|
|
90
|
-
READY: 'onReadyToPlay',
|
|
91
|
-
ERROR: 'onError',
|
|
92
|
-
PROGRESS: 'onProgress',
|
|
93
|
-
COMPLETE: 'onPlaybackComplete',
|
|
94
|
-
STALLED: 'onPlaybackStalled',
|
|
95
|
-
RESUMED: 'onPlaybackResumed'
|
|
96
|
-
};
|
|
97
121
|
//# sourceMappingURL=index.js.map
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
1
|
+
{"version":3,"names":["forwardRef","requireNativeComponent","UIManager","NativeModules","Platform","jsx","_jsx","LINKING_ERROR","select","ios","default","getViewManagerConfig","UnifiedPlayer","Error","NativeUnifiedPlayerView","UnifiedPlayerModule","UnifiedPlayerEventTypes","READY","ERROR","PROGRESS","COMPLETE","STALLED","RESUMED","UnifiedPlayerView","props","ref","play","viewTag","console","log","error","pause","seekTo","time","getCurrentTime","Promise","reject","getDuration"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { type ViewStyle } from 'react-native';
|
|
2
|
-
/**
|
|
3
|
-
* Props for the UnifiedPlayerView component
|
|
4
|
-
*/
|
|
5
2
|
export type UnifiedPlayerProps = {
|
|
6
3
|
videoUrl: string;
|
|
7
4
|
style: ViewStyle;
|
|
8
5
|
autoplay?: boolean;
|
|
9
6
|
loop?: boolean;
|
|
10
|
-
|
|
7
|
+
isPaused?: boolean;
|
|
11
8
|
onReadyToPlay?: () => void;
|
|
12
9
|
onError?: (error: any) => void;
|
|
13
10
|
onPlaybackComplete?: () => void;
|
|
@@ -16,31 +13,49 @@ export type UnifiedPlayerProps = {
|
|
|
16
13
|
duration: number;
|
|
17
14
|
}) => void;
|
|
18
15
|
};
|
|
16
|
+
export declare const UnifiedPlayerEventTypes: {
|
|
17
|
+
READY: string;
|
|
18
|
+
ERROR: string;
|
|
19
|
+
PROGRESS: string;
|
|
20
|
+
COMPLETE: string;
|
|
21
|
+
STALLED: string;
|
|
22
|
+
RESUMED: string;
|
|
23
|
+
};
|
|
19
24
|
/**
|
|
20
25
|
* UnifiedPlayerView component for video playback
|
|
21
26
|
*/
|
|
22
|
-
export declare const UnifiedPlayerView: (
|
|
27
|
+
export declare const UnifiedPlayerView: import("react").ForwardRefExoticComponent<UnifiedPlayerProps & import("react").RefAttributes<import("react").Component<UnifiedPlayerProps, {}, any> & import("react-native").NativeMethods>>;
|
|
23
28
|
/**
|
|
24
29
|
* API methods for controlling playback
|
|
25
30
|
*/
|
|
26
31
|
export declare const UnifiedPlayer: {
|
|
32
|
+
/**
|
|
33
|
+
* Start playback
|
|
34
|
+
* @param viewTag - The tag of the player view
|
|
35
|
+
*/
|
|
27
36
|
play: (viewTag: number) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Pause playback
|
|
39
|
+
* @param viewTag - The tag of the player view
|
|
40
|
+
*/
|
|
28
41
|
pause: (viewTag: number) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Seek to a specific time
|
|
44
|
+
* @param viewTag - The tag of the player view
|
|
45
|
+
* @param time - Time in seconds to seek to
|
|
46
|
+
*/
|
|
29
47
|
seekTo: (viewTag: number, time: number) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Get current playback time
|
|
50
|
+
* @param viewTag - The tag of the player view
|
|
51
|
+
* @returns Promise resolving to current time in seconds
|
|
52
|
+
*/
|
|
30
53
|
getCurrentTime: (viewTag: number) => Promise<number>;
|
|
54
|
+
/**
|
|
55
|
+
* Get video duration
|
|
56
|
+
* @param viewTag - The tag of the player view
|
|
57
|
+
* @returns Promise resolving to duration in seconds
|
|
58
|
+
*/
|
|
31
59
|
getDuration: (viewTag: number) => Promise<number>;
|
|
32
60
|
};
|
|
33
|
-
export declare const UnifiedPlayerEvents: {
|
|
34
|
-
addListener: (eventType: string, listener: (...args: any[]) => any) => import("react-native").EmitterSubscription | {
|
|
35
|
-
remove: () => void;
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
export declare const UnifiedPlayerEventTypes: {
|
|
39
|
-
READY: string;
|
|
40
|
-
ERROR: string;
|
|
41
|
-
PROGRESS: string;
|
|
42
|
-
COMPLETE: string;
|
|
43
|
-
STALLED: string;
|
|
44
|
-
RESUMED: string;
|
|
45
|
-
};
|
|
46
61
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAkBtB,MAAM,MAAM,kBAAkB,GAAG;IAE/B,QAAQ,EAAE,MAAM,CAAC;IAGjB,KAAK,EAAE,SAAS,CAAC;IAGjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAGnB,IAAI,CAAC,EAAE,OAAO,CAAC;IAGf,QAAQ,CAAC,EAAE,OAAO,CAAC;IAGnB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAG3B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAG/B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAGhC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACxE,CAAC;AAYF,eAAO,MAAM,uBAAuB;;;;;;;CAOnC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,8LAK5B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;IACxB;;;OAGG;oBACa,MAAM,KAAG,IAAI;IAU7B;;;OAGG;qBACc,MAAM,KAAG,IAAI;IAU9B;;;;OAIG;sBACe,MAAM,QAAQ,MAAM,KAAG,IAAI;IAe7C;;;;OAIG;8BACuB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;IAUlD;;;;OAIG;2BACoB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;CAShD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-unified-player",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Unified Player",
|
|
5
5
|
"source": "./src/index.tsx",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -86,6 +86,9 @@
|
|
|
86
86
|
"react": "*",
|
|
87
87
|
"react-native": "*"
|
|
88
88
|
},
|
|
89
|
+
"workspaces": [
|
|
90
|
+
"example"
|
|
91
|
+
],
|
|
89
92
|
"packageManager": "yarn@3.6.1",
|
|
90
93
|
"jest": {
|
|
91
94
|
"preset": "react-native",
|
package/src/index.tsx
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
|
+
import { type ElementRef, forwardRef } from 'react'; // Import from 'react'
|
|
1
2
|
import {
|
|
2
3
|
requireNativeComponent,
|
|
3
4
|
UIManager,
|
|
5
|
+
NativeModules,
|
|
4
6
|
Platform,
|
|
5
7
|
type ViewStyle,
|
|
6
|
-
NativeModules,
|
|
7
|
-
NativeEventEmitter,
|
|
8
8
|
} from 'react-native';
|
|
9
9
|
|
|
10
|
+
// Check if the native module is available
|
|
10
11
|
const LINKING_ERROR =
|
|
11
12
|
`The package 'react-native-unified-player' doesn't seem to be linked. Make sure: \n\n` +
|
|
12
13
|
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
13
14
|
'- You rebuilt the app after installing the package\n' +
|
|
14
15
|
'- You are not using Expo Go\n';
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// Verify the native module exists
|
|
18
|
+
if (
|
|
19
|
+
!UIManager.getViewManagerConfig('UnifiedPlayerView') &&
|
|
20
|
+
!NativeModules.UnifiedPlayer
|
|
21
|
+
) {
|
|
22
|
+
throw new Error(LINKING_ERROR);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Define the props for the UnifiedPlayerView component
|
|
19
26
|
export type UnifiedPlayerProps = {
|
|
20
27
|
// Video source URL
|
|
21
28
|
videoUrl: string;
|
|
@@ -29,8 +36,8 @@ export type UnifiedPlayerProps = {
|
|
|
29
36
|
// Should video loop when finished
|
|
30
37
|
loop?: boolean;
|
|
31
38
|
|
|
32
|
-
//
|
|
33
|
-
|
|
39
|
+
// Is the player currently paused
|
|
40
|
+
isPaused?: boolean;
|
|
34
41
|
|
|
35
42
|
// Callback when video is ready to play
|
|
36
43
|
onReadyToPlay?: () => void;
|
|
@@ -45,91 +52,114 @@ export type UnifiedPlayerProps = {
|
|
|
45
52
|
onProgress?: (data: { currentTime: number; duration: number }) => void;
|
|
46
53
|
};
|
|
47
54
|
|
|
48
|
-
//
|
|
49
|
-
const ComponentName = 'UnifiedPlayerView';
|
|
50
|
-
|
|
51
|
-
// Native component import
|
|
55
|
+
// Native component registration
|
|
52
56
|
const NativeUnifiedPlayerView =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
requireNativeComponent<UnifiedPlayerProps>('UnifiedPlayerView');
|
|
58
|
+
|
|
59
|
+
// Newline added here
|
|
60
|
+
|
|
61
|
+
// Native module for player control methods
|
|
62
|
+
const UnifiedPlayerModule = NativeModules.UnifiedPlayer;
|
|
58
63
|
|
|
59
|
-
//
|
|
60
|
-
const
|
|
64
|
+
// Export event types for reference
|
|
65
|
+
export const UnifiedPlayerEventTypes = {
|
|
66
|
+
READY: 'onReadyToPlay',
|
|
67
|
+
ERROR: 'onError',
|
|
68
|
+
PROGRESS: 'onProgress',
|
|
69
|
+
COMPLETE: 'onPlaybackComplete',
|
|
70
|
+
STALLED: 'onPlaybackStalled',
|
|
71
|
+
RESUMED: 'onPlaybackResumed',
|
|
72
|
+
};
|
|
61
73
|
|
|
62
74
|
/**
|
|
63
75
|
* UnifiedPlayerView component for video playback
|
|
64
76
|
*/
|
|
65
|
-
export const UnifiedPlayerView =
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
export const UnifiedPlayerView = forwardRef<
|
|
78
|
+
ElementRef<typeof NativeUnifiedPlayerView>,
|
|
79
|
+
UnifiedPlayerProps
|
|
80
|
+
>((props, ref) => {
|
|
81
|
+
return <NativeUnifiedPlayerView {...props} ref={ref} />;
|
|
82
|
+
});
|
|
68
83
|
|
|
69
84
|
/**
|
|
70
85
|
* API methods for controlling playback
|
|
71
86
|
*/
|
|
72
87
|
export const UnifiedPlayer = {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Start playback
|
|
90
|
+
* @param viewTag - The tag of the player view
|
|
91
|
+
*/
|
|
92
|
+
play: (viewTag: number): void => {
|
|
93
|
+
try {
|
|
94
|
+
console.log('UnifiedPlayer.play called with viewTag:', viewTag);
|
|
76
95
|
UnifiedPlayerModule.play(viewTag);
|
|
96
|
+
console.log('Native play method called successfully');
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Error calling play:', error);
|
|
77
99
|
}
|
|
78
100
|
},
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Pause playback
|
|
104
|
+
* @param viewTag - The tag of the player view
|
|
105
|
+
*/
|
|
106
|
+
pause: (viewTag: number): void => {
|
|
107
|
+
try {
|
|
108
|
+
console.log('UnifiedPlayer.pause called with viewTag:', viewTag);
|
|
83
109
|
UnifiedPlayerModule.pause(viewTag);
|
|
110
|
+
console.log('Native pause method called successfully');
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Error calling pause:', error);
|
|
84
113
|
}
|
|
85
114
|
},
|
|
86
115
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Seek to a specific time
|
|
118
|
+
* @param viewTag - The tag of the player view
|
|
119
|
+
* @param time - Time in seconds to seek to
|
|
120
|
+
*/
|
|
121
|
+
seekTo: (viewTag: number, time: number): void => {
|
|
122
|
+
try {
|
|
123
|
+
console.log(
|
|
124
|
+
'UnifiedPlayer.seekTo called with viewTag:',
|
|
125
|
+
viewTag,
|
|
126
|
+
'time:',
|
|
127
|
+
time
|
|
128
|
+
);
|
|
90
129
|
UnifiedPlayerModule.seekTo(viewTag, time);
|
|
130
|
+
console.log('Native seekTo method called successfully');
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('Error calling seekTo:', error);
|
|
91
133
|
}
|
|
92
134
|
},
|
|
93
135
|
|
|
94
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Get current playback time
|
|
138
|
+
* @param viewTag - The tag of the player view
|
|
139
|
+
* @returns Promise resolving to current time in seconds
|
|
140
|
+
*/
|
|
95
141
|
getCurrentTime: (viewTag: number): Promise<number> => {
|
|
96
|
-
|
|
142
|
+
try {
|
|
143
|
+
console.log('UnifiedPlayer.getCurrentTime called with viewTag:', viewTag);
|
|
97
144
|
return UnifiedPlayerModule.getCurrentTime(viewTag);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('Error calling getCurrentTime:', error);
|
|
147
|
+
return Promise.reject(error);
|
|
98
148
|
}
|
|
99
|
-
return Promise.resolve(0);
|
|
100
149
|
},
|
|
101
150
|
|
|
102
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Get video duration
|
|
153
|
+
* @param viewTag - The tag of the player view
|
|
154
|
+
* @returns Promise resolving to duration in seconds
|
|
155
|
+
*/
|
|
103
156
|
getDuration: (viewTag: number): Promise<number> => {
|
|
104
|
-
|
|
157
|
+
try {
|
|
158
|
+
console.log('UnifiedPlayer.getDuration called with viewTag:', viewTag);
|
|
105
159
|
return UnifiedPlayerModule.getDuration(viewTag);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('Error calling getDuration:', error);
|
|
162
|
+
return Promise.reject(error);
|
|
106
163
|
}
|
|
107
|
-
return Promise.resolve(0);
|
|
108
164
|
},
|
|
109
165
|
};
|
|
110
|
-
|
|
111
|
-
// Events emitter for native events
|
|
112
|
-
let eventEmitter: NativeEventEmitter | null = null;
|
|
113
|
-
|
|
114
|
-
if (UnifiedPlayerModule) {
|
|
115
|
-
eventEmitter = new NativeEventEmitter(UnifiedPlayerModule);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export const UnifiedPlayerEvents = {
|
|
119
|
-
addListener: (eventType: string, listener: (...args: any[]) => any) => {
|
|
120
|
-
if (eventEmitter) {
|
|
121
|
-
return eventEmitter.addListener(eventType, listener);
|
|
122
|
-
}
|
|
123
|
-
return { remove: () => {} };
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
// Event names
|
|
128
|
-
export const UnifiedPlayerEventTypes = {
|
|
129
|
-
READY: 'onReadyToPlay',
|
|
130
|
-
ERROR: 'onError',
|
|
131
|
-
PROGRESS: 'onProgress',
|
|
132
|
-
COMPLETE: 'onPlaybackComplete',
|
|
133
|
-
STALLED: 'onPlaybackStalled',
|
|
134
|
-
RESUMED: 'onPlaybackResumed',
|
|
135
|
-
};
|