react-native-unified-player 0.2.0
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/LICENSE +20 -0
- package/README.md +95 -0
- package/UnifiedPlayer.podspec +32 -0
- package/android/build.gradle +81 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +7 -0
- package/android/src/main/AndroidManifestNew.xml +4 -0
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerEventEmitter.kt +62 -0
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerModule.kt +93 -0
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerPackage.kt +20 -0
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerView.kt +265 -0
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerViewManager.kt +33 -0
- package/ios/UnifiedPlayerModule.h +5 -0
- package/ios/UnifiedPlayerViewManager.m +670 -0
- package/lib/module/index.js +97 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/index.d.ts +46 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +154 -0
- package/src/index.tsx +135 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
package com.unifiedplayer
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import android.view.Gravity
|
|
8
|
+
import android.widget.FrameLayout
|
|
9
|
+
import com.facebook.react.bridge.Arguments
|
|
10
|
+
import com.google.android.exoplayer2.ExoPlayer
|
|
11
|
+
import com.google.android.exoplayer2.MediaItem
|
|
12
|
+
import com.google.android.exoplayer2.Player
|
|
13
|
+
import com.google.android.exoplayer2.PlaybackException
|
|
14
|
+
import com.google.android.exoplayer2.Timeline
|
|
15
|
+
import com.google.android.exoplayer2.Tracks
|
|
16
|
+
import com.google.android.exoplayer2.ui.PlayerView
|
|
17
|
+
import com.google.android.exoplayer2.video.VideoSize
|
|
18
|
+
import com.facebook.react.bridge.WritableMap
|
|
19
|
+
|
|
20
|
+
class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
21
|
+
companion object {
|
|
22
|
+
private const val TAG = "UnifiedPlayerView"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private var videoUrl: String? = null
|
|
26
|
+
private var authToken: String? = null
|
|
27
|
+
private var autoplay: Boolean = true
|
|
28
|
+
private var loop: Boolean = false
|
|
29
|
+
private var playerView: PlayerView
|
|
30
|
+
private var player: ExoPlayer? = null
|
|
31
|
+
private var isPlaying = false
|
|
32
|
+
private var currentProgress = 0
|
|
33
|
+
|
|
34
|
+
init {
|
|
35
|
+
setBackgroundColor(Color.BLACK)
|
|
36
|
+
|
|
37
|
+
// Create ExoPlayer
|
|
38
|
+
player = ExoPlayer.Builder(context).build()
|
|
39
|
+
|
|
40
|
+
// Create PlayerView
|
|
41
|
+
playerView = PlayerView(context).apply {
|
|
42
|
+
layoutParams = LayoutParams(
|
|
43
|
+
LayoutParams.MATCH_PARENT,
|
|
44
|
+
LayoutParams.MATCH_PARENT
|
|
45
|
+
)
|
|
46
|
+
setPlayer(player)
|
|
47
|
+
// playerView.surfaceView?.surfaceType = android.view.SurfaceView.SURFACE_TYPE_SOFTWARE // Reverted: Let's remove surfaceType setting
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
addView(playerView)
|
|
51
|
+
// Add logging for playerView dimensions and post play call
|
|
52
|
+
playerView.post {
|
|
53
|
+
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
|
+
}
|
|
61
|
+
|
|
62
|
+
player?.addListener(object : Player.Listener {
|
|
63
|
+
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
64
|
+
Log.d(TAG, "onPlaybackStateChanged: $playbackState") // Added log
|
|
65
|
+
when (playbackState) {
|
|
66
|
+
Player.STATE_READY -> {
|
|
67
|
+
Log.d(TAG, "ExoPlayer STATE_READY")
|
|
68
|
+
sendEvent("onReadyToPlay", Arguments.createMap())
|
|
69
|
+
// Removed custom autoplay logic, relying on player?.playWhenReady
|
|
70
|
+
}
|
|
71
|
+
Player.STATE_ENDED -> {
|
|
72
|
+
Log.d(TAG, "ExoPlayer STATE_ENDED")
|
|
73
|
+
sendEvent("onPlaybackComplete", Arguments.createMap())
|
|
74
|
+
}
|
|
75
|
+
Player.STATE_BUFFERING -> {
|
|
76
|
+
Log.d(TAG, "ExoPlayer STATE_BUFFERING")
|
|
77
|
+
}
|
|
78
|
+
Player.STATE_IDLE -> {
|
|
79
|
+
Log.d(TAG, "ExoPlayer STATE_IDLE")
|
|
80
|
+
}
|
|
81
|
+
Player.STATE_BUFFERING -> {
|
|
82
|
+
Log.d(TAG, "ExoPlayer STATE_BUFFERING")
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
88
|
+
if (isPlaying) {
|
|
89
|
+
Log.d(TAG, "ExoPlayer isPlaying")
|
|
90
|
+
sendEvent("onPlay", Arguments.createMap())
|
|
91
|
+
} else {
|
|
92
|
+
Log.d(TAG, "ExoPlayer isPaused")
|
|
93
|
+
sendEvent("onPause", Arguments.createMap())
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
override fun onPlayerError(error: PlaybackException) {
|
|
98
|
+
Log.e(TAG, "ExoPlayer Error: ${error.message}, errorCode: ${error.errorCodeName}, errorCode বিস্তারিত: ${error.errorCode}")
|
|
99
|
+
val event = Arguments.createMap().apply {
|
|
100
|
+
putInt("code", error.errorCode)
|
|
101
|
+
putString("message", error.message)
|
|
102
|
+
}
|
|
103
|
+
sendEvent("onError", event)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
|
107
|
+
Log.d(TAG, "ExoPlayer onMediaItemTransition")
|
|
108
|
+
sendEvent("onLoadStart", Arguments.createMap())
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
override fun onPlaybackSuppressionReasonChanged(playbackSuppressionReason: Int) {
|
|
112
|
+
// Called when playback is suppressed temporarily due to content restrictions.
|
|
113
|
+
Log.d(TAG, "ExoPlayer onPlaybackSuppressionReasonChanged: $playbackSuppressionReason")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
|
|
117
|
+
// Called when playWhenReady state changes.
|
|
118
|
+
Log.d(TAG, "ExoPlayer onPlayWhenReadyChanged: playWhenReady=$playWhenReady, reason=$reason")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override fun onPositionDiscontinuity(reason: Int) {
|
|
122
|
+
// Called when there's a discontinuity in playback, such as seeking or ad insertion.
|
|
123
|
+
Log.d(TAG, "ExoPlayer onPositionDiscontinuity: reason=$reason")
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override fun onRepeatModeChanged(repeatMode: Int) {
|
|
127
|
+
// Called when the repeat mode changes.
|
|
128
|
+
Log.d(TAG, "ExoPlayer onRepeatModeChanged: repeatMode=$repeatMode")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
|
132
|
+
// Called when shuffle mode is enabled or disabled.
|
|
133
|
+
Log.d(TAG, "ExoPlayer onShuffleModeEnabledChanged: shuffleModeEnabled=$shuffleModeEnabled")
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
override fun onTracksChanged(tracks: Tracks) {
|
|
137
|
+
// Called when available tracks change.
|
|
138
|
+
Log.d(TAG, "ExoPlayer onTracksChanged: tracks=$tracks")
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
override fun onVolumeChanged(volume: Float) {
|
|
142
|
+
// Called when volume changes.
|
|
143
|
+
Log.d(TAG, "ExoPlayer onVolumeChanged: volume=$volume")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
|
147
|
+
// Called when video size changes.
|
|
148
|
+
Log.d(TAG, "ExoPlayer onVideoSizeChanged: videoSize=$videoSize")
|
|
149
|
+
Log.d(TAG, "Video size changed: width=${videoSize.width}, height=${videoSize.height}")
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
override fun onSurfaceSizeChanged(width: Int, height: Int) {
|
|
153
|
+
// Called when the size of the surface changes.
|
|
154
|
+
Log.d(TAG, "ExoPlayer onSurfaceSizeChanged: width=$width, height=$height")
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
override fun onRenderedFirstFrame() {
|
|
158
|
+
// Called when the first frame is rendered.
|
|
159
|
+
Log.d(TAG, "ExoPlayer onRenderedFirstFrame")
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
override fun onSkipSilenceEnabledChanged(skipSilenceEnabled: Boolean) {
|
|
163
|
+
// Called when skip silence is enabled or disabled.
|
|
164
|
+
Log.d(TAG, "ExoPlayer onSkipSilenceEnabledChanged: skipSilenceEnabled=$skipSilenceEnabled")
|
|
165
|
+
}
|
|
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
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fun setVideoUrl(url: String?) {
|
|
175
|
+
Log.d(TAG, "Setting video URL: $url")
|
|
176
|
+
if (url.isNullOrEmpty()) {
|
|
177
|
+
Log.d(TAG, "Video URL is null or empty, skipping load")
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
videoUrl = url
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
// Load video with ExoPlayer
|
|
185
|
+
Log.d(TAG, "Creating MediaItem from URI: $videoUrl")
|
|
186
|
+
val mediaItem = MediaItem.fromUri(videoUrl!!)
|
|
187
|
+
player?.setMediaItem(mediaItem)
|
|
188
|
+
Log.d(TAG, "Preparing ExoPlayer")
|
|
189
|
+
player?.prepare()
|
|
190
|
+
Log.d(TAG, "ExoPlayer prepared")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fun setAuthToken(token: String?) {
|
|
194
|
+
authToken = token
|
|
195
|
+
// If URL already set, reload with new token
|
|
196
|
+
if (!videoUrl.isNullOrEmpty()) {
|
|
197
|
+
setVideoUrl(videoUrl) // reload video
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fun setAutoplay(value: Boolean) {
|
|
202
|
+
autoplay = value
|
|
203
|
+
player?.playWhenReady = value // Set ExoPlayer's playWhenReady property
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fun setLoop(value: Boolean) {
|
|
207
|
+
loop = value
|
|
208
|
+
player?.repeatMode = if (loop) Player.REPEAT_MODE_ALL else Player.REPEAT_MODE_OFF
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
fun play() {
|
|
212
|
+
Log.d(TAG, "Play called")
|
|
213
|
+
player?.play()
|
|
214
|
+
isPlaying = true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fun pause() {
|
|
218
|
+
Log.d(TAG, "Pause called")
|
|
219
|
+
player?.pause()
|
|
220
|
+
isPlaying = false
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
fun seekTo(time: Float) {
|
|
224
|
+
Log.d(TAG, "Seek called: $time")
|
|
225
|
+
player?.seekTo((time * 1000).toLong()) // time in seconds, ExoPlayer in milliseconds
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fun getCurrentTime(): Float {
|
|
229
|
+
return (player?.currentPosition?.toFloat() ?: 0f) / 1000f // to seconds
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fun getDuration(): Float {
|
|
233
|
+
return (player?.duration?.toFloat() ?: 0f) / 1000f // to seconds
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private fun sendEvent(eventName: String, params: WritableMap) {
|
|
237
|
+
UnifiedPlayerEventEmitter.getInstance()?.sendEvent(eventName, params)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
241
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
242
|
+
val width = right - left
|
|
243
|
+
val height = bottom - top
|
|
244
|
+
Log.d(TAG, "UnifiedPlayerView onLayout: width=$width, height=$height")
|
|
245
|
+
// Ensure playerView also gets laid out
|
|
246
|
+
playerView.layout(0, 0, width, height)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
override fun onAttachedToWindow() {
|
|
250
|
+
super.onAttachedToWindow()
|
|
251
|
+
Log.d(TAG, "UnifiedPlayerView onAttachedToWindow")
|
|
252
|
+
playerView.setPlayer(player)
|
|
253
|
+
// Autoplay logic can remain in post block or be moved here
|
|
254
|
+
if (autoplay) {
|
|
255
|
+
player?.play()
|
|
256
|
+
isPlaying = true
|
|
257
|
+
Log.d(TAG, "Autoplay: player?.play() called in onAttachedToWindow")
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
override fun onDetachedFromWindow() {
|
|
262
|
+
super.onDetachedFromWindow()
|
|
263
|
+
player?.release()
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.unifiedplayer
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
4
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
6
|
+
|
|
7
|
+
class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
|
|
8
|
+
override fun getName() = "UnifiedPlayerView"
|
|
9
|
+
|
|
10
|
+
override fun createViewInstance(reactContext: ThemedReactContext): UnifiedPlayerView {
|
|
11
|
+
return UnifiedPlayerView(reactContext)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@ReactProp(name = "videoUrl")
|
|
15
|
+
fun setVideoUrl(view: UnifiedPlayerView, url: String?) {
|
|
16
|
+
view.setVideoUrl(url)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@ReactProp(name = "authToken")
|
|
20
|
+
fun setAuthToken(view: UnifiedPlayerView, token: String?) {
|
|
21
|
+
view.setAuthToken(token)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@ReactProp(name = "autoplay")
|
|
25
|
+
fun setAutoplay(view: UnifiedPlayerView, autoplay: Boolean) {
|
|
26
|
+
view.setAutoplay(autoplay)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@ReactProp(name = "loop")
|
|
30
|
+
fun setLoop(view: UnifiedPlayerView, loop: Boolean) {
|
|
31
|
+
view.setLoop(loop)
|
|
32
|
+
}
|
|
33
|
+
}
|