Repository navigation
Conversation
LumeEngine decodes audio to PCM and downmixes to a single output width. With nothing stated by the host it read AVAudioSession.outputNumberOfChannels at audio-lane-build time, which lost an ordering race: LumeEngineEngineView starts the session from .onAppear while FullScreenPlayerView only activated and widened the route from its own later .task. A session opens exactly one URL with no rebuild-in-place, so a width resolved too early is the width for the whole stream — a 5.1 track could reach a surround receiver as stereo. - New PlaybackAudioRoute owns playback audio-session setup. Its activateForPlayback() is idempotent and is called from LumeEngineCoordinator.makeConfiguration, so whichever of the two callers runs first negotiates and the other reuses the result. Ordering can no longer produce a stereo-clamped lane. - It activates the session *before* reading channel counts. An inactive session reports the pre-activation default of 2 even on a route that can carry more, so the old order asked for a widening that never happened. - negotiatedWidth is min(granted, maximum) and nil at or below stereo. It never passes maximumOutputNumberOfChannels through: that is capability, not the granted width, and over-shooting fails the renderer outright (silence, wedged pipeline) rather than merely downmixing. - MultiViewScreen keeps its deliberately non-widening session via activateStereo() — only one tile is audible, so negotiating a surround layout for a muted grid would be wrong. - The exported diagnostic report now includes engine.lume/diagnostics, so the once-per-stream colour and audio-width readouts survive into a report a user sends. The raw engine.lume/ffmpeg bridge stays excluded: it logs at AV_LOG_WARNING and some streams hit it per frame. - AGENTS.md records that Dolby word marks must never appear in user-facing UI or store copy; technical labels only. Refs #207
16 of 17 tasks
…ion-atmos # Conflicts: # Lume/Views/Player/FullScreenPlayerView.swift # Lume/Views/Player/LumeEngineCoordinator.swift
…layer DV profiles 5, 20 and 10.0 start fine on KSPlayer, VLCKit and AVPlayer but render pink/green, so the failure-driven fallback never fires. KSPlayer now reports the stream's DV record at readyToPlay; when the base layer is IPT the host swaps that stream to LumeEngine (the only engine that applies the RPU), resuming where it was. Always on, no setting. AirPlay keeps precedence, and if LumeEngine fails the stream returns to the user's engine and is not rerouted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
App side of #207, round 2a. It pairs with bilipp/LumeEngine#33, which carries the picture fixes:
This PR makes sure LumeEngine gets the right audio output width, and that its diagnostics reach the exported report.
Audio: negotiate the width before the lane is built
Before this PR, LumeEngine read
AVAudioSession.outputNumberOfChannelswhen it built its audio lane. That lost an ordering race:LumeEngineEngineViewstarts the session from.onAppear, butFullScreenPlayerViewonly activated and widened the route from a later.task. A session opens exactly one URL with no rebuild-in-place, so a width resolved too early stays for the whole stream. The result: a 5.1 track could reach a surround receiver as stereo.PlaybackAudioRouteowns playback audio-session setup.activateForPlayback()is idempotent and is called fromLumeEngineCoordinator.makeConfiguration. Whichever caller runs first negotiates and the other reuses the result, so ordering can no longer clamp the lane to stereo.negotiatedWidthismin(granted, maximum), andnilat or below stereo. It never passesmaximumOutputNumberOfChannelsthrough: that is what the route could carry, not what was granted, and over-shooting failsAVSampleBufferAudioRendereroutright (silence, wedged pipeline).MultiViewScreenkeeps a deliberately stereo session viaactivateStereo(). Only one tile is audible, so a surround layout for a muted grid would be wrong.Diagnostics
The exported diagnostic report now includes
engine.lume/diagnostics, so the engine's once-per-stream readouts survive into a report a user sends:stream-open(DV profile, object audio)video-formatdv-reshapeaudio-widthThe raw
engine.lume/ffmpegbridge stays excluded, because some streams log to it per frame.Docs
AGENTS.md: Dolby word marks must never appear in user-facing UI, String Catalogs or store copy; use technical labels (HDR10,DV P8.1,E-AC-3 JOC, …). It also records the host-stated audio-width contract.Refs #207. This PR does not close it: the technical-label UI badges (round 2b) are still to come.
What was measured
DVprofile20.mp4): tinted on every path, Apple's own decoder included. Fixed in LumeEngine#33 and verified on macOS; still needs a run on Apple TV / iPhone.widthSource=configuredwithroute=HDMIOutput(…).Type of change
feat— new featurePlatforms verified
visionOS is not verified. No simulator runtime is installed, and
maindoes not build for it independently of this PR:PictureInPictureBridgeis gated#if os(iOS) || os(macOS) || os(tvOS)in LumeEngine, whileLumeEngineCoordinatorreferences it ungated.Screenshots / recordings
None; no UI changes.
Checklist
--strict, String Catalog normalization).LumeTestsgreen (iPhone 17 Pro / iOS 26.4,-parallel-testing-enabled NO).LumeUITestsnot run.PlaybackAudioRouteTests(stereo route ⇒ nil; granted 6 / maximum 8 ⇒ 6, never 8; ungranted preference ⇒ nil; clamping) andDebugLogExporterTests.