ExoPlayer icon indicating copy to clipboard operation
ExoPlayer copied to clipboard

Player is accessed on the wrong thread.

Open ahmadbajwa8282 opened this issue 3 years ago • 8 comments

ExoPlayer Version

2.17.1

Devices that reproduce the issue

Vivo V2027 Android 12 (SDK 31) Vivo V2130 Android 12 (SDK 31) Vivo V2132 Android 12 (SDK 31) Vivo X80 Android 12 (SDK 31) Vivo V2146 Android 11 (SDK 30)

Devices that do not reproduce the issue

POCO X3 Android 12 (SDK 31) Samsung A32 Android 11 (SDK 30) Infinix Note 7 Android 10 (SDK 29) Sony Xperia ZX3 Android 10 (SDK 29)

Reproducible in the demo app?

No

Reproduction steps

none

Expected result

don't crash

Actual result

exception.class.missing._Unknown_: java.lang.IllegalStateException	Player is accessed on the wrong thread.
  at android.app.ActivityThread.handleStopService (ActivityThread.java:5021)
  at android.app.ActivityThread.access$2400 (ActivityThread.java:284)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2327)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:233)
  at android.os.Looper.loop (Looper.java:334)
  at android.app.ActivityThread.main (ActivityThread.java:8399)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:582)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1068)
Caused by: java.lang.IllegalStateException: 
  at com.google.android.exoplayer2.ExoPlayerImpl.verifyApplicationThread (ExoPlayerImpl.java)
  at com.google.android.exoplayer2.ExoPlayerImpl.getCurrentTimeline (ExoPlayerImpl.java)
  at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.K (DefaultAnalyticsCollector.java:25)
  at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.generateEventTime (DefaultAnalyticsCollector.java)
  at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.generateReadingMediaPeriodEventTime (DefaultAnalyticsCollector.java)
  at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.onSurfaceSizeChanged (DefaultAnalyticsCollector.java)
  at com.google.android.exoplayer2.ExoPlayerImpl.lambda$maybeNotifySurfaceSizeChanged$28 (ExoPlayerImpl.java)
  at com.google.android.exoplayer2.ExoPlayerImpl$$InternalSyntheticLambda$0$2d4737b778d7b8503f2f9305c33da9d50db855343d46ce9f1ebd9b998f070724$0.invoke (ExoPlayerImpl.java)
  at com.google.android.exoplayer2.util.ListenerSet$ListenerHolder.invoke (ListenerSet.java)
  at com.google.android.exoplayer2.util.ListenerSet.lambda$queueEvent$0 (ListenerSet.java)
  at com.google.android.exoplayer2.util.ListenerSet$$InternalSyntheticLambda$1$4aa6d67c50ced20c53fd0cecd314136314e77eb5e787a34f9137864aa48c8e5a$0.run (ListenerSet.java)
  at com.google.android.exoplayer2.util.ListenerSet.flushEvents (ListenerSet.java:66)
  at com.google.android.exoplayer2.util.ListenerSet.sendEvent (ListenerSet.java)
  at com.google.android.exoplayer2.ExoPlayerImpl.maybeNotifySurfaceSizeChanged (ExoPlayerImpl.java)
  at com.google.android.exoplayer2.ExoPlayerImpl.access$1800 (ExoPlayerImpl.java)
  at com.google.android.exoplayer2.ExoPlayerImpl$ComponentListener.surfaceDestroyed (ExoPlayerImpl.java)
  at android.service.wallpaper.WallpaperService$Engine.reportSurfaceDestroyed (WallpaperService.java:1945)
  at android.service.wallpaper.WallpaperService$Engine.detach (WallpaperService.java:1971)
  at android.service.wallpaper.WallpaperService.onDestroy (WallpaperService.java:2400)
  at android.app.ActivityThread.handleStopService (ActivityThread.java:5001)
  at android.app.ActivityThread.access$2400 (ActivityThread.java:284)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2327)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:233)
  at android.os.Looper.loop (Looper.java:334)
  at android.app.ActivityThread.main (ActivityThread.java:8399)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:582)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1068)

Code

open class MovieLiveWallpaperService : WallpaperService() {
    private var contexts: Activity? = null

    enum class Mode {
        FIT_CENTER,
        FIT_START,
        FIT_END,
        FIT_XY,
        CENTER_CROP
    }

    private val mode: Mode
        get() = AppSharedPreferences
            .getStringDefValue(this,"scale_type", Mode.CENTER_CROP.name).let(
                Mode::valueOf)

