--- a/Sources/AetherEngine/Audio/AudioOutput.swift
+++ b/Sources/AetherEngine/Audio/AudioOutput.swift
@@ -11,6 +11,62 @@
 
     private let lock = NSLock()
 
+    // Modified for Teevo (products/apple-media), 20 October 2026: the synchronizer's rate changes,
+    // the renderer's flushes and the video renderer's attach and detach wait on the audio server,
+    // which can take a second or more while it starts a queue (a multichannel route, eARC, a busy
+    // device). Asked for on the main actor, every pause, play, seek and sound-track change froze the
+    // interface while the picture played on (owner: "teevo player UI getting stuck a lot whilst
+    // playing larger files. video continue"). They now run on one serial queue, shared by every
+    // AudioOutput so a session's teardown stays ahead of the next session's setup: from the main
+    // thread without waiting, from any other thread waiting as before. Order is kept: every such call
+    // goes through the queue, `enqueue` waits behind calls still on it, `rate` answers what they
+    // set, and `settled()` lets a caller on the main actor wait without blocking it. Nothing on the
+    // queue holds `lock` while the server answers: the main thread takes it to hand calls over. The
+    // epoch a flush retires changes as it is handed over, with the count of calls still pending, so
+    // a buffer can't slip in between (DEC-106 kept that by holding `lock` across the flush).
+    // scripts/fetch-aetherengine.mjs applies this change.
+    private static let control = DispatchQueue(label: "engine.audio.control", qos: .userInteractive)
+    /// Calls handed to `control` and not yet done. Guarded by `lock`.
+    private var pendingControls = 0
+    /// The rate those calls leave the clock at. Guarded by `lock`.
+    private var intendedRate: Float = 0
+
+    /// `work` on the control queue, after `prelude` under `lock`: at once from the main thread,
+    /// waited for from any other.
+    private func perform(rate: Float? = nil, prelude: () -> Void = {}, _ work: @escaping @Sendable () -> Void) {
+        lock.lock()
+        prelude()
+        if let rate { intendedRate = rate }
+        pendingControls += 1
+        lock.unlock()
+        let done: @Sendable () -> Void = { [self] in
+            work()
+            lock.lock()
+            pendingControls -= 1
+            lock.unlock()
+        }
+        if Thread.isMainThread {
+            Self.control.async(execute: done)
+        } else {
+            Self.control.sync(execute: done)
+        }
+    }
+
+    /// Modified for Teevo: one made on the control queue, behind the last session's teardown:
+    /// making the renderer waits on the audio server too.
+    static func make() async -> AudioOutput {
+        await withCheckedContinuation { (made: CheckedContinuation<AudioOutput, Never>) in
+            control.async { made.resume(returning: AudioOutput()) }
+        }
+    }
+
+    /// Modified for Teevo: returns once every call handed to the control queue so far has run.
+    func settled() async {
+        await withCheckedContinuation { (finished: CheckedContinuation<Void, Never>) in
+            Self.control.async { finished.resume() }
+        }
+    }
+
     /// AE#464: the host's audio presentation offset, applied to every buffer on its way into the
     /// renderer. Guarded because the host writes it from the main actor while the demux thread reads
     /// it in `enqueue`.
@@ -53,7 +109,11 @@
     /// differently here and nowhere else, which is the whole reason AE#549 needs it (see
     /// `RendererClockResume`).
     var rate: Float {
-        synchronizer.rate
+        // Modified for Teevo: while calls are still on the control queue, the rate they set.
+        lock.lock()
+        let pending = pendingControls > 0 ? intendedRate : nil
+        lock.unlock()
+        return pending ?? synchronizer.rate
     }
 
     /// AE#549: how often this renderer has flushed itself, for the diagnostic line.
@@ -118,7 +178,9 @@
     /// FigVideoQueueRemote err=-12080 after the first enqueue. Taken as the renderer rather than read off the
     /// layer here, because the layer is main-actor isolated in the 27 SDKs and this runs off it (#351).
     func attachVideoRenderer(_ videoRenderer: AVSampleBufferVideoRenderer) {
-        synchronizer.addRenderer(videoRenderer)
+        // Modified for Teevo: on the control queue, behind the last session's detach.
+        nonisolated(unsafe) let videoRenderer = videoRenderer
+        perform { [self] in synchronizer.addRenderer(videoRenderer) }
     }
 
     /// Remove the video renderer and block until removal completes. The synchronizer detaches asynchronously;
