@@ -17,7 +17,7 @@ import com.google.android.exoplayer2.ui.PlayerNotificationManager
1717
1818@ReactModule(name = AudioModule .NAME )
1919class AudioModule (private val reactContext : ReactApplicationContext ) :
20- NativeAudioModuleSpec (reactContext) {
20+ NativeAudioModuleSpec (reactContext) {
2121
2222 private var exoPlayer: ExoPlayer ? = null
2323 private var progressHandler: Handler ? = Handler (Looper .getMainLooper())
@@ -31,65 +31,84 @@ class AudioModule(private val reactContext: ReactApplicationContext) :
3131
3232 init {
3333 reactContext.runOnUiQueueThread {
34- initializePlayer()
3534 initializeProgressRunnable()
3635 createNotificationChannel()
3736 }
3837 }
3938
4039 private fun createNotificationChannel () {
41- val channel = NotificationChannel (
42- CHANNEL_ID ,
43- " Audio Player" ,
44- NotificationManager .IMPORTANCE_LOW
45- )
46- val notificationManager = reactContext.getSystemService(Context .NOTIFICATION_SERVICE ) as NotificationManager
40+ val channel =
41+ NotificationChannel (CHANNEL_ID , " Audio Player" , NotificationManager .IMPORTANCE_LOW )
42+ val notificationManager =
43+ reactContext.getSystemService(Context .NOTIFICATION_SERVICE ) as NotificationManager
4744 notificationManager.createNotificationChannel(channel)
4845 }
4946
50- private fun initializePlayer () {
47+ private fun ensurePlayerReady () {
5148 if (exoPlayer == null ) {
52- exoPlayer = ExoPlayer .Builder (reactContext).build().apply {
53- addListener(object : Player .Listener {
54- override fun onPlaybackStateChanged (state : Int ) {
55- when (state) {
56- Player .STATE_BUFFERING -> emitAudioStateChange(" BUFFERING" )
57- Player .STATE_READY -> emitAudioStateChange(" LOADED" )
58- Player .STATE_ENDED -> emitAudioStateChange(" COMPLETED" )
59- }
60- }
49+ exoPlayer =
50+ ExoPlayer .Builder (reactContext).build().apply {
51+ addListener(
52+ object : Player .Listener {
53+ override fun onPlaybackStateChanged (state : Int ) {
54+ when (state) {
55+ Player .STATE_BUFFERING ->
56+ emitAudioStateChange(" BUFFERING" )
57+ Player .STATE_READY -> emitAudioStateChange(" LOADED" )
58+ Player .STATE_ENDED -> emitAudioStateChange(" COMPLETED" )
59+ }
60+ }
6161
62- override fun onPlayerError (error : PlaybackException ) {
63- Log .e(TAG , " ExoPlayer Error: ${error.message} " )
64- emitAudioStateChange(" ERROR" , error.message)
62+ override fun onPlayerError (error : PlaybackException ) {
63+ Log .e(TAG , " ExoPlayer Error: ${error.message} " )
64+ emitAudioStateChange(" ERROR" , error.message)
65+ }
66+ }
67+ )
6568 }
66- })
67- }
6869 }
6970 }
7071
71- @ReactMethod
72- override fun addListener (eventName : String ) {
73- // Required for RN event emitter
74- }
72+ @ReactMethod override fun addListener (eventName : String ) {}
7573
76- @ReactMethod
77- override fun removeListeners (count : Double ) {
78- // Required for RN event emitter
79- }
74+ @ReactMethod override fun removeListeners (count : Double ) {}
8075
8176 @ReactMethod
82- override fun setMediaPlayerInfo (title : String? , artist : String? , album : String? , artwork : String? , promise : Promise ) {
83- try {
84- currentTitle = title ? : " Unknown"
85- currentArtist = artist ? : " Unknown"
86- currentAlbum = album ? : " Unknown"
87- currentArtwork = artwork ? : " "
88-
89- updateNotification()
90- promise.resolve(null )
91- } catch (e: Exception ) {
92- promise.reject(" ERROR" , " Error setting media player info: ${e.message} " )
77+ override fun setMediaPlayerInfo (
78+ title : String? ,
79+ artist : String? ,
80+ album : String? ,
81+ artwork : String? ,
82+ promise : Promise
83+ ) {
84+ reactContext.runOnUiQueueThread {
85+ try {
86+ if (exoPlayer == null ) {
87+ ensurePlayerReady()
88+ }
89+
90+ currentTitle = title ? : " Unknown"
91+ currentArtist = artist ? : " Unknown"
92+ currentAlbum = album ? : " Unknown"
93+ currentArtwork = artwork ? : " "
94+
95+ // Update media session metadata
96+ try {
97+ updateNotification()
98+ } catch (e: Exception ) {
99+ Log .e(TAG , " Error updating notification: ${e.message} " )
100+ // Don't fail the whole operation if notification update fails
101+ }
102+
103+ promise.resolve(null )
104+ } catch (e: Exception ) {
105+ Log .e(TAG , " Error setting media info: ${e.message} " , e)
106+ promise.reject(
107+ " ERROR" ,
108+ " Error setting media player info: ${e.message ? : " Unknown error" } " ,
109+ e
110+ )
111+ }
93112 }
94113 }
95114
@@ -98,7 +117,7 @@ class AudioModule(private val reactContext: ReactApplicationContext) :
98117 reactContext.runOnUiQueueThread {
99118 try {
100119 if (url.isBlank()) throw IllegalArgumentException (" URL cannot be empty" )
101- initializePlayer ()
120+ ensurePlayerReady ()
102121 val mediaItem = MediaItem .fromUri(Uri .parse(url))
103122 exoPlayer?.setMediaItem(mediaItem)
104123 exoPlayer?.prepare()
@@ -112,6 +131,7 @@ class AudioModule(private val reactContext: ReactApplicationContext) :
112131 @ReactMethod
113132 override fun playAudio () {
114133 reactContext.runOnUiQueueThread {
134+ ensurePlayerReady()
115135 exoPlayer?.playWhenReady = true
116136 emitAudioStateChange(" PLAYING" )
117137 progressRunnable?.let { progressHandler?.post(it) }
@@ -153,33 +173,41 @@ class AudioModule(private val reactContext: ReactApplicationContext) :
153173 }
154174
155175 private fun emitAudioStateChange (state : String , message : String? = null) {
156- val params = Arguments .createMap().apply {
157- putString(" state" , state)
158- message?.let { putString(" message" , it) }
159- }
160- reactContext.getJSModule(DeviceEventManagerModule .RCTDeviceEventEmitter ::class .java)
161- ?.emit(" onAudioStateChange" , params)
176+ val params =
177+ Arguments .createMap().apply {
178+ putString(" state" , state)
179+ message?.let { putString(" message" , it) }
180+ }
181+ reactContext
182+ .getJSModule(DeviceEventManagerModule .RCTDeviceEventEmitter ::class .java)
183+ ?.emit(" onAudioStateChange" , params)
162184 }
163185
164186 private fun initializeProgressRunnable () {
165- progressRunnable = object : Runnable {
166- override fun run () {
167- if (exoPlayer?.isPlaying == true ) {
168- val currentPosition = exoPlayer?.currentPosition?.toDouble() ? : 0.0
169- val duration = exoPlayer?.duration?.toDouble() ? : 1.0
170-
171- val params = Arguments .createMap().apply {
172- putDouble(" progress" , currentPosition / duration)
173- putDouble(" currentTime" , currentPosition / 1000 )
174- putDouble(" totalDuration" , duration / 1000 )
175- }
187+ progressRunnable =
188+ object : Runnable {
189+ override fun run () {
190+ if (exoPlayer?.isPlaying == true ) {
191+ val currentPosition = exoPlayer?.currentPosition?.toDouble() ? : 0.0
192+ val duration = exoPlayer?.duration?.toDouble() ? : 1.0
193+
194+ val params =
195+ Arguments .createMap().apply {
196+ putDouble(" progress" , currentPosition / duration)
197+ putDouble(" currentTime" , currentPosition / 1000 )
198+ putDouble(" totalDuration" , duration / 1000 )
199+ }
176200
177- reactContext.getJSModule(DeviceEventManagerModule .RCTDeviceEventEmitter ::class .java)
178- ?.emit(" onAudioProgress" , params)
179- progressHandler?.postDelayed(this , 1000 )
201+ reactContext
202+ .getJSModule(
203+ DeviceEventManagerModule .RCTDeviceEventEmitter ::class
204+ .java
205+ )
206+ ?.emit(" onAudioProgress" , params)
207+ progressHandler?.postDelayed(this , 1000 )
208+ }
209+ }
180210 }
181- }
182- }
183211 }
184212
185213 override fun onCatalystInstanceDestroy () {
@@ -193,17 +221,32 @@ class AudioModule(private val reactContext: ReactApplicationContext) :
193221
194222 private fun updateNotification () {
195223 if (playerNotificationManager == null ) {
196- playerNotificationManager = PlayerNotificationManager .Builder (reactContext, NOTIFICATION_ID , CHANNEL_ID )
197- .setMediaDescriptionAdapter(object : PlayerNotificationManager .MediaDescriptionAdapter {
198- override fun getCurrentContentTitle (player : Player ) = currentTitle
199- override fun createCurrentContentIntent (player : Player ): PendingIntent ? = null
200- override fun getCurrentContentText (player : Player ) = currentArtist
201- override fun getCurrentSubText (player : Player ) = currentAlbum
202- override fun getCurrentLargeIcon (player : Player , callback : PlayerNotificationManager .BitmapCallback ): Bitmap ? = null
203- })
204- .build()
224+ playerNotificationManager =
225+ PlayerNotificationManager .Builder (reactContext, NOTIFICATION_ID , CHANNEL_ID )
226+ .setMediaDescriptionAdapter(
227+ object : PlayerNotificationManager .MediaDescriptionAdapter {
228+ override fun getCurrentContentTitle (player : Player ) =
229+ currentTitle
230+ override fun createCurrentContentIntent (
231+ player : Player
232+ ): PendingIntent ? = null
233+ override fun getCurrentContentText (player : Player ) =
234+ currentArtist
235+ override fun getCurrentSubText (player : Player ) =
236+ currentAlbum
237+ override fun getCurrentLargeIcon (
238+ player : Player ,
239+ callback : PlayerNotificationManager .BitmapCallback
240+ ): Bitmap ? = null
241+ }
242+ )
243+ .build()
244+ .apply {
245+ setPlayer(exoPlayer)
246+ }
247+ } else {
248+ playerNotificationManager?.setPlayer(exoPlayer)
205249 }
206- playerNotificationManager?.setPlayer(exoPlayer)
207250 }
208251
209252 companion object {
@@ -212,4 +255,4 @@ class AudioModule(private val reactContext: ReactApplicationContext) :
212255 private const val CHANNEL_ID = " audio_player_channel"
213256 private const val NOTIFICATION_ID = 1001
214257 }
215- }
258+ }
0 commit comments