androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
https://developer.android.com/media/media3
Apache License 2.0
1.71k stars 411 forks source link

RTSP: Support LoadErrorHandlingPolicy #435

Open sarimmehdi opened 1 year ago

sarimmehdi commented 1 year ago

Media3 Version

Media3 1.0.2

Devices that reproduce the issue

Custom Android device which is used to control a drone

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Yes

Reproduction steps

I am using the following code to stream RTSP video from a webcam to our custom Android device:

private const val TAG = "PlayerActivity"

 /**
 * A fullscreen activity to play audio or video streams.
 */
class PlayerActivity : AppCompatActivity() {

    private val playbackStateListener: Player.Listener = playbackStateListener()

     @UnstableApi
    private val onLiveStreamEndedListener = onLiveStreamEndedListener()

    private var player: ExoPlayer? = null

    private fun playbackStateListener() = object : Player.Listener {

        override fun onPlaybackStateChanged(playbackState: Int) {
            var stateString = "UNKNOWN_STATE             -"
            when (playbackState) {
                ExoPlayer.STATE_IDLE -> stateString = "ExoPlayer.STATE_IDLE      -"
                ExoPlayer.STATE_BUFFERING -> stateString = "ExoPlayer.STATE_BUFFERING -"
                ExoPlayer.STATE_READY -> stateString = "ExoPlayer.STATE_READY     -"
                ExoPlayer.STATE_ENDED -> {
                    stateString = "ExoPlayer.STATE_ENDED     -"
                }
            }
            Log.d(TAG, "changed state to $stateString")
        }
    }

    @OptIn(UnstableApi::class)
    private fun onLiveStreamEndedListener() = object : DefaultLoadErrorHandlingPolicy() {

        override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorHandlingPolicy.LoadErrorInfo): Long {
            return 1000
        }

        override fun getMinimumLoadableRetryCount(dataType: Int): Int {
            return Int.MAX_VALUE
        }
    }

    private val viewBinding by lazy(LazyThreadSafetyMode.NONE) {
        ActivityPlayerBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)
    }

    public override fun onStart() {
        super.onStart()
        initializePlayer()
    }

    public override fun onResume() {
        super.onResume()
        hideSystemUi()
        if (player == null) {
            initializePlayer()
        }
    }

    public override fun onStop() {
        super.onStop()
        releasePlayer()
    }

    @OptIn(UnstableApi::class)
    private fun initializePlayer() {
        val mediaSourceFactory = DefaultMediaSourceFactory(this)
            .setLoadErrorHandlingPolicy(onLiveStreamEndedListener)

        player = ExoPlayer.Builder(this)
            .setMediaSourceFactory(mediaSourceFactory)
            .build()
            .also { exoPlayer ->
                viewBinding.videoView.player = exoPlayer
                viewBinding.videoView.useController = false
                val mediaItem = MediaItem.Builder()
                    .setUri("rtsp://192.168.0.10:8554/H264Video")
                    .setMimeType(MimeTypes.APPLICATION_RTSP)
                    .setLiveConfiguration(MediaItem.LiveConfiguration.Builder().setMaxPlaybackSpeed(1.02f).build())
                    .build()
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.playWhenReady = true
                exoPlayer.addListener(playbackStateListener)
                exoPlayer.prepare()
            }
    }

    private fun releasePlayer() {
        player?.let { exoPlayer ->
            exoPlayer.removeListener(playbackStateListener)
            exoPlayer.release()
        }
        player = null
    }

    @SuppressLint("InlinedApi")
    private fun hideSystemUi() {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        WindowInsetsControllerCompat(window, viewBinding.videoView).let { controller ->
            controller.hide(WindowInsetsCompat.Type.systemBars())
            controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        }
    }
}

The live stream works when the camera is connected. If I disconnect the camera, the live stream stops as it should. However, when I reconnect the camera, the live stream doesn't resume even though I have declared a custom policy using the variable onLiveStreamEndedListener and set it for the exoplayer using the MediaSourceFactory.

Expected result

Livestream video should resume after the camera is reconnected

Actual result

Livestream video does not resume even after the camera is reconnected

Media

It is livestream from a custom camera.

Bug Report

icbaker commented 1 year ago

ExoPlayer's RTSP support does not currently support LoadErrorHandlingPolicy:

https://github.com/androidx/media/blob/2fc189d6a40f116bd54da69ab9a065219f6973e7/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaSource.java#L161-L166

distinctdan commented 11 months ago

Any movement on this? Right now the RTSP logic makes the player say that it's still playing after it gets disconnected, which makes it basically impossible to manually implement error handling. Error handling is essential for any non-trivial app.