/* * Copyright (c) Double Symmetry GmbH * Commercial use requires a license. See https://rntp.dev/pricing */ package com.doublesymmetry.trackplayer import android.os.Handler import android.os.Looper import androidx.media3.session.MediaController import java.util.concurrent.FutureTask /** * Thread-safe wrapper that enforces all [MediaController] access happens on the main thread. * * The controller instance is private — callers must use [run], [sync], or [get] to interact * with it, all of which guarantee main-thread execution. */ class MainThreadMediaController( private val mainHandler: Handler = Handler(Looper.getMainLooper()) ) { private var controller: MediaController? = null /** Update the held controller. Must be called from the main thread. */ fun set(newController: MediaController?) { assertMainThread() controller = newController } /** Fire-and-forget: dispatches [block] to the main thread with the controller. */ fun run(block: (MediaController?) -> Unit) { if (isMainThread()) { block(controller) } else { mainHandler.post { block(controller) } } } /** Blocking: runs [block] on the main thread and returns the result. */ fun sync(block: (MediaController?) -> T): T { if (isMainThread()) { return block(controller) } val task = FutureTask { block(controller) } mainHandler.post(task) return task.get() } /** Direct access for code already on the main thread (e.g. Player.Listener callbacks). */ fun get(): MediaController? { assertMainThread() return controller } private fun isMainThread(): Boolean = Looper.myLooper() == Looper.getMainLooper() private fun assertMainThread() { check(isMainThread()) { "MediaController must be accessed from the main thread" } } }