@@ -41,8 +41,11 @@ internal val androidVideoLogger = Logger.withTag("AndroidVideoPlayerSurface")
4141
4242@UnstableApi
4343@Stable
44- actual open class VideoPlayerState {
45- private val context: Context = ContextProvider .getContext()
44+ actual open class VideoPlayerState internal constructor(isInPreview : Boolean ) {
45+ actual constructor () : this (false )
46+
47+ private var appContext: Context ? = null
48+ internal var previewMode: Boolean = isInPreview
4649 internal var exoPlayer: ExoPlayer ? = null
4750 private var updateJob: Job ? = null
4851 private val coroutineScope = CoroutineScope (Dispatchers .Main + SupervisorJob ())
@@ -214,12 +217,29 @@ actual open class VideoPlayerState {
214217
215218
216219 init {
217- audioProcessor.setOnAudioLevelUpdateListener { left, right ->
218- _leftLevel = left
219- _rightLevel = right
220+ if (! previewMode) {
221+ audioProcessor.setOnAudioLevelUpdateListener { left, right ->
222+ _leftLevel = left
223+ _rightLevel = right
224+ }
225+ ensureInitialized()
226+ }
227+ }
228+
229+ private fun ensureInitialized (): Boolean {
230+ synchronized(playerInitializationLock) {
231+ if (isPlayerReleased) return false
232+ if (exoPlayer != null ) return true
233+
234+ val context = appContext ? : runCatching { ContextProvider .getContext().applicationContext }
235+ .getOrNull()
236+ ? : return false
237+
238+ appContext = context
239+ initializePlayer(context)
240+ registerScreenLockReceiver(context)
241+ return exoPlayer != null
220242 }
221- initializePlayer()
222- registerScreenLockReceiver()
223243 }
224244
225245 private fun shouldUseConservativeCodecHandling (): Boolean {
@@ -239,8 +259,8 @@ actual open class VideoPlayerState {
239259 manufacturer.equals(" mediatek" , ignoreCase = true )
240260 }
241261
242- private fun registerScreenLockReceiver () {
243- unregisterScreenLockReceiver()
262+ private fun registerScreenLockReceiver (context : Context ) {
263+ unregisterScreenLockReceiver(context )
244264
245265 screenLockReceiver = object : BroadcastReceiver () {
246266 override fun onReceive (context : Context ? , intent : Intent ? ) {
@@ -288,11 +308,16 @@ actual open class VideoPlayerState {
288308 addAction(Intent .ACTION_SCREEN_OFF )
289309 addAction(Intent .ACTION_SCREEN_ON )
290310 }
291- context.registerReceiver(screenLockReceiver, filter)
292- androidVideoLogger.d { " Screen lock receiver registered" }
311+ try {
312+ context.registerReceiver(screenLockReceiver, filter)
313+ androidVideoLogger.d { " Screen lock receiver registered" }
314+ } catch (e: Exception ) {
315+ androidVideoLogger.e { " Error registering screen lock receiver: ${e.message} " }
316+ screenLockReceiver = null
317+ }
293318 }
294319
295- private fun unregisterScreenLockReceiver () {
320+ private fun unregisterScreenLockReceiver (context : Context ) {
296321 screenLockReceiver?.let {
297322 try {
298323 context.unregisterReceiver(it)
@@ -304,45 +329,50 @@ actual open class VideoPlayerState {
304329 }
305330 }
306331
307- private fun initializePlayer () {
332+ private fun initializePlayer (context : Context ) {
308333 synchronized(playerInitializationLock) {
309- if (isPlayerReleased) return
310-
311- val audioSink = DefaultAudioSink .Builder (context)
312- .setAudioProcessors(arrayOf(audioProcessor))
313- .build()
334+ if (isPlayerReleased || exoPlayer != null ) return
314335
315- val renderersFactory = object : DefaultRenderersFactory (context) {
316- override fun buildAudioSink (
317- context : Context ,
318- enableFloatOutput : Boolean ,
319- enableAudioTrackPlaybackParams : Boolean
320- ): AudioSink = audioSink
321- }.apply {
322- setExtensionRendererMode(DefaultRenderersFactory .EXTENSION_RENDERER_MODE_PREFER )
323- // Activer le fallback du décodeur pour une meilleure stabilité
324- setEnableDecoderFallback(true )
325-
326- // Sur les appareils problématiques, utiliser des paramètres plus conservateurs
327- if (shouldUseConservativeCodecHandling()) {
328- // On ne peut pas désactiver l'async queueing car la méthode n'existe pas
329- // Mais on peut utiliser le MediaCodecSelector par défaut
330- setMediaCodecSelector(MediaCodecSelector .DEFAULT )
336+ try {
337+ val audioSink = DefaultAudioSink .Builder (context)
338+ .setAudioProcessors(arrayOf(audioProcessor))
339+ .build()
340+
341+ val renderersFactory = object : DefaultRenderersFactory (context) {
342+ override fun buildAudioSink (
343+ context : Context ,
344+ enableFloatOutput : Boolean ,
345+ enableAudioTrackPlaybackParams : Boolean
346+ ): AudioSink = audioSink
347+ }.apply {
348+ setExtensionRendererMode(DefaultRenderersFactory .EXTENSION_RENDERER_MODE_PREFER )
349+ // Activer le fallback du décodeur pour une meilleure stabilité
350+ setEnableDecoderFallback(true )
351+
352+ // Sur les appareils problématiques, utiliser des paramètres plus conservateurs
353+ if (shouldUseConservativeCodecHandling()) {
354+ // On ne peut pas désactiver l'async queueing car la méthode n'existe pas
355+ // Mais on peut utiliser le MediaCodecSelector par défaut
356+ setMediaCodecSelector(MediaCodecSelector .DEFAULT )
357+ }
331358 }
332- }
333359
334- exoPlayer = ExoPlayer .Builder (context)
335- .setRenderersFactory(renderersFactory)
336- .setHandleAudioBecomingNoisy(true )
337- .setWakeMode(C .WAKE_MODE_LOCAL )
338- .setPauseAtEndOfMediaItems(false )
339- .setReleaseTimeoutMs(2000 ) // Augmenter le timeout de libération
340- .build()
341- .apply {
342- playerListener = createPlayerListener()
343- addListener(playerListener!! )
344- volume = _volume
345- }
360+ exoPlayer = ExoPlayer .Builder (context)
361+ .setRenderersFactory(renderersFactory)
362+ .setHandleAudioBecomingNoisy(true )
363+ .setWakeMode(C .WAKE_MODE_LOCAL )
364+ .setPauseAtEndOfMediaItems(false )
365+ .setReleaseTimeoutMs(2000 ) // Augmenter le timeout de libération
366+ .build()
367+ .apply {
368+ playerListener = createPlayerListener()
369+ addListener(playerListener!! )
370+ volume = _volume
371+ }
372+ } catch (e: Exception ) {
373+ androidVideoLogger.e { " Error initializing player: ${e.message} " }
374+ exoPlayer = null
375+ }
346376 }
347377 }
348378
@@ -460,7 +490,12 @@ actual open class VideoPlayerState {
460490 player.release()
461491
462492 // Réinitialiser
463- initializePlayer()
493+ exoPlayer = null
494+ playerListener = null
495+ appContext?.let { context ->
496+ initializePlayer(context)
497+ registerScreenLockReceiver(context)
498+ }
464499
465500 // Restaurer l'élément média et la position
466501 currentMediaItem?.let {
@@ -509,12 +544,32 @@ actual open class VideoPlayerState {
509544 }
510545
511546 actual fun openUri (uri : String , initializeplayerState : InitialPlayerState ) {
547+ if (previewMode) {
548+ _error = null
549+ _hasMedia = true
550+ _isPlaying = initializeplayerState == InitialPlayerState .PLAY
551+ return
552+ }
553+ if (! ensureInitialized()) {
554+ _error = VideoPlayerError .UnknownError (" Android context is not available (preview or missing ContextProvider initialization)." )
555+ return
556+ }
512557 val mediaItemBuilder = MediaItem .Builder ().setUri(uri)
513558 val mediaItem = mediaItemBuilder.build()
514559 openFromMediaItem(mediaItem, initializeplayerState)
515560 }
516561
517562 actual fun openFile (file : PlatformFile , initializeplayerState : InitialPlayerState ) {
563+ if (previewMode) {
564+ _error = null
565+ _hasMedia = true
566+ _isPlaying = initializeplayerState == InitialPlayerState .PLAY
567+ return
568+ }
569+ if (! ensureInitialized()) {
570+ _error = VideoPlayerError .UnknownError (" Android context is not available (preview or missing ContextProvider initialization)." )
571+ return
572+ }
518573 val mediaItemBuilder = MediaItem .Builder ()
519574 val videoUri: Uri = when (val androidFile = file.androidFile) {
520575 is AndroidFile .UriWrapper -> androidFile.uri
@@ -529,43 +584,55 @@ actual open class VideoPlayerState {
529584 synchronized(playerInitializationLock) {
530585 if (isPlayerReleased) return
531586
532- exoPlayer?. let { player ->
533- player.stop()
534- player.clearMediaItems()
535- try {
536- _error = null
537- resetStates(keepMedia = true )
587+ val player = exoPlayer ? : run {
588+ _isPlaying = false
589+ _hasMedia = false
590+ _error = VideoPlayerError . UnknownError ( " Video player is not initialized. " )
591+ return
592+ }
538593
539- // Extraire les métadonnées avant de préparer le lecteur
540- extractMediaItemMetadata(mediaItem)
541-
542- player.setMediaItem(mediaItem)
543- player.prepare()
544- player.volume = volume
545- player.repeatMode = if (loop) Player .REPEAT_MODE_ALL else Player .REPEAT_MODE_OFF
546-
547- // Contrôler l'état de lecture initial
548- if (initializeplayerState == InitialPlayerState .PLAY ) {
549- player.play()
550- _hasMedia = true
551- } else {
552- player.pause()
553- _isPlaying = false
554- _hasMedia = true
555- }
556- } catch (e: Exception ) {
557- androidVideoLogger.d { " Error opening media: ${e.message} " }
594+ player.stop()
595+ player.clearMediaItems()
596+ try {
597+ _error = null
598+ resetStates(keepMedia = true )
599+
600+ // Extraire les métadonnées avant de préparer le lecteur
601+ extractMediaItemMetadata(mediaItem)
602+
603+ player.setMediaItem(mediaItem)
604+ player.prepare()
605+ player.volume = volume
606+ player.repeatMode = if (loop) Player .REPEAT_MODE_ALL else Player .REPEAT_MODE_OFF
607+
608+ // Contrôler l'état de lecture initial
609+ if (initializeplayerState == InitialPlayerState .PLAY ) {
610+ player.play()
611+ _hasMedia = true
612+ } else {
613+ player.pause()
558614 _isPlaying = false
559- _hasMedia = false
560- _error = VideoPlayerError .SourceError (" Failed to load media: ${e.message} " )
615+ _hasMedia = true
561616 }
617+ } catch (e: Exception ) {
618+ androidVideoLogger.d { " Error opening media: ${e.message} " }
619+ _isPlaying = false
620+ _hasMedia = false
621+ _error = VideoPlayerError .SourceError (" Failed to load media: ${e.message} " )
562622 }
563623 }
564624 }
565625
566626 actual fun play () {
567627 synchronized(playerInitializationLock) {
568628 if (! isPlayerReleased) {
629+ if (previewMode && exoPlayer == null ) {
630+ _hasMedia = true
631+ _isPlaying = true
632+ return
633+ }
634+
635+ ensureInitialized()
569636 exoPlayer?.let { player ->
570637 if (player.playbackState == Player .STATE_IDLE ) {
571638 player.prepare()
@@ -580,6 +647,12 @@ actual open class VideoPlayerState {
580647 actual fun pause () {
581648 synchronized(playerInitializationLock) {
582649 if (! isPlayerReleased) {
650+ if (previewMode && exoPlayer == null ) {
651+ _isPlaying = false
652+ return
653+ }
654+
655+ ensureInitialized()
583656 exoPlayer?.pause()
584657 }
585658 }
@@ -588,6 +661,14 @@ actual open class VideoPlayerState {
588661 actual fun stop () {
589662 synchronized(playerInitializationLock) {
590663 if (! isPlayerReleased) {
664+ if (previewMode && exoPlayer == null ) {
665+ _hasMedia = false
666+ _isPlaying = false
667+ resetStates(keepMedia = true )
668+ return
669+ }
670+
671+ ensureInitialized()
591672 exoPlayer?.let { player ->
592673 player.stop()
593674 player.seekTo(0 )
@@ -711,8 +792,11 @@ actual open class VideoPlayerState {
711792
712793 playerListener = null
713794 exoPlayer = null
714- unregisterScreenLockReceiver()
795+ appContext?. let { unregisterScreenLockReceiver(it) }
715796 resetStates()
716797 }
717798 }
718- }
799+ }
800+
801+ internal actual fun createVideoPlayerState (isInPreview : Boolean ): VideoPlayerState =
802+ VideoPlayerState (isInPreview)
0 commit comments