fix(tvos): make EAC3 playback conform to Dolby's guidance

Groundwork for #1300. Establishes the session, buffering and route
handling Dolby's application guide prescribes, and adds the diagnostic
arm needed to find out whether Apple's sample-buffer renderer can carry
Atmos objects at all.

Audio session, per the guide's sequence:

- Adopt the long-form playback profile in one atomic call at app launch
  and activate the session there. The SDK only accepts that policy with
  category Playback, a Default/MoviePlayback/SpokenAudio mode and no
  options, so it cannot be assembled from separate calls.
- Report the resolved rendering mode in the player, hidden unless the
  system resolves it. Apple only resolves it for CarPlay and AirPlay, so
  an unresolved value means unknown, never "not Dolby".

Diagnostics (Apple TV only, Settings > Video Playback > Atmos Output Test):

- Add a sample-buffer arm. It reads the asset with AVAssetReader at
  outputSettings nil and hands the untouched compressed buffers and the
  untouched format description straight to the renderer, with a variant
  that rebuilds the description the way playback builds it. Every
  existing mode went through AVPlayer, so nothing exercised the path
  playback actually uses; this is what tells us whether the renderer or
  our construction is at fault.
- Add an AirPlay route picker. AirPlay is the only route where the system
  resolves the rendering mode and the supported channel layouts, so it is
  what makes those observations reachable at all, and the AVPlayer arms
  now allow external playback so every arm can be compared on the same
  destination.
- Add a session-mode toggle for the one profile difference between the
  guide and previous playback behaviour.
- Report the session profile, supported layouts, both format
  descriptions, the magic cookie and the renderer status, and release the
  session on stop so a failed run cannot contaminate the next one.

Also bumps MPVKit to 1.0.14, which carries the matching audio output
work: the channel layout AVFoundation itself uses for Dolby content, a
renderer-failure observer so the fallback to PCM can actually run, the
prescribed feed ordering and preroll, flush recovery that re-supplies the
discarded audio instead of shifting later audio into its place, and
capability-driven fallback on route and capability changes.

