1+ package com.raival.compose.file.explorer.screen.viewer.media.misc
2+
3+ import android.content.Context
4+ import android.net.Uri
5+ import android.os.Handler
6+ import android.os.Looper
7+ import androidx.media3.common.MediaItem
8+ import androidx.media3.common.MediaMetadata
9+ import androidx.media3.common.Player
10+ import androidx.media3.exoplayer.ExoPlayer
11+
12+ /* *
13+ * A video player manager using Media3 ExoPlayer.
14+ * Provides video playback controls and state management.
15+ */
16+ class VideoPlayerManager (context : Context ) {
17+
18+ private val exoPlayer: ExoPlayer = ExoPlayer .Builder (context).build()
19+ private var listener: VideoPlayerManagerListener ? = null
20+
21+ // Handler to update playback progress every second.
22+ private val updateHandler = Handler (Looper .getMainLooper())
23+ private val updateRunnable = object : Runnable {
24+ override fun run () {
25+ val currentPosition = exoPlayer.currentPosition
26+ val duration = exoPlayer.duration.takeIf { it > 0 } ? : 0L
27+ val remaining = if (duration > currentPosition) duration - currentPosition else 0L
28+ listener?.onProgressUpdated(currentPosition, remaining)
29+ updateHandler.postDelayed(this , 1000 )
30+ }
31+ }
32+
33+ /* *
34+ * Set a listener to receive callbacks.
35+ */
36+ fun setListener (listener : VideoPlayerManagerListener ) {
37+ this .listener = listener
38+ }
39+
40+ /* *
41+ * Prepares the player with the given video URI.
42+ */
43+ fun prepare (uri : Uri ) {
44+ val mediaItem = MediaItem .fromUri(uri)
45+ exoPlayer.setMediaItem(mediaItem)
46+ exoPlayer.repeatMode = Player .REPEAT_MODE_ONE
47+ exoPlayer.prepare()
48+
49+ // Listen for playback and metadata events.
50+ exoPlayer.addListener(object : Player .Listener {
51+ override fun onPlaybackStateChanged (state : Int ) {
52+ val isPlaying = exoPlayer.playWhenReady && state == Player .STATE_READY
53+ val isLoading = state == Player .STATE_BUFFERING
54+ listener?.onPlaybackStateChanged(isPlaying, isLoading)
55+ }
56+
57+ override fun onMediaMetadataChanged (mediaMetadata : MediaMetadata ) {
58+ val title = mediaMetadata.title?.toString()
59+ val duration = exoPlayer.duration.takeIf { it > 0 } ? : 0L
60+
61+ val metadata = VideoMetadata (title, duration)
62+ listener?.onMetadataChanged(metadata)
63+ }
64+ })
65+ }
66+
67+ /* *
68+ * Gets the ExoPlayer instance for video rendering.
69+ */
70+ fun getPlayer (): ExoPlayer = exoPlayer
71+
72+ /* *
73+ * Starts playback and begins periodic progress updates.
74+ */
75+ fun play () {
76+ exoPlayer.playWhenReady = true
77+ updateHandler.post(updateRunnable)
78+ }
79+
80+ /* *
81+ * Pauses playback and stops progress updates.
82+ */
83+ fun pause () {
84+ exoPlayer.playWhenReady = false
85+ updateHandler.removeCallbacks(updateRunnable)
86+ }
87+
88+ /* *
89+ * Stops playback completely.
90+ */
91+ fun stop () {
92+ exoPlayer.stop()
93+ updateHandler.removeCallbacks(updateRunnable)
94+ }
95+
96+ /* *
97+ * Fast-forwards playback by 10 seconds.
98+ */
99+ fun forward () {
100+ val newPosition = exoPlayer.currentPosition + 10000L
101+ exoPlayer.seekTo(newPosition)
102+ }
103+
104+ /* *
105+ * Rewinds playback by 10 seconds.
106+ */
107+ fun backward () {
108+ val newPosition = (exoPlayer.currentPosition - 10000L ).coerceAtLeast(0L )
109+ exoPlayer.seekTo(newPosition)
110+ }
111+
112+ /* *
113+ * Seek to a new position
114+ */
115+ fun seekTo (position : Long ) {
116+ val newPosition = position.coerceIn(0 , exoPlayer.duration)
117+ exoPlayer.seekTo(newPosition)
118+ }
119+
120+ /* *
121+ * Releases player resources.
122+ */
123+ fun release () {
124+ updateHandler.removeCallbacks(updateRunnable)
125+ exoPlayer.release()
126+ }
127+
128+ /* *
129+ * Callback interface for playback state, progress, and metadata updates.
130+ */
131+ interface VideoPlayerManagerListener {
132+ /* *
133+ * Called when playback state changes.
134+ * @param isPlaying true if the video is playing.
135+ * @param isLoading true if the video is buffering/loading.
136+ */
137+ fun onPlaybackStateChanged (isPlaying : Boolean , isLoading : Boolean )
138+
139+ /* *
140+ * Called periodically with the current position and remaining time (in milliseconds).
141+ */
142+ fun onProgressUpdated (currentPosition : Long , remainingTime : Long )
143+
144+ /* *
145+ * Called when metadata for the current media item is available.
146+ */
147+ fun onMetadataChanged (metadata : VideoMetadata )
148+ }
149+
150+ /* *
151+ * Data class to hold basic video metadata.
152+ */
153+ data class VideoMetadata (
154+ val title : String? ,
155+ val duration : Long
156+ )
157+ }
0 commit comments