Player is accessed on the wrong thread.
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 bugreportto [email protected] after filing this issue.
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.
https://exoplayer.dev/hello-world.html#a-note-on-threading This site can’t be reached
Works for me. Please try again.
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"?
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.
Can you explain with code exmaple?
@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.
@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)
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)