    fun setToWallPaper(activity: Activity) {
        Utility.sendEvent("Live_Engine")
        contexts = activity
        try {
            val intent = Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER)
            intent.putExtra(
                WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, ComponentName(
                    activity,
                    MovieLiveWallpaperService::class.java
                )
            )
            activity.startActivity(intent)
        } catch (ignore: ActivityNotFoundException) {
        }
    }

    override fun onCreateEngine(): Engine = GlEngine()

    protected fun initExoMediaPlayer(): ExoPlayer {
        val player = this.let { ExoPlayer.Builder(it).build() }
        player.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
        player.playWhenReady = true
        player.repeatMode = Player.REPEAT_MODE_ONE
        player.volume = 0f
        if (mode == Mode.CENTER_CROP) {
            player.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
        } else {
            player.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
        }
        if (mode == Mode.FIT_CENTER) {
            player.addListener(object : Player.Listener {
                override fun onPlayerError(error: PlaybackException) {
                    super.onPlayerError(error)
                    contexts?.let {
                        Toast.makeText(
                            it,
                            "${error.message} Something Went Wrong!",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
            })

        }
        return player
    }

    inner class GlEngine : WallpaperService.Engine() {

        private var exoMediaPlayer = initExoMediaPlayer()

        override fun onVisibilityChanged(visible: Boolean) {
            exoMediaPlayer.playWhenReady = visible
            if (visible) onResume()
            else onPause()
        }

        @Synchronized
        override fun onSurfaceCreated(holder: SurfaceHolder) {
            kotlin.runCatching {
                if (mode == Mode.FIT_CENTER) {
                    exoMediaPlayer.setVideoSurface(holder.surface)
                } else {
                    exoMediaPlayer.setVideoSurfaceHolder(holder)
                }
                val videoUri = [email protected] {
                    AppSharedPreferences.getString(
                        it,
                        "absolutePath"
                    )
                }
                val isLocalVideo =
                    AppSharedPreferences.getBool(this@MovieLiveWallpaperService, "isLocalVideo")
                when {
                    isLocalVideo -> exoMediaPlayer.setMediaSource(
                        buildMediaSourceForLocal(
                            Uri.parse(
                                videoUri
                            )
                        )
                    )
                    !isLocalVideo -> exoMediaPlayer.setMediaSource(
                        buildMediaSource(
                            Uri.parse(
                                videoUri
                            )
                        )
                    )
                }

                exoMediaPlayer.prepare()
                exoMediaPlayer.play()
            }
        }

        private fun buildMediaSource(uri: Uri): MediaSource {
            val userAgent = "exoplayer-liveWallpaper"
            val media_item: MediaItem = MediaItem.fromUri(uri)
            val cache_datasource_factory = CacheDataSource.Factory()
                .setCache(EngineVideoCache.getInstance(this@MovieLiveWallpaperService))
                .setUpstreamDataSourceFactory(
                    DefaultHttpDataSource.Factory()
                        .setUserAgent(userAgent)
                )
            return ProgressiveMediaSource.Factory(cache_datasource_factory)
                .createMediaSource(media_item)
        }

        private fun buildMediaSourceForLocal(uri: Uri): MediaSource {
            val media_item: MediaItem = MediaItem.fromUri(uri)
            val dataSource = CacheDataSource.Factory()
                    .setCache(EngineVideoCache.getInstance(this@MovieLiveWallpaperService))
                    .setUpstreamDataSourceFactory(DefaultDataSource.Factory(this@MovieLiveWallpaperService))
            return  ProgressiveMediaSource.Factory(dataSource)
                .createMediaSource(media_item)
        }

        override fun onSurfaceChanged(
            holder: SurfaceHolder?,
            format: Int,
            width: Int,
            height: Int
        ) {
        }

        @Synchronized
        override fun onSurfaceDestroyed(holder: SurfaceHolder?) {
        }

        override fun onDestroy() {
            kotlin.runCatching {
                super.onDestroy()
                exoMediaPlayer.stop()
                exoMediaPlayer.release()
            }
        }

        private fun onPause() {
            kotlin.runCatching {
                exoMediaPlayer.pause()
            }
        }

        private fun onResume() {
            kotlin.runCatching {
                exoMediaPlayer.play()
            }
        }
    }
}

Bug Report

  • [ ] You will email the zip file produced by adb bugreport to [email protected] after filing this issue.

ahmadbajwa8282 avatar Jun 13 '22 11:06 ahmadbajwa8282

This is something you need to fix in your app I think.

Can you check out this section from the Developer Guide that explains the issue you need to solve.

marcbaechinger avatar Jun 14 '22 16:06 marcbaechinger

https://exoplayer.dev/hello-world.html#a-note-on-threading This site can’t be reached

ahmadbajwa8282 avatar Jun 15 '22 04:06 ahmadbajwa8282

Works for me. Please try again.

marcbaechinger avatar Jun 15 '22 08:06 marcbaechinger

This doc says "ExoPlayer instances must be accessed from a single application thread." But I wanted to use Exoplayer within the wallpaper service. Can you help me out with how I can use it without having this exception "Player is accessed on the wrong thread"?

ahmadbajwa8282 avatar Jun 16 '22 05:06 ahmadbajwa8282

Like you quote: All methods need to be called on that same thread on which you have created the player. So if you create the player in your initExoMediaPlayer then you need to make sure to call all methods on that thread that is creating the service.

You need to make sure that the player is not accessed from another thread. If this happens you need to create a Handler at the code site where you create the player and then post a message to that Handler when calling a player method to be on the same thread.

marcbaechinger avatar Jun 16 '22 08:06 marcbaechinger

Can you explain with code exmaple?

ahmadbajwa8282 avatar Jun 16 '22 09:06 ahmadbajwa8282

@ahmadbajwa8282 ExoPlayer devs aren't there to help you learn coding. They already provide you with such an extensible amazing media player that ExoPlayer is. You need to learn what threads are and how to use them.

Processes and Threads - Developer Google

For example, if you initialize ExoPlayer (in other words 'build it') on Thread-4, then you need to manipulate the player only on that thread. Usually, ExoPlayer is created on the main thread and you can only call it from the main thread.

yuroyami avatar Jun 25 '22 19:06 yuroyami

@marcbaechinger This is very much an ExoPlayer issue rather than an app issue, ExoPlayer subscribes to the events from the SurfaceHolder inside of ExoPlayerImpl and the SurfaceHolder subscribes to the SurfaceView, the events that are triggered from the SurfaceView / SurfaceHolder are always on the main thread (as it's an Android view). If ExoPlayer is running on a different thread / looper, when the analytics collector then tries to find the media period event time, the generateReadingMediaPeriodEventTime method tries to access the player timeline, which checks the thread and throws an IllegalStateException.

It's a fairly simple work around in ExoPlayerImpl by setting up a handler on the applicationLooper and running listeners.sendEvent on that handler inside of maybeNotifySurfaceSizeChanged. To fix it outside of ExoPlayer requires creating a custom SurfaceHolder class that listens to the callbacks and forwards them on the correct thread back to ExoPlayer.

java.lang.IllegalStateException: Player is accessed on the wrong thread.
Current thread: 'main'
Expected thread: 'ExoPlayer'
See https://exoplayer.dev/issues/player-accessed-on-wrong-thread
  at com.google.android.exoplayer2.ExoPlayerImpl.verifyApplicationThread(ExoPlayerImpl.java:2600)
  at com.google.android.exoplayer2.ExoPlayerImpl.getCurrentTimeline(ExoPlayerImpl.java:1194)
  at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.generateEventTime(DefaultAnalyticsCollector.java:909)
  at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.generateEventTime(DefaultAnalyticsCollector.java:965)
  at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.generateReadingMediaPeriodEventTime(DefaultAnalyticsCollector.java:973)
  at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.onSurfaceSizeChanged(DefaultAnalyticsCollector.java:374)
  at com.google.android.exoplayer2.ExoPlayerImpl.lambda$maybeNotifySurfaceSizeChanged$27(ExoPlayerImpl.java:2529)
  at com.google.android.exoplayer2.ExoPlayerImpl$$ExternalSyntheticLambda23.invoke(Unknown Source:6)
  at com.google.android.exoplayer2.util.ListenerSet$ListenerHolder.invoke(ListenerSet.java:281)
  at com.google.android.exoplayer2.util.ListenerSet.lambda$queueEvent$0(ListenerSet.java:190)
  at com.google.android.exoplayer2.util.ListenerSet$$ExternalSyntheticLambda1.run(Unknown Source:6)
  at com.google.android.exoplayer2.util.ListenerSet.flushEvents(ListenerSet.java:211)
  at com.google.android.exoplayer2.util.ListenerSet.sendEvent(ListenerSet.java:226)
  at com.google.android.exoplayer2.ExoPlayerImpl.maybeNotifySurfaceSizeChanged(ExoPlayerImpl.java:2528)
  at com.google.android.exoplayer2.ExoPlayerImpl.access$1900(ExoPlayerImpl.java:104)
  at com.google.android.exoplayer2.ExoPlayerImpl$ComponentListener.surfaceChanged(ExoPlayerImpl.java:2894)
  at android.view.SurfaceView.updateSurface(SurfaceView.java:1177)
  at android.view.SurfaceView.lambda$new$0$SurfaceView(SurfaceView.java:173)
  at android.view.-$$Lambda$SurfaceView$w68OV7dB_zKVNsA-r0IrAUtyWas.onPreDraw(Unknown Source:2)
  at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:1093)
  at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3203)
  at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2018)
  at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8467)
  at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
  at android.view.Choreographer.doCallbacks(Choreographer.java:796)
  at android.view.Choreographer.doFrame(Choreographer.java:731)
  at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
  at android.os.Handler.handleCallback(Handler.java:938)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:223)
  at android.app.ActivityThread.main(ActivityThread.java:7668)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