This does not yet fix #1300. Whether the sample-buffer renderer can carry
JOC is still unknown; it removes every difference from the documented
setup that could explain the failure, and gives us the arm to answer it
on real hardware.
This commit is contained in:
edde746
2026-07-26 20:42:58 +02:00
parent a56b9a3dfb
commit 6c14049e95
62 changed files with 1743 additions and 148 deletions
+32 -4
View File
@@ -418,6 +418,15 @@ class _Translations$settings$nb extends Translations$settings$en {
@override String get atmosTestRawStreamDescription => 'Strømmer testfilen akkurat som Atmos-avspilling i spilleren. Krever testfilens URL.';
@override String get atmosTestRawFile => 'Rå EAC3-fil';
@override String get atmosTestRawFileDescription => 'Spiller av testfilen med kjent lengde. Krever testfilens URL.';
@override String get atmosTestAsbarNative => 'Sample-buffer-renderer (nativ)';
@override String get atmosTestAsbarNativeDescription => 'Sender filens urørte komprimerte lyd rett til systemets renderer. Krever URL til testfilen.';
@override String get atmosTestAsbarGenerated => 'Sample-buffer-renderer (gjenoppbygd)';
@override String get atmosTestAsbarGeneratedDescription => 'Det samme, men med lydbeskrivelsen bygd slik avspilling bygger den. Krever URL til testfilen.';
@override String get atmosTestSessionMode => 'Bruk filmavspillingsmodus';
@override String get atmosTestSessionModeDescription => 'Av bruker modusen Dolby dokumenterer. På bruker den tidligere modusen.';
@override String get atmosTestShowRoutePicker => 'Velg AirPlay-utgang';
@override String get atmosTestHideRoutePicker => 'Skjul AirPlay-utgangsvelger';
@override String get atmosTestRoutePickerDescription => 'Sender testen til en AirPlay-mottaker. Bare AirPlay rapporterer den valgte lydmodusen.';
@override String get atmosTestStop => 'Stopp test';
@override String get atmosTestUrl => 'Testfilens URL';
@override String get atmosTestUrlDescription => 'HTTP-URL til en rå .ec3 Dolby Atmos-fil (f.eks. hentet ut med ffmpeg)';
@@ -1514,6 +1523,11 @@ class _Translations$videoSettings$nb extends Translations$videoSettings$en {
@override String get audioOutput => 'Lydutgang';
@override String get performanceOverlay => 'Ytelsesoverlegg';
@override String get audioPassthrough => 'Direkte lydutgang';
@override String get audioOutputDolbyAtmos => 'Dolby Atmos';
@override String get audioOutputDolbyAudio => 'Dolby Audio';
@override String get audioOutputSurround => 'Surround';
@override String get audioOutputSpatial => 'Romlig lyd';
@override String get audioOutputStereo => 'Stereo';
@override String get audioNormalization => 'Normaliser lydstyrke';
@override String get audioDownmix => 'Nedmiks til stereo';
}
@@ -2463,6 +2477,15 @@ extension on TranslationsNb {
'settings.atmosTestRawStreamDescription' => 'Strømmer testfilen akkurat som Atmos-avspilling i spilleren. Krever testfilens URL.',
'settings.atmosTestRawFile' => 'Rå EAC3-fil',
'settings.atmosTestRawFileDescription' => 'Spiller av testfilen med kjent lengde. Krever testfilens URL.',
'settings.atmosTestAsbarNative' => 'Sample-buffer-renderer (nativ)',
'settings.atmosTestAsbarNativeDescription' => 'Sender filens urørte komprimerte lyd rett til systemets renderer. Krever URL til testfilen.',
'settings.atmosTestAsbarGenerated' => 'Sample-buffer-renderer (gjenoppbygd)',
'settings.atmosTestAsbarGeneratedDescription' => 'Det samme, men med lydbeskrivelsen bygd slik avspilling bygger den. Krever URL til testfilen.',
'settings.atmosTestSessionMode' => 'Bruk filmavspillingsmodus',
'settings.atmosTestSessionModeDescription' => 'Av bruker modusen Dolby dokumenterer. På bruker den tidligere modusen.',
'settings.atmosTestShowRoutePicker' => 'Velg AirPlay-utgang',
'settings.atmosTestHideRoutePicker' => 'Skjul AirPlay-utgangsvelger',
'settings.atmosTestRoutePickerDescription' => 'Sender testen til en AirPlay-mottaker. Bare AirPlay rapporterer den valgte lydmodusen.',
'settings.atmosTestStop' => 'Stopp test',
'settings.atmosTestUrl' => 'Testfilens URL',
'settings.atmosTestUrlDescription' => 'HTTP-URL til en rå .ec3 Dolby Atmos-fil (f.eks. hentet ut med ffmpeg)',
@@ -2690,6 +2713,8 @@ extension on TranslationsNb {
'videoControls.language' => 'Språk',
'videoControls.noSubtitlesFound' => 'Ingen undertekster funnet',
'videoControls.noSubtitlesAvailable' => 'Ingen undertekster tilgjengelig',
_ => null,
} ?? switch (path) {
'videoControls.noAudioTracksAvailable' => 'Ingen lydspor tilgjengelig',
'videoControls.noTracksAvailable' => 'Ingen spor tilgjengelig',
'videoControls.subtitleDownloaded' => 'Undertekst lastet ned',
@@ -2699,8 +2724,6 @@ extension on TranslationsNb {
'messages.markedAsWatched' => 'Merket som sett',
'messages.markedAsUnwatched' => 'Merket som usett',
'messages.markedAsWatchedOffline' => 'Merket som sett (synkroniseres når tilkoblet)',
_ => null,
} ?? switch (path) {
'messages.markedAsUnwatchedOffline' => 'Merket som usett (synkroniseres når tilkoblet)',
'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatisk fjernet: ${title}',
'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('nb'))(n, one: 'Fjernet automatisk ${n} avspilt nedlasting', other: 'Fjernet automatisk ${n} avspilte nedlastinger', ),
@@ -3204,6 +3227,8 @@ extension on TranslationsNb {
'watchTogether.codeMustBe5Chars' => 'Øktkoden må være 5 tegn',
'watchTogether.joinInstructions' => 'Skriv inn vertens øktkode for å bli med.',
'watchTogether.failedToCreate' => 'Kunne ikke opprette økt',
_ => null,
} ?? switch (path) {
'watchTogether.failedToJoin' => 'Kunne ikke bli med i økt',
'watchTogether.sessionCodeCopied' => 'Øktkode kopiert til utklippstavle',
'watchTogether.relayUnreachable' => 'Reléserveren kan ikke nås. Blokkering hos internettleverandøren kan hindre Se sammen.',
@@ -3213,8 +3238,6 @@ extension on TranslationsNb {
'watchTogether.joinCurrentPlaybackDescription' => 'Hopp tilbake til det verten ser på nå',
'watchTogether.failedToOpenCurrentPlayback' => 'Kunne ikke åpne gjeldende avspilling',
'watchTogether.participantJoined' => ({required Object name}) => '${name} ble med',
_ => null,
} ?? switch (path) {
'watchTogether.participantLeft' => ({required Object name}) => '${name} forlot',
'watchTogether.participantPaused' => ({required Object name}) => '${name} satte avspillingen på pause',
'watchTogether.participantResumed' => ({required Object name}) => '${name} startet avspillingen igjen',
@@ -3413,6 +3436,11 @@ extension on TranslationsNb {
'videoSettings.audioOutput' => 'Lydutgang',
'videoSettings.performanceOverlay' => 'Ytelsesoverlegg',
'videoSettings.audioPassthrough' => 'Direkte lydutgang',
'videoSettings.audioOutputDolbyAtmos' => 'Dolby Atmos',
'videoSettings.audioOutputDolbyAudio' => 'Dolby Audio',
'videoSettings.audioOutputSurround' => 'Surround',
'videoSettings.audioOutputSpatial' => 'Romlig lyd',
'videoSettings.audioOutputStereo' => 'Stereo',
'videoSettings.audioNormalization' => 'Normaliser lydstyrke',
'videoSettings.audioDownmix' => 'Nedmiks til stereo',
'performanceOverlay.color' => 'Farge',