Try this VideoSurface
custom view:
import android.content.Context
import android.graphics.SurfaceTexture
import android.media.MediaPlayer
import android.net.Uri
import android.util.AttributeSet
import android.view.Surface
import android.view.TextureView
import android.view.TextureView.SurfaceTextureListener
/**
* A [TextureView]-based custom video surface that wraps [MediaPlayer] for
* lightweight video playback. This class allows playing looping videos inside
* a Compose `AndroidView` or traditional Views without requiring ExoPlayer.
*
* Usage:
* ```
* val videoSurface = VideoSurface(context).apply {
* setSource(videoUri)
* setOnPreparedListener { mp ->
* // do something when ready, e.g. hide loader
* }
* setOnCompletionListener {
* // handle completion if looping is disabled
* }
* setOnErrorListener { mp, what, extra ->
* // handle error
* true
* }
* }
* ```
*
* Notes:
* - Releases its [MediaPlayer] automatically on [onDetachedFromWindow].
* - You must call [setSource] before the surface is available.
* - Starts playback automatically once prepared.
*/
class VideoSurface @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : TextureView(context, attrs, defStyle), SurfaceTextureListener {
private val mediaPlayer = MediaPlayer()
private var source: Uri? = null
private var completionListener: MediaPlayer.OnCompletionListener? = null
private var preparedListener: MediaPlayer.OnPreparedListener? = null
private var errorListener: MediaPlayer.OnErrorListener? = null
init {
surfaceTextureListener = this
}
/**
* Sets the video source [Uri].
*
* Must be called before the surface is available for playback to start.
*/
fun setSource(source: Uri?) {
this.source = source
}
/**
* Registers a listener to be notified when playback completes.
*/
fun setOnCompletionListener(listener: MediaPlayer.OnCompletionListener?) {
completionListener = listener
}
/**
* Registers a listener to be notified when the video is prepared.
*/
fun setOnPreparedListener(listener: MediaPlayer.OnPreparedListener?) {
preparedListener = listener
}
/**
* Registers a listener to be notified when an error occurs during playback.
*/
fun setOnErrorListener(listener: MediaPlayer.OnErrorListener?) {
errorListener = listener
}
/**
* Releases the [MediaPlayer] when the view is detached.
*/
override fun onDetachedFromWindow() {
mediaPlayer.reset()
super.onDetachedFromWindow()
}
override fun onSurfaceTextureAvailable(
surfaceTexture: SurfaceTexture,
width: Int,
height: Int
) {
val surface = Surface(surfaceTexture)
try {
mediaPlayer.apply {
setOnCompletionListener(completionListener)
setOnErrorListener(errorListener)
setSurface(surface)
isLooping = true
source?.let { setDataSource(context, it) }
setOnPreparedListener { mp ->
start()
preparedListener?.onPrepared(mp)
}
prepareAsync()
}
} catch (e: Exception) {
e.printStackTrace()
mediaPlayer.reset()
}
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) = Unit
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
surface.release()
return true
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) = Unit
}