--- a/Sources/AetherEngine/AetherEngine+Loading.swift
+++ b/Sources/AetherEngine/AetherEngine+Loading.swift
@@ -556,6 +556,7 @@
                   // AE#154: a VOD resume anchor seeks; nil keeps the live no-initial-seek contract.
                   skipInitialSeek: startPosition == nil,
                   inPlaceSwap: inPlaceHandover,
+                  handOverOffMain: true,
                   contract: .init(
                       isLive: options.isLive,
                       // AE#440: the same join tail exists where AVPlayer owns the buffer; the engine only
@@ -576,6 +577,9 @@
                       readinessDeadline: RemoteHLSReadinessDeadline.defaultBudgetSeconds,
                       // No probe lists this route's audio, so the item's own tracks are the list.
                       readsBackAudioTracks: true))
+        // Modified for Teevo: as on the loopback route, the hand-over runs off the main actor.
+        await host.itemHandedOver()
+        try checkLoadCurrent(bypassGeneration)
 
         attachRemoteHLSCueClock(host: host, expectedGeneration: bypassGeneration)
 
@@ -1792,6 +1796,7 @@
                   skipInitialSeek: LiveReloadPolicy.skipInitialSeek(
                       isLive: isLive, isRejoin: liveRejoin),
                   inPlaceSwap: inPlaceHandover,
+                  handOverOffMain: true,
                   contract: .init(
                       isLive: isLive,
                       // AE#440: the join tail, opt-in. The host itself gates this on `isLive`.
@@ -1800,6 +1805,10 @@
                       // the HDMI route cannot, because Atmos passthrough and a stereo LPCM route
                       // report the same two channels.
                       audioIsAtmosStreamCopy: nativeVideoSession?.audioIsAtmosStreamCopy == true))
+        // Modified for Teevo (products/apple-media), 20 October 2026: the host hands the item over off
+        // the main actor; what follows reads `currentItem`, so it waits for that without blocking.
+        await host.itemHandedOver()
+        try checkLoadCurrent(generation)
         forceNativeLegibleDeselectedUntilHostSelects()
         // Sodalite#175: from the session's own pick, since a track-switch reload has no active index yet.
         let sessionAudioPick = nativeVideoSession.map(\.activeAudioSourceStreamIndex).flatMap { $0 >= 0 ? Int($0) : nil }
--- a/Sources/AetherEngine/AetherEngine.swift
+++ b/Sources/AetherEngine/AetherEngine.swift
@@ -6376,8 +6376,12 @@
             guard !Task.isCancelled, self.loadGeneration == gen else { return }
             // currentVideoFrameRate is 0 on audio tracks and while paused, so the first non-zero entry is
             // the video track's own measure of how many frames the player is actually putting on screen.
-            let playerRate = self.currentAVPlayer?.currentItem?.tracks
-                .lazy.map(\.currentVideoFrameRate).first { $0 > 0 }
+            // Modified for Teevo: read off the main actor; an item's tracks wait on the media server.
+            let item = self.currentAVPlayer?.currentItem
+            let playerRate = await NativeAVPlayerHost.offMain {
+                item?.tracks.lazy.map(\.currentVideoFrameRate).first { $0 > 0 }
+            }
+            guard !Task.isCancelled, self.loadGeneration == gen else { return }
             func fmt(_ value: Double?) -> String {
                 value.map { String(format: "%.3f", $0) } ?? "n/a"
             }
@@ -7174,7 +7178,9 @@
             // the successor otherwise.
             nativeHost?.prepareForItemHandover()
         } else {
-            nativeHost?.tearDown()
+            // Modified for Teevo (products/apple-media), 20 October 2026: the item leaves Apple's player
+            // off the main actor (patch 0004); the next load hands over behind it, in order.
+            nativeHost?.tearDown(handOverOffMain: true)
         }
         if !keepNativeHost {
             nativeHost = nil
--- a/Sources/AetherEngine/Native/NativeAVPlayerHost.swift
+++ b/Sources/AetherEngine/Native/NativeAVPlayerHost.swift
@@ -25,6 +25,12 @@
     let targetIsland: Double
 }
 