sixones avatar Aug 25 '22 10:08 sixones

I confirm @sixones analysis and can confirm this is reproduced on 2.18.1 release. Here is my callstack, but I don't think it brings additionnal informations. I tryed to disable analyticsCollector with: setAnalyticsCollector(null) or with player.getAnalyticsCollector().setPlayer(null, null); but unfortunately it doesn't seem possible.

11-15 22:37:14.353  7819  7819 E SurfaceView: Exception configuring surface
11-15 22:37:14.353  7819  7819 E SurfaceView: java.lang.IllegalStateException: Player is accessed on the wrong thread.
11-15 22:37:14.353  7819  7819 E SurfaceView: Current thread: 'main'
11-15 22:37:14.353  7819  7819 E SurfaceView: Expected thread: 'RNVLooper_0'
11-15 22:37:14.353  7819  7819 E SurfaceView: See https://exoplayer.dev/issues/player-accessed-on-wrong-thread
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.ExoPlayerImpl.verifyApplicationThread(ExoPlayerImpl.java:2597)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.ExoPlayerImpl.getCurrentMediaItemIndex(ExoPlayerImpl.java:1010)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.generateEventTime(DefaultAnalyticsCollector.java:958)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.generateReadingMediaPeriodEventTime(DefaultAnalyticsCollector.java:973)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector.onSurfaceSizeChanged(DefaultAnalyticsCollector.java:374)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.ExoPlayerImpl.lambda$maybeNotifySurfaceSizeChanged$27(ExoPlayerImpl.java:2526)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.-$$Lambda$ExoPlayerImpl$LEzBBscn-_l3k3Wp16Yiv1NbVFo.invoke(Unknown Source:6)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.util.ListenerSet$ListenerHolder.invoke(ListenerSet.java:281)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.util.ListenerSet.lambda$queueEvent$0(ListenerSet.java:190)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.util.-$$Lambda$ListenerSet$NbKDn9xtItiyMgYZmjIx_Sv1FFQ.run(Unknown Source:6)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.util.ListenerSet.flushEvents(ListenerSet.java:211)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.util.ListenerSet.sendEvent(ListenerSet.java:226)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.ExoPlayerImpl.maybeNotifySurfaceSizeChanged(ExoPlayerImpl.java:2525)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.ExoPlayerImpl.access$1800(ExoPlayerImpl.java:104)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.google.android.exoplayer2.ExoPlayerImpl$ComponentListener.surfaceChanged(ExoPlayerImpl.java:2882)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.SurfaceView.updateSurface(SurfaceView.java:770)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.SurfaceView$2.onPreDraw(SurfaceView.java:155)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:1088)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2745)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1721)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7598)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.Choreographer.doCallbacks(Choreographer.java:790)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.Choreographer.doFrame(Choreographer.java:725)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.os.Handler.handleCallback(Handler.java:883)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.os.Handler.dispatchMessage(Handler.java:100)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.os.Looper.loop(Looper.java:214)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at android.app.ActivityThread.main(ActivityThread.java:7356)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at java.lang.reflect.Method.invoke(Native Method)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
11-15 22:37:14.353  7819  7819 E SurfaceView: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

freeboub avatar Nov 15 '22 22:11 freeboub