expo-libvlc-player 6.0.0 → 6.0.2

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.
@@ -29,5 +29,5 @@ repositories {
29
29
  }
30
30
 
31
31
  dependencies {
32
- implementation 'org.videolan.android:libvlc-all:3.6.5'
32
+ implementation 'org.videolan.android:libvlc-all:3.7.0'
33
33
  }
@@ -110,7 +110,7 @@ class LibVlcPlayerModule : Module() {
110
110
  }
111
111
 
112
112
  OnViewDestroys { view: LibVlcPlayerView ->
113
- MediaPlayerManager.unregisterPlayerView(view)
113
+ MediaPlayerManager.unregisterExpoView(view)
114
114
  view.destroyPlayer()
115
115
  }
116
116
 
@@ -127,7 +127,7 @@ class LibVlcPlayerModule : Module() {
127
127
  }
128
128
 
129
129
  AsyncFunction("seek") { view: LibVlcPlayerView, value: Double, type: String? ->
130
- view.seek(value, type ?: "time")
130
+ view.seek(value, type)
131
131
  }
132
132
 
133
133
  AsyncFunction("record") { view: LibVlcPlayerView, path: String? ->
@@ -6,9 +6,11 @@ import android.graphics.Matrix
6
6
  import android.net.Uri
7
7
  import android.os.Handler
8
8
  import android.os.Looper
9
+ import android.util.Size
9
10
  import android.view.PixelCopy
10
11
  import android.view.Surface
11
12
  import android.view.TextureView
13
+ import android.view.ViewGroup
12
14
  import expo.modules.kotlin.AppContext
13
15
  import expo.modules.kotlin.viewevent.EventDispatcher
14
16
  import expo.modules.kotlin.views.ExpoView
@@ -42,17 +44,14 @@ private val DISPLAY_MANAGER: DisplayManager? = null
42
44
  private val ENABLE_SUBTITLES: Boolean = true
43
45
  private val USE_TEXTURE_VIEW: Boolean = true
44
46
 
45
- private val MAX_RETRY_COUNT: Int = 5
46
-
47
47
  class LibVlcPlayerView(
48
48
  context: Context,
49
49
  appContext: AppContext,
50
50
  ) : ExpoView(context, appContext) {
51
- private val playerView = VLCVideoLayout(context)
51
+ private val playerView: VLCVideoLayout = VLCVideoLayout(context)
52
52
 
53
53
  var libVLC: LibVLC? = null
54
54
  var mediaPlayer: MediaPlayer? = null
55
- var media: Media? = null
56
55
  var vlcDialog: VLCDialog? = null
57
56
 
58
57
  var firstPlay: Boolean = true
@@ -74,20 +73,20 @@ class LibVlcPlayerView(
74
73
  val onBackground by EventDispatcher<Unit>()
75
74
 
76
75
  init {
77
- MediaPlayerManager.registerPlayerView(this)
78
- addView(playerView)
76
+ MediaPlayerManager.registerExpoView(this)
77
+ addPlayerView()
79
78
  }
80
79
 
81
80
  override fun onAttachedToWindow() {
82
81
  super.onAttachedToWindow()
83
82
 
84
- attachPlayer()
83
+ attachPlayerView()
85
84
  }
86
85
 
87
86
  override fun onDetachedFromWindow() {
88
87
  super.onDetachedFromWindow()
89
88
 
90
- detachPlayer()
89
+ detachPlayerView()
91
90
  }
92
91
 
93
92
  override fun onSizeChanged(
@@ -114,11 +113,15 @@ class LibVlcPlayerView(
114
113
  }
115
114
 
116
115
  fun createPlayer() {
117
- libVLC = LibVLC(context, options)
116
+ var args = options
117
+ args.toggleStartPausedOption(autoplay)
118
+
119
+ libVLC = LibVLC(context, args)
118
120
  setDialogCallbacks(libVLC!!)
119
121
 
120
122
  mediaPlayer = MediaPlayer(libVLC!!)
121
123
  setPlayerListener(mediaPlayer!!)
124
+ attachPlayerView()
122
125
 
123
126
  try {
124
127
  URI(source)
@@ -127,26 +130,32 @@ class LibVlcPlayerView(
127
130
  return
128
131
  }
129
132
 
130
- media = Media(libVLC!!, Uri.parse(source!!))
131
- mediaPlayer!!.setMedia(media!!)
132
- media!!.release()
133
-
134
- if (autoplay) {
135
- mediaPlayer!!.play()
136
- }
133
+ val media = Media(libVLC!!, Uri.parse(source!!))
134
+ mediaPlayer!!.setMedia(media)
135
+ media.release()
136
+ mediaPlayer!!.play()
137
137
 
138
138
  firstPlay = true
139
139
  shouldInit = false
140
140
  }
141
141
 
142
+ fun resetPlayer() {
143
+ detachPlayer()
144
+ attachPlayer()
145
+ }
146
+
142
147
  fun attachPlayer() {
143
- mediaPlayer?.let { player ->
144
- val parent = playerView.getParent()
148
+ attachPlayerView()
149
+ addPlayerView()
150
+ }
145
151
 
146
- if (parent == null) {
147
- addView(playerView)
148
- }
152
+ fun detachPlayer() {
153
+ detachPlayerView()
154
+ removePlayerView()
155
+ }
149
156
 
157
+ fun attachPlayerView() {
158
+ mediaPlayer?.let { player ->
150
159
  val attached = player.getVLCVout().areViewsAttached()
151
160
 
152
161
  if (!attached) {
@@ -155,9 +164,30 @@ class LibVlcPlayerView(
155
164
  }
156
165
  }
157
166
 
158
- fun detachPlayer() {
159
- mediaPlayer?.detachViews()
160
- removeAllViews()
167
+ fun detachPlayerView() {
168
+ mediaPlayer?.let { player ->
169
+ val attached = player.getVLCVout().areViewsAttached()
170
+
171
+ if (attached) {
172
+ player.detachViews()
173
+ }
174
+ }
175
+ }
176
+
177
+ fun addPlayerView() {
178
+ val parent = playerView.parent as? ViewGroup
179
+
180
+ if (parent == null) {
181
+ addView(playerView)
182
+ }
183
+ }
184
+
185
+ fun removePlayerView() {
186
+ val parent = playerView.parent as? ViewGroup
187
+
188
+ if (parent != null) {
189
+ removeView(playerView)
190
+ }
161
191
  }
162
192
 
163
193
  fun destroyPlayer() {
@@ -165,7 +195,6 @@ class LibVlcPlayerView(
165
195
  libVLC = null
166
196
  mediaPlayer?.release()
167
197
  mediaPlayer = null
168
- media = null
169
198
  removeAllViews()
170
199
  }
171
200
 
@@ -210,9 +239,9 @@ class LibVlcPlayerView(
210
239
  val matrix = Matrix()
211
240
 
212
241
  mediaPlayer?.let { player ->
213
- val video = player.getCurrentVideoTrack() ?: return@post
242
+ val video = getVideoSize()
214
243
 
215
- if (hasVideoSize()) {
244
+ if (hasVideoSize) {
216
245
  val viewWidth = view.width.toFloat()
217
246
  val viewHeight = view.height.toFloat()
218
247
 
@@ -367,28 +396,29 @@ class LibVlcPlayerView(
367
396
  return mediaInfo
368
397
  }
369
398
 
370
- fun hasAudioVideo(): Boolean {
371
- val tracks = getMediaTracks()
372
- val length = getMediaLength()
373
-
374
- val hasAudio = tracks.audio.any { track -> track.id != -1 }
375
- val hasVideo = tracks.video.any { track -> track.id != -1 }
399
+ fun getVideoSize(): Size {
400
+ val video = mediaPlayer?.getCurrentVideoTrack()
376
401
 
377
- val hasAudioOnly = hasAudio && !hasVideo && length > 0L
378
- val hasAudioAndVideo = hasAudio && hasVideo && hasVideoSize() && length > 0L
402
+ if (video != null) {
403
+ return Size(video.width, video.height)
404
+ }
379
405
 
380
- return hasAudioOnly || hasAudioAndVideo
406
+ return Size(0, 0)
381
407
  }
382
408
 
383
- fun hasVideoSize(): Boolean {
384
- val video = mediaPlayer?.getCurrentVideoTrack()
409
+ val hasVideoOut: Boolean
410
+ get() {
411
+ val tracks = getMediaTracks()
412
+ val length = getMediaLength()
413
+ val hasVideo = tracks.video.any { track -> track.id != -1 }
414
+ return hasVideo && hasVideoSize && length > 0L
415
+ }
385
416
 
386
- return if (video != null) {
387
- video.width > 0 && video.height > 0
388
- } else {
389
- false
417
+ val hasVideoSize: Boolean
418
+ get() {
419
+ val video = getVideoSize()
420
+ return video.width > 0 && video.height > 0
390
421
  }
391
- }
392
422
 
393
423
  var source: String? = null
394
424
  set(value) {
@@ -485,20 +515,33 @@ class LibVlcPlayerView(
485
515
  }
486
516
 
487
517
  fun play() {
488
- mediaPlayer?.play()
518
+ mediaPlayer?.let { player ->
519
+ if (!autoplay) {
520
+ player.play()
521
+ }
522
+
523
+ player.play()
524
+ }
489
525
  }
490
526
 
491
527
  fun pause() {
492
528
  mediaPlayer?.pause()
493
529
  }
494
530
 
531
+ fun pauseIf(condition: Boolean? = true) {
532
+ mediaPlayer?.let { player ->
533
+ val shouldPause = condition == true && player.isPlaying()
534
+ if (shouldPause) player.pause()
535
+ }
536
+ }
537
+
495
538
  fun stop() {
496
539
  mediaPlayer?.stop()
497
540
  }
498
541
 
499
542
  fun seek(
500
543
  value: Double,
501
- type: String,
544
+ type: String? = "time",
502
545
  ) {
503
546
  mediaPlayer?.let { player ->
504
547
  if (player.isSeekable()) {
@@ -537,9 +580,9 @@ class LibVlcPlayerView(
537
580
  mediaPlayer?.let { player ->
538
581
  try {
539
582
  val view = getTextureView() ?: throw Exception()
540
- val video = player.getCurrentVideoTrack() ?: throw Exception()
583
+ val video = getVideoSize()
541
584
 
542
- if (!hasVideoSize()) throw Exception()
585
+ if (!hasVideoSize) throw Exception()
543
586
 
544
587
  val surface = Surface(view.surfaceTexture)
545
588
  val bitmap = Bitmap.createBitmap(video.width, video.height, Bitmap.Config.ARGB_8888)
@@ -588,169 +631,199 @@ class LibVlcPlayerView(
588
631
  }
589
632
 
590
633
  fun retryUntil(
591
- maxRetries: Int = MAX_RETRY_COUNT,
634
+ maxRetries: Int = MediaPlayerConstants.MAX_RETRY_COUNT,
592
635
  retry: Int = 0,
593
- delay: Long = 100L,
594
- block: () -> Boolean,
636
+ delay: Long = MediaPlayerConstants.RETRY_DELAY_MS,
637
+ block: (isLastAttempt: Boolean) -> Boolean,
595
638
  ) {
596
- if (block() || retry >= maxRetries) return
639
+ val isLastAttempt = retry >= maxRetries
640
+
641
+ if (block(isLastAttempt) || isLastAttempt) return
597
642
 
598
- val expDelay = delay.toDouble() * 1.5
643
+ val expDelay = (delay.toDouble() * 1.5).toLong()
599
644
 
600
645
  postDelayed({
601
- retryUntil(
602
- maxRetries,
603
- retry + 1,
604
- expDelay.toLong(),
605
- block,
606
- )
646
+ retryUntil(maxRetries, retry + 1, expDelay, block)
607
647
  }, delay)
608
648
  }
609
649
  }
610
650
 
611
- fun LibVlcPlayerView.setPlayerListener(player: MediaPlayer) {
612
- player.setEventListener(
613
- EventListener { event ->
614
- when (event.type) {
615
- Event.Buffering -> {
616
- onBuffering(Unit)
617
- }
651
+ fun LibVlcPlayerView.setPlayerListener(mediaPlayer: MediaPlayer?) {
652
+ mediaPlayer?.let { player ->
653
+ player.setEventListener(
654
+ EventListener { event ->
655
+ val type = event.type
618
656
 
619
- Event.Playing -> {
620
- onPlaying(Unit)
657
+ @Suppress("ktlint")
658
+ when (type) {
659
+ Event.Buffering -> {
660
+ onBuffering(Unit)
661
+ }
621
662
 
622
- if (firstPlay) {
623
- retryUntil {
624
- onFirstPlay(getMediaInfo())
625
- return@retryUntil hasAudioVideo()
626
- }
663
+ Event.Playing,
664
+ Event.Paused,
665
+ Event.Stopped -> {
666
+ if (type == Event.Playing) {
667
+ onPlaying(Unit)
627
668
 
628
- retryUntil {
629
- setContentFit()
630
- return@retryUntil hasVideoSize()
631
- }
669
+ if (firstPlay) {
670
+ setupPlayer()
632
671
 
633
- setupPlayer()
672
+ firstPlay = false
634
673
 
635
- firstPlay = false
636
- }
674
+ retryUntil { isLastAttempt ->
675
+ val shouldSendEvent = hasVideoOut || isLastAttempt
637
676
 
638
- attachPlayer()
677
+ if (shouldSendEvent) {
678
+ onFirstPlay(getMediaInfo())
679
+ }
639
680
 
640
- MediaPlayerManager.keepAwakeManager.toggleKeepAwake()
681
+ return@retryUntil hasVideoOut
682
+ }
641
683
 
642
- retryUntil {
643
- val volume = player.getVolume()
644
- val hasVolume = volume > MediaPlayerConstants.MIN_PLAYER_VOLUME
645
- MediaPlayerManager.audioFocusManager.updateAudioFocus()
646
- return@retryUntil hasVolume
647
- }
648
- }
684
+ retryUntil { isLastAttempt ->
685
+ val shouldFitContent = hasVideoSize || isLastAttempt
649
686
 
650
- Event.Paused -> {
651
- onPaused(Unit)
687
+ if (shouldFitContent) {
688
+ setContentFit()
689
+ }
652
690
 
653
- MediaPlayerManager.keepAwakeManager.toggleKeepAwake()
654
- MediaPlayerManager.audioFocusManager.updateAudioFocus()
655
- }
691
+ return@retryUntil hasVideoSize
692
+ }
693
+ }
694
+ }
695
+
696
+ if (type == Event.Paused) {
697
+ onPaused(Unit)
698
+ }
656
699
 
657
- Event.Stopped -> {
658
- onStopped(Unit)
700
+ if (type == Event.Stopped) {
701
+ onStopped(Unit)
659
702
 
660
- detachPlayer()
703
+ resetPlayer()
661
704
 
662
- MediaPlayerManager.keepAwakeManager.toggleKeepAwake()
663
- MediaPlayerManager.audioFocusManager.updateAudioFocus()
664
- }
705
+ if (repeat) {
706
+ player.play()
707
+ }
708
+ }
709
+
710
+ MediaPlayerManager.keepAwakeManager.toggleKeepAwake()
711
+
712
+ retryUntil { isLastAttempt ->
713
+ val volume = player.getVolume()
714
+ val hasVolume = volume > MediaPlayerConstants.MIN_PLAYER_VOLUME
715
+ val shouldUpdateFocus = hasVolume || isLastAttempt
665
716
 
666
- Event.EndReached -> {
667
- player.stop()
717
+ if (shouldUpdateFocus) {
718
+ MediaPlayerManager.audioFocusManager.updateAudioFocus()
719
+ }
668
720
 
669
- if (repeat) {
670
- player.play()
721
+ return@retryUntil hasVolume
722
+ }
671
723
  }
672
- }
673
724
 
674
- Event.EncounteredError -> {
675
- onEncounteredError(mapOf("error" to "Player encountered an error"))
725
+ Event.EndReached -> {
726
+ player.stop()
727
+ }
676
728
 
677
- player.stop()
678
- }
729
+ Event.EncounteredError -> {
730
+ onEncounteredError(mapOf("error" to "Player encountered an error"))
679
731
 
680
- Event.TimeChanged -> {
681
- onTimeChanged(mapOf("time" to player.getTime().toInt()))
682
- }
732
+ player.stop()
733
+ }
683
734
 
684
- Event.PositionChanged -> {
685
- onPositionChanged(mapOf("position" to player.getPosition()))
686
- }
735
+ Event.TimeChanged -> {
736
+ onTimeChanged(mapOf("time" to player.getTime().toInt()))
737
+ }
738
+
739
+ Event.PositionChanged -> {
740
+ onPositionChanged(mapOf("position" to player.getPosition()))
741
+ }
742
+
743
+ Event.ESAdded -> {
744
+ onESAdded(getMediaTracks())
745
+ }
746
+
747
+ Event.RecordChanged -> {
748
+ val recording =
749
+ Recording(
750
+ path = event.getRecordPath(),
751
+ isRecording = event.getRecording(),
752
+ )
687
753
 
688
- Event.ESAdded -> {
689
- onESAdded(getMediaTracks())
754
+ onRecordChanged(recording)
755
+ }
690
756
  }
757
+ },
758
+ )
759
+ }
760
+ }
691
761
 
692
- Event.RecordChanged -> {
693
- val recording =
694
- Recording(
695
- path = event.getRecordPath(),
696
- isRecording = event.getRecording(),
762
+ fun LibVlcPlayerView.setDialogCallbacks(ILibVLC: LibVLC?) {
763
+ ILibVLC?.let { libVLC ->
764
+ VLCDialog.setCallbacks(
765
+ libVLC,
766
+ object : VLCDialog.Callbacks {
767
+ override fun onDisplay(dialog: VLCDialog.ErrorMessage) {
768
+ vlcDialog = dialog
769
+
770
+ val dialog =
771
+ Dialog(
772
+ title = dialog.getTitle(),
773
+ text = dialog.getText(),
697
774
  )
698
775
 
699
- onRecordChanged(recording)
776
+ onDialogDisplay(dialog)
700
777
  }
701
- }
702
- },
703
- )
704
- }
705
778
 
706
- fun LibVlcPlayerView.setDialogCallbacks(libVLC: LibVLC) {
707
- VLCDialog.setCallbacks(
708
- libVLC,
709
- object : VLCDialog.Callbacks {
710
- override fun onDisplay(dialog: VLCDialog.ErrorMessage) {
711
- vlcDialog = dialog
779
+ override fun onDisplay(dialog: VLCDialog.LoginDialog) {
780
+ vlcDialog = dialog
712
781
 
713
- val dialog =
714
- Dialog(
715
- title = dialog.getTitle(),
716
- text = dialog.getText(),
717
- )
782
+ val dialog =
783
+ Dialog(
784
+ title = dialog.getTitle(),
785
+ text = dialog.getText(),
786
+ )
718
787
 
719
- onDialogDisplay(dialog)
720
- }
788
+ onDialogDisplay(dialog)
789
+ }
721
790
 
722
- override fun onDisplay(dialog: VLCDialog.LoginDialog) {
723
- vlcDialog = dialog
791
+ override fun onDisplay(dialog: VLCDialog.QuestionDialog) {
792
+ vlcDialog = dialog
724
793
 
725
- val dialog =
726
- Dialog(
727
- title = dialog.getTitle(),
728
- text = dialog.getText(),
729
- )
794
+ val dialog =
795
+ Dialog(
796
+ title = dialog.getTitle(),
797
+ text = dialog.getText(),
798
+ cancelText = dialog.getCancelText(),
799
+ action1Text = dialog.getAction1Text(),
800
+ action2Text = dialog.getAction2Text(),
801
+ )
730
802
 
731
- onDialogDisplay(dialog)
732
- }
803
+ onDialogDisplay(dialog)
804
+ }
733
805
 
734
- override fun onDisplay(dialog: VLCDialog.QuestionDialog) {
735
- vlcDialog = dialog
806
+ override fun onDisplay(dialog: VLCDialog.ProgressDialog) {}
736
807
 
737
- val dialog =
738
- Dialog(
739
- title = dialog.getTitle(),
740
- text = dialog.getText(),
741
- cancelText = dialog.getCancelText(),
742
- action1Text = dialog.getAction1Text(),
743
- action2Text = dialog.getAction2Text(),
744
- )
808
+ override fun onCanceled(dialog: VLCDialog) {}
745
809
 
746
- onDialogDisplay(dialog)
747
- }
810
+ override fun onProgressUpdate(dialog: VLCDialog.ProgressDialog) {}
811
+ },
812
+ )
813
+ }
814
+ }
748
815
 
749
- override fun onDisplay(dialog: VLCDialog.ProgressDialog) {}
816
+ private fun MutableList<String>.toggleStartPausedOption(autoplay: Boolean) {
817
+ val options =
818
+ setOf(
819
+ "--start-paused",
820
+ "-start-paused",
821
+ ":start-paused",
822
+ )
750
823
 
751
- override fun onCanceled(dialog: VLCDialog) {}
824
+ removeAll { option -> option in options }
752
825
 
753
- override fun onProgressUpdate(dialog: VLCDialog.ProgressDialog) {}
754
- },
755
- )
826
+ if (!autoplay) {
827
+ add("--start-paused")
828
+ }
756
829
  }
@@ -6,4 +6,7 @@ object MediaPlayerConstants {
6
6
  const val DEFAULT_PLAYER_TIME: Int = 0
7
7
  const val MIN_PLAYER_VOLUME: Int = 0
8
8
  const val MAX_PLAYER_VOLUME: Int = 100
9
+
10
+ const val RETRY_DELAY_MS: Long = 200L
11
+ const val MAX_RETRY_COUNT: Int = 5
9
12
  }