+/// Modified for Teevo: AVFoundation's objects carried to and from a load off the main actor.
+private struct OffMainValue<Value>: @unchecked Sendable {
+    let value: Value
+    init(_ value: Value) { self.value = value }
+}
+
 @MainActor
 final class NativeAVPlayerHost {
 
@@ -246,6 +252,66 @@
 
     let avPlayer: AVPlayer
 
+    // Modified for Teevo (products/apple-media), 20 October 2026: handing AVPlayer an item
+    // (`replaceCurrentItem`, the next one or nil) and reading an item's tracks back both wait on
+    // Apple's media server, which can take a second while it stops the outgoing item's sound or
+    // builds the next. On the main actor each sound-track change on the loopback route froze the
+    // interface while the picture played on (owner: "teevo player UI getting stuck a lot whilst
+    // playing larger files"). The engine's loads and its teardown between them (`handOverOffMain`)
+    // now hand over on one serial queue, in the order asked, without the main actor waiting: the
+    // mount seek follows its own hand-over there, and `itemHandedOver()` lets the engine wait for
+    // them without blocking before it reads `currentItem` again. Any other hand-over (an in-place
+    // swap, whose callers read the item straight after) is made on the main actor as before,
+    // unless one is queued, so the order holds. Track reads go through `offMain`.
+    // scripts/fetch-aetherengine.mjs applies this change.
+    private static let handOvers = DispatchQueue(label: "engine.native.handover", qos: .userInteractive)
+    /// Hand-overs on `handOvers` not yet run.
+    private var handOversPending = 0
+
+    /// `item` to the player, then `then` with it: on `handOvers` when `offMain` or while one is
+    /// queued there (so the order holds), at once otherwise, as before.
+    private func handOver(_ item: AVPlayerItem?, offMain: Bool, then: (@Sendable (AVPlayer) -> Void)? = nil) {
+        let player = avPlayer
+        guard offMain || handOversPending > 0 else {
+            player.replaceCurrentItem(with: item)
+            then?(player)
+            return
+        }
+        handOversPending += 1
+        Self.handOvers.async { [weak self] in
+            CATransaction.begin()
+            player.replaceCurrentItem(with: item)
+            CATransaction.commit()
+            then?(player)
+            Task { @MainActor in self?.handOversPending -= 1 }
+        }
+    }
+
+    /// Modified for Teevo: returns once every hand-over asked for so far has run.
+    func itemHandedOver() async {
+        await withCheckedContinuation { (done: CheckedContinuation<Void, Never>) in
+            Self.handOvers.async { done.resume() }
+        }
+    }
+
+    /// Modified for Teevo: a track's property, loaded off the main actor. A playback item's track
+    /// answers `load` by asking the item's queue synchronously (b18-crashes sampled
+    /// `load(.formatDescriptions)` in publishDetectedVideoFormat holding the main thread 1.2 s), so
+    /// the main actor never asks it itself.
+    static func trackLoad<T>(_ property: AVAsyncProperty<AVAssetTrack, T>, of track: AVAssetTrack) async -> T? {
+        let asked = OffMainValue((track, property))
+        return await Task.detached(priority: .userInitiated) {
+            OffMainValue(try? await asked.value.0.load(asked.value.1))
+        }.value.value
+    }
+
+    /// Modified for Teevo: a read that waits on the media server (an item's `tracks`), off the main actor.
+    nonisolated static func offMain<T: Sendable>(_ read: @escaping @Sendable () -> T) async -> T {
+        await withCheckedContinuation { (done: CheckedContinuation<T, Never>) in
+            DispatchQueue.global(qos: .userInitiated).async { done.resume(returning: read()) }
+        }
+    }
+
     // MARK: - Private state
 
     private var playerItem: AVPlayerItem?
@@ -457,10 +523,12 @@
     /// after an in-PiP recovery reload) and the pause bounces transport for nothing. The swap
     /// keeps transport intent, clocks and the old item alive until replaceCurrentItem hands
     /// AVPlayer the fresh one.
+    /// `handOverOffMain` (Modified for Teevo): the hand-over and the mount seek run on `handOvers`;
+    /// the caller awaits `itemHandedOver()` before it reads `currentItem`.
     func load(url: URL, startPosition: Double?, perFrameHDR: Bool = true, skipInitialSeek: Bool = false,
-              inPlaceSwap: Bool = false, contract: SessionLoadContract) {
+              inPlaceSwap: Bool = false, handOverOffMain: Bool = false, contract: SessionLoadContract) {
         let outgoingDiagnostics = inPlaceSwap ? itemDiagnostics : nil
-        unloadCurrentItem(inPlaceSwap: inPlaceSwap)
+        unloadCurrentItem(inPlaceSwap: inPlaceSwap, handOverOffMain: handOverOffMain)
 
         self.sessionContract = contract
         mountedStartPosition = skipInitialSeek ? nil : (startPosition ?? 0)
@@ -620,6 +688,8 @@
             let nsErr = item.error as NSError?
             let errSuffix = nsErr.map { " err=\($0.domain)/\($0.code) '\($0.localizedDescription)'" } ?? ""
             EngineLog.emit("[NativeAVPlayerHost] #\(sid) item.status=\(statusStr)\(errSuffix)", category: .engine)
+            // Modified for Teevo: read here, on the item's queue, rather than on the main actor below.
+            let readyDuration = item.status == .readyToPlay ? item.duration.seconds : 0
 
             // On .failed: dump track FourCCs (hev1 vs hvc1 rejection, dvhe vs dvh1) and full NSError chain.
             // On .readyToPlay: dump audio CMAudioFormatDescription (channel layout tag diagnoses FLAC-bridge downmix vs route downmix).
@@ -646,7 +716,7 @@
                 guard let self, self.sessionID == sid else { return }
                 switch item.status {
                 case .readyToPlay:
-                    self.duration = item.duration.seconds.isFinite ? item.duration.seconds : 0
+                    self.duration = readyDuration.isFinite ? readyDuration : 0
                     self.isReady = true
                     // Re-assert play() if the replaceCurrentItem swap swallowed it (playIntent latch).
                     if self.playIntent, self.avPlayer.timeControlStatus == .paused {
@@ -860,7 +930,34 @@
             }
         }
 
-        avPlayer.replaceCurrentItem(with: item)
+        // Modified for Teevo: the mount seek follows the hand-over, wherever that runs.
+        var mountSeek: (@Sendable (AVPlayer) -> Void)?
+        if !skipInitialSeek {
+            // AE#509: name the seek and the axis it is spent on. This is the only unconditional
+            // reposition of a fresh item, it happens before the item can answer for itself, and
+            // nothing logged it: a session parked at an unreachable position looked exactly like a
+            // session that placed nothing. `startPosition` is an ITEM-axis anchor, while a live
+            // host only ever sees the published clock (item + shift), so a live anchor that is not
+            // nil is the one shape where those two axes can be confused.
+            EngineLog.emit(
+                "[NativeAVPlayerHost] #\(sid) mount seek: item axis "
+                + String(format: "%.2f", startPosition ?? 0) + "s "
+                + "(startPosition=\(startPosition.map { String(format: "%.2f", $0) } ?? "nil"), "
+                + "live=\(contract.isLive))",
+                category: .engine)
+            // Load-time seek (not a user scrub): no seekInFlight needed; the async seek(to:) carries #37/#38 semantics for user seeks.
+            let target = CMTime(seconds: startPosition ?? 0, preferredTimescale: 600)
+            mountSeek = { [weak self] player in
+                player.seek(to: target, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] finished in
+                    guard finished else { return }
+                    Task { @MainActor in
+                        guard let self, self.sessionID == sid else { return }
+                        self.mountSeekPending = false
+                    }
+                }
+            }
+        }
+        handOver(item, offMain: handOverOffMain, then: mountSeek)
         // Fold cached counters without a native read. The final reconciliation is requested only
         // after detach, and never holds up the handover.
         if let outgoingDiagnostics {
@@ -896,33 +993,12 @@
         }
 
         // Explicit seek prevents AVPlayer from defaulting to the EVENT-playlist live edge. Remote-HLS and loopback live REJOINS set skipInitialSeek (backlog-start seek was the prime suspect for permanent waitingToPlay on rejoin; see LiveReloadPolicy.skipInitialSeek).
-        if !skipInitialSeek {
-            // AE#509: name the seek and the axis it is spent on. This is the only unconditional
-            // reposition of a fresh item, it happens before the item can answer for itself, and
-            // nothing logged it: a session parked at an unreachable position looked exactly like a
-            // session that placed nothing. `startPosition` is an ITEM-axis anchor, while a live
-            // host only ever sees the published clock (item + shift), so a live anchor that is not
-            // nil is the one shape where those two axes can be confused.
-            EngineLog.emit(
-                "[NativeAVPlayerHost] #\(sid) mount seek: item axis "
-                + String(format: "%.2f", startPosition ?? 0) + "s "
-                + "(startPosition=\(startPosition.map { String(format: "%.2f", $0) } ?? "nil"), "
-                + "live=\(contract.isLive))",
-                category: .engine)
-            // Load-time seek (not a user scrub): no seekInFlight needed; the async seek(to:) carries #37/#38 semantics for user seeks.
-            avPlayer.seek(to: CMTime(seconds: startPosition ?? 0, preferredTimescale: 600),
-                          toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] finished in
-                guard finished else { return }
-                Task { @MainActor in
-                    guard let self, self.sessionID == sid else { return }
-                    self.mountSeekPending = false
-                }
-            }
-        }
+        // Modified for Teevo: made above as `mountSeek` and run after the hand-over.
     }
 