@@ -127,16 +189,21 @@
     /// err=-12080 and the display layer stops rendering (audio keeps going). The semaphore wait (sub-100ms) makes
     /// the handoff deterministic.
     func detachVideoRenderer(_ videoRenderer: AVSampleBufferVideoRenderer) {
-        let semaphore = DispatchSemaphore(value: 0)
-        synchronizer.removeRenderer(videoRenderer, at: synchronizer.currentTime()) { _ in
-            semaphore.signal()
-        }
-        let result = semaphore.wait(timeout: .now() + .seconds(1))
-        #if DEBUG
-        if result == .timedOut {
-            EngineLog.emit("[AudioOutput] detachVideoRenderer: timed out waiting for synchronizer removal", category: .swPlayback)
+        // Modified for Teevo: the wait is the control queue's, so the next session's attach, behind
+        // it on the same queue, still finds the renderer released, and the main thread doesn't wait.
+        nonisolated(unsafe) let videoRenderer = videoRenderer
+        perform { [self] in
+            let semaphore = DispatchSemaphore(value: 0)
+            synchronizer.removeRenderer(videoRenderer, at: synchronizer.currentTime()) { _ in
+                semaphore.signal()
+            }
+            let result = semaphore.wait(timeout: .now() + .seconds(1))
+            #if DEBUG
+            if result == .timedOut {
+                EngineLog.emit("[AudioOutput] detachVideoRenderer: timed out waiting for synchronizer removal", category: .swPlayback)
+            }
+            #endif
         }
-        #endif
     }
 
     var volume: Float {
@@ -146,16 +213,22 @@
 
     /// Set playback speed (0.5-2.0). Hosts own rate state (lastRate/pausedByHost); this object is stateless about it.
     func setRate(_ rate: Float) {
-        let at = synchronizer.currentTime()
-        EngineLog.emit("[AudioOutput] setRate \(rate) at t=\(String(format: "%.3f", at.seconds))", category: .swPlayback)
-        synchronizer.setRate(rate, time: at)
+        // Modified for Teevo: on the control queue, from the clock's time when it runs there.
+        perform(rate: rate) { [self] in
+            let at = synchronizer.currentTime()
+            EngineLog.emit("[AudioOutput] setRate \(rate) at t=\(String(format: "%.3f", at.seconds))", category: .swPlayback)
+            synchronizer.setRate(rate, time: at)
+        }
     }
 
     /// Pause audio (and the master clock). Hosts resume via setRate (pausedByHost pattern); deliberately no resume() here.
     func pause() {
-        let at = synchronizer.currentTime()
-        EngineLog.emit("[AudioOutput] pause at t=\(String(format: "%.3f", at.seconds))", category: .swPlayback)
-        synchronizer.setRate(0.0, time: at)
+        // Modified for Teevo: on the control queue, at the clock's time when it runs there.
+        perform(rate: 0) { [self] in
+            let at = synchronizer.currentTime()
+            EngineLog.emit("[AudioOutput] pause at t=\(String(format: "%.3f", at.seconds))", category: .swPlayback)
+            synchronizer.setRate(0.0, time: at)
+        }
     }
 
     /// AE#464: set the audio presentation offset. Positive presents audio later than video, which on
@@ -184,7 +257,14 @@
     /// synchronizer clock, i.e. against the source axis too). Only the renderer sees the shift.
     @discardableResult
     func enqueue(sampleBuffer: CMSampleBuffer, ifEpoch epoch: UInt64? = nil) -> Bool {
+        // Modified for Teevo: behind any flush, stop or clock change still on the control queue, so a
+        // buffer meant for after it never lands before it (and is thrown away by it).
         lock.lock()
+        while pendingControls > 0 {
+            lock.unlock()
+            Self.control.sync {}
+            lock.lock()
+        }
         if let epoch, epoch != _epoch {
             lock.unlock()
             return false
@@ -290,27 +370,26 @@
 
     /// Flush the audio renderer (call on seek).
     func flush() {
-        lock.lock()
-        defer { lock.unlock() }
-        _epoch &+= 1
-        renderer.flush()
+        // Modified for Teevo: the epoch retires at once, the renderer flushes on the control queue.
+        perform(prelude: { _epoch &+= 1 }) { [self] in renderer.flush() }
     }
 
     func stop() {
-        lock.lock()
-        defer { lock.unlock() }
-        _epoch &+= 1
-        synchronizer.setRate(0.0, time: .zero)
-        renderer.flush()
+        // Modified for Teevo: as `flush`.
+        perform(rate: 0, prelude: { _epoch &+= 1 }) { [self] in
+            synchronizer.setRate(0.0, time: .zero)
+            renderer.flush()
+        }
     }
 
     /// Atomically jump the master clock to a time and resume at a rate. The ONLY way the clock is (re)anchored:
     /// demux loops call it once on the first decoded packet, seek paths call it directly. Avoids the
     /// falling-through-time races that pause -> flush -> setRate would expose.
     func seekClock(to time: CMTime, rate: Float) {
-        lock.lock()
-        defer { lock.unlock() }
-        EngineLog.emit("[AudioOutput] seekClock to=\(String(format: "%.3f", time.seconds)) rate=\(rate)", category: .swPlayback)
-        synchronizer.setRate(rate, time: time)
+        // Modified for Teevo: on the control queue.
+        perform(rate: rate) { [self] in
+            EngineLog.emit("[AudioOutput] seekClock to=\(String(format: "%.3f", time.seconds)) rate=\(rate)", category: .swPlayback)
+            synchronizer.setRate(rate, time: time)
+        }
     }
 }
--- a/Sources/AetherEngine/Audio/AudioPlaybackHost.swift
+++ b/Sources/AetherEngine/Audio/AudioPlaybackHost.swift
@@ -176,7 +176,10 @@
         try aDec.open(stream: aStream)
         self.audioDecoder = aDec
         self.audioStreamIndex = resolvedAudioIdx
-        self.audioOutput = AudioOutput()
+        // Modified for Teevo (products/apple-media), 20 October 2026: made off the main actor, as
+        // making the renderer waits on the audio server (AudioOutput.make). scripts/fetch-aetherengine.mjs
+        // applies this change.
+        self.audioOutput = await AudioOutput.make()
         self.audioOutput?.volume = volume
 
         if let start = startPosition, start.isFinite, start > 0 {
--- a/Sources/AetherEngine/Native/SoftwarePlaybackHost.swift
+++ b/Sources/AetherEngine/Native/SoftwarePlaybackHost.swift
@@ -979,7 +979,9 @@
         self.splitDisplaySetSubtitleStreamIndices = dem.splitDisplaySetSubtitleStreamIndices()
 
         // AudioOutput owns the AVSampleBufferRenderSynchronizer (master clock). Created unconditionally: video-only previously got no clock (frozen frame, currentTime=0). Layer attached in play() after the engine hangs it in the view hierarchy (attaching free-floating fails FigVideoQueueRemote -12080 on tvOS 26+).
-        self.audioOutput = AudioOutput()
+        // Modified for Teevo (products/apple-media), 20 October 2026: made off the main actor, as
+        // making the renderer waits on the audio server (AudioOutput.make).
+        self.audioOutput = await AudioOutput.make()
         self.audioOutput?.volume = volume
         self.audioOutput?.setPresentationOffset(seconds: audioDelaySeconds)   // AE#464
 
@@ -1448,6 +1450,11 @@
         }
         // Arm now so the demux loop doesn't re-arm at stale initialClockTime (a pre-first-audio seek snapped back to session start without this).
         clockArmed = true
+        // Modified for Teevo (products/apple-media), 20 October 2026: the flush and the anchor above
+        // run on AudioOutput's control queue so the main actor never waits on the audio server; the
+        // loop is released once they have, without blocking it. scripts/fetch-aetherengine.mjs
+        // applies this change.
+        await audioOutput?.settled()
         // The source stands at the target and the clock is anchored on it: everything the loop
         // reads from here belongs to this position. Closing the window releases the loop.
         noteSeekSettled(generation)