-    func tearDown() {
-        unloadCurrentItem()
+    /// `handOverOffMain` (Modified for Teevo): the item leaves the player on `handOvers`.
+    func tearDown(handOverOffMain: Bool = false) {
+        unloadCurrentItem(handOverOffMain: handOverOffMain)
     }
 
     /// Retire the outgoing session before the engine subscribes for its successor, without
@@ -2150,7 +2226,7 @@
         retiredItemDroppedFrames = 0
     }
 
-    private func unloadCurrentItem(inPlaceSwap: Bool = false) {
+    private func unloadCurrentItem(inPlaceSwap: Bool = false, handOverOffMain: Bool = false) {
         // Invalidating KVO does not cancel callbacks already queued onto the main actor. Retire
         // their session now so they drop on their `sessionID == sid` guard, including during the
         // handover gap where the old item is still attached and still producing them.
@@ -2226,7 +2302,8 @@
         // Clear playIntent so the previous session can't restart the next item at ITS readyToPlay.
         playIntent = false
         avPlayer.pause()
-        avPlayer.replaceCurrentItem(with: nil)
+        // Modified for Teevo: off the main actor when asked; it waits while the server stops the item's sound.
+        handOver(nil, offMain: handOverOffMain)
         playerItem = nil
         isReady = false
         currentTime = 0
@@ -2257,7 +2334,7 @@
         for track in tracks {
             let fourcc: String
             var extra = ""
-            if let cm = (try? await track.load(.formatDescriptions))?.first {
+            if let cm = (await Self.trackLoad(.formatDescriptions, of: track))?.first {
                 fourcc = fourccString(CMFormatDescriptionGetMediaSubType(cm))
                 if track.mediaType == .audio {
                     extra = " " + audioFormatDescription(cm)
@@ -2265,15 +2342,15 @@
             } else {
                 fourcc = "?"
             }
-            let enabled = (try? await track.load(.isEnabled)) ?? false
-            let playable = (try? await track.load(.isPlayable)) ?? false
+            let enabled = (await Self.trackLoad(.isEnabled, of: track)) ?? false
+            let playable = (await Self.trackLoad(.isPlayable, of: track)) ?? false
             EngineLog.emit("[NativeAVPlayerHost] #\(sid) asset.track type=\(track.mediaType.rawValue) codec='\(fourcc)' enabled=\(enabled) playable=\(playable)\(extra) (\(reason))", category: .engine)
         }
     }
 
     /// Dump item.tracks at readyToPlay (HLS: asset.tracks is empty; item.tracks has the resolved list after playlist+init.mp4 parse). Channel layout tag diagnoses multichannel-routing path.
     private static func dumpPlayerItemTracks(_ item: AVPlayerItem, sid: Int) async {
-        let tracks = item.tracks
+        let tracks = await offMain { item.tracks }   // Modified for Teevo: off the main actor.
         if tracks.isEmpty {
             EngineLog.emit("[NativeAVPlayerHost] #\(sid) item.tracks empty (readyToPlay)", category: .engine)
             return
@@ -2282,7 +2359,7 @@
             guard let assetTrack = itemTrack.assetTrack else { continue }
             let fourcc: String
             var extra = ""
-            if let cm = (try? await assetTrack.load(.formatDescriptions))?.first {
+            if let cm = (await Self.trackLoad(.formatDescriptions, of: assetTrack))?.first {
                 fourcc = fourccString(CMFormatDescriptionGetMediaSubType(cm))
                 if assetTrack.mediaType == .audio {
                     extra = " " + audioFormatDescription(cm)
@@ -2544,10 +2621,13 @@
     private func publishDetectedVideoFormat(from item: AVPlayerItem) async {
         let sid = sessionID
         guard sid != 0, playerItem === item else { return }
-        for itemTrack in item.tracks {
+        // Modified for Teevo: the tracks are read off the main actor (they wait on the media server).
+        let tracks = await Self.offMain { item.tracks }
+        guard sessionID == sid, playerItem === item else { return }
+        for itemTrack in tracks {
             guard let assetTrack = itemTrack.assetTrack, assetTrack.mediaType == .video else { continue }
-            guard let cm = try? await assetTrack.load(.formatDescriptions).first else { continue }
-            let rate = (try? await assetTrack.load(.nominalFrameRate)).map(Double.init)
+            guard let cm = await Self.trackLoad(.formatDescriptions, of: assetTrack)?.first else { continue }
+            let rate = (await Self.trackLoad(.nominalFrameRate, of: assetTrack)).map(Double.init)
             guard sessionID == sid, playerItem === item else { return }
             let subType = CMFormatDescriptionGetMediaSubType(cm)
             let ext = CMFormatDescriptionGetExtensions(cm) as? [String: Any] ?? [:]
@@ -2583,11 +2663,12 @@
             selectedOptionLanguage = option.extendedLanguageTag
         }
         var readings: [RemoteHLSStreamDescription.AudioReading] = []
-        for itemTrack in item.tracks {
+        // Modified for Teevo: off the main actor, as in publishDetectedVideoFormat.
+        for itemTrack in await Self.offMain({ item.tracks }) {
             guard let assetTrack = itemTrack.assetTrack, assetTrack.mediaType == .audio else { continue }
-            guard let cm = try? await assetTrack.load(.formatDescriptions).first else { continue }
-            let extendedTag = try? await assetTrack.load(.extendedLanguageTag)
-            let languageCode = try? await assetTrack.load(.languageCode)
+            guard let cm = await Self.trackLoad(.formatDescriptions, of: assetTrack)?.first else { continue }
+            let extendedTag = await Self.trackLoad(.extendedLanguageTag, of: assetTrack)
+            let languageCode = await Self.trackLoad(.languageCode, of: assetTrack)
             let language = [extendedTag ?? nil, languageCode ?? nil, selectedOptionLanguage]
                 .compactMap { $0 }.first { !$0.isEmpty && $0 != "und" }
             if let reading = RemoteHLSStreamDescription.audioReading(
@@ -2609,7 +2690,7 @@
             let mediaType = assetTrack?.mediaType.rawValue ?? "?"
             var fdesc: CMFormatDescription?
             if let assetTrack {
-                fdesc = (try? await assetTrack.load(.formatDescriptions))?.first
+                fdesc = (await Self.trackLoad(.formatDescriptions, of: assetTrack))?.first
             }
             let fourCC: String
             if let cm = fdesc {
@@ -2653,10 +2734,10 @@
         #if os(iOS) || os(tvOS)
         var trackChannels: Int = 0
         var isFLAC = false
-        for itemTrack in item.tracks {
+        for itemTrack in await offMain({ item.tracks }) {   // Modified for Teevo: off the main actor.
             guard let assetTrack = itemTrack.assetTrack else { continue }
             guard assetTrack.mediaType == .audio else { continue }
-            guard let cm = try? await assetTrack.load(.formatDescriptions).first else { continue }
+            guard let cm = await Self.trackLoad(.formatDescriptions, of: assetTrack)?.first else { continue }
             let codec = fourccString(CMFormatDescriptionGetMediaSubType(cm))
             if codec.lowercased() == "flac" {
                 isFLAC = true
@@ -2703,10 +2784,10 @@
         guard !isAtmosStreamCopy else { return }
         var trackChannels: Int = 0
         var codecID: String = ""
-        for itemTrack in item.tracks {
+        for itemTrack in await offMain({ item.tracks }) {   // Modified for Teevo: off the main actor.
             guard let assetTrack = itemTrack.assetTrack else { continue }
             guard assetTrack.mediaType == .audio else { continue }
-            guard let cm = try? await assetTrack.load(.formatDescriptions).first else { continue }
+            guard let cm = await Self.trackLoad(.formatDescriptions, of: assetTrack)?.first else { continue }
             let codec = fourccString(CMFormatDescriptionGetMediaSubType(cm))
             let lower = codec.lowercased()
             if lower == "ec-3" || lower == "ac-3" {
