Files
MatchLiveTv/docs/IOS_STREAM_AUDIO_MUTE.md
T

6.0 KiB
Raw Blame History

iOS — mute audio in diretta (allineamento da Mac)

Documento operativo per Cursor su Mac: verificare e, se serve, ritoccare lapp iOS dopo il ramo feature/stream-audio-mute.

Aggiornato: 2026-08-12
Branch: feature/stream-audio-mute (parte da main)
Android di riferimento: mute microfono in overlay + sync regia
API produzione: https://www.matchlivetv.it

Il codice iOS è già nel branch (engine, overlay, API, Cable, L10n). Su Mac va compilato, testato su device e, se HaishinKit 2.2.5 rifiuta un parametro, adattato come sotto.


Perché esiste

In palestra la musica di sottofondo finisce nel microfono del telefono. Il relay YouTube fa -c:a copy, quindi YouTube sente quella musica e può reclamare il copyright.

Il mute non ferma lo stream: continua video + overlay + AAC, ma i sample audio sono silenzio.


Contratto API / Cable (già in backend)

Pezzo Dettaglio
Campo sessione audio_muted (boolean, default false) in JSON sessione e GET/POST /regia/:token/status.json
REST app PATCH /api/v1/sessions/:id/audio_mute body { "muted": true | false }
REST regia POST /regia/:token/audio_mute body { "muted": true | false } (senza body: toggle)
Cable command { "type": "command", "action": "mute_audio" | "unmute_audio", "muted": true | false }
Cable event { "type": "stream_event", "event": "audio_muted", "muted": true | false }

Idempotente: se lo stato è già quello richiesto, il backend non reinvia eventi.


File iOS già modificati

native/ios/MatchLiveTv/Streaming/BroadcastModels.swift          # BroadcastMetrics.audioMuted
native/ios/MatchLiveTv/Streaming/LiveBroadcastEngine.swift      # setAudioMuted + AudioMixerSettings.isMuted
native/ios/MatchLiveTv/Streaming/LiveBroadcastCoordinator.swift # setAudioMuted
native/ios/MatchLiveTv/Domain/Models.swift                      # StreamSession.audioMuted + withAudioMuted
native/ios/MatchLiveTv/Data/API/ApiDtos.swift                   # audioMuted + AudioMuteRequest
native/ios/MatchLiveTv/Data/API/MatchLiveAPI.swift              # setAudioMute
native/ios/MatchLiveTv/Data/Repository/SessionRepository.swift
native/ios/MatchLiveTv/Data/Cable/SessionCableService.swift     # onAudioMute + AudioMuteCommandLogic
native/ios/MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift
native/ios/MatchLiveTv/UI/Broadcast/BroadcastScreen.swift
native/ios/MatchLiveTv/Core/AppLanguage.swift                   # IT/EN/FR/DE/ES
native/ios/MatchLiveTvTests/LiveBroadcastCoordinatorTests.swift

Engine HaishinKit 2.2.5 (punto da verificare su Mac)

Implementazione attesa in LiveBroadcastEngine:

func setAudioMuted(_ muted: Bool) async {
    audioMuted = muted
    await applyAudioMute()
    metrics.audioMuted = muted
    emitMetrics()
}

private func applyAudioMute() async {
    guard pipelineConfigured else { return }
    var settings = await mixer.audioMixerSettings
    settings.isMuted = audioMuted
    var track = settings.tracks[0] ?? AudioMixerTrackSettings(downmix: true, channelMap: [0])
    track.isMuted = audioMuted
    settings.tracks[0] = track
    try? await mixer.setAudioMixerSettings(settings)
}

In configurePipeline i settings iniziali passano isMuted: audioMuted sia sul mixer sia sulla track 0.

Se il build fallisce:

  1. AudioMixerSettings potrebbe non avere isMuted nel memberwise usato oggi — in quel caso muta solo settings.tracks[0].isMuted.
  2. setAudioMixerSettings è async throws nel codice attuale; se la signature è sync, togli try await.
  3. Non staccare il microfono (attachAudio(nil)): YouTube/MediaMTX vogliono AAC continuo. Mute = silenzio, non assenza di traccia.

Dopo prepareBroadcast / resumeBroadcast la schermata richiama setAudioMuted(session.audioMuted) per riallineare lo stato persistito.


UI overlay

Toolbar destra (come Android), sotto pausa:

Stato SF Symbol Chiave L10n
Audio on speaker.wave.2.fill broadcast.mute.cd
Audio off speaker.slash.fill broadcast.unmute.cd

Bottone highlighted quando mutato (stesso verde della pausa). Pannello telemetria: riga broadcast.audio.muted.label se mutato.


Chiavi L10n (già aggiunte, 5 lingue)

Android iOS
broadcast_mute_cd broadcast.mute.cd
broadcast_unmute_cd broadcast.unmute.cd
broadcast_audio_muted_label broadcast.audio.muted.label
broadcast_snackbar_muted broadcast.snackbar.muted
broadcast_snackbar_unmuted broadcast.snackbar.unmuted
broadcast_snackbar_muted_remote broadcast.snackbar.muted.remote
broadcast_snackbar_unmuted_remote broadcast.snackbar.unmuted.remote
broadcast_snackbar_mute_error broadcast.snackbar.mute.error

Test su Mac (acceptance)

  1. cd native/ios && python3 generate_xcodeproj.py
  2. Build simulatore / device.
  3. Avvia una diretta (anche verso ambiente di staging).
  4. Overlay: tap mute → icona speaker.slash, snackbar «Audio silenziato», telemetria «Audio off».
  5. Apri la regia sullo stesso match → il pulsante deve dire «Riattiva audio».
  6. Dalla regia riattiva → il telefono mostra speaker.wave e snackbar «dalla regia».
  7. Mute, metti in pausa, riprendi: laudio resta mutato (stato persistito).
  8. YouTube / player HLS: video continua, audio silenzioso (non assente).
  9. Unit test: LiveBroadcastCoordinatorTests.testAudioMuteCommandFromCable.
cd native/ios
python3 generate_xcodeproj.py
xcodebuild -project MatchLiveTv.xcodeproj -scheme MatchLiveTv \
  -destination 'generic/platform=iOS Simulator' \
  -derivedDataPath build/DerivedData \
  ONLY_ACTIVE_ARCH=YES ARCHS=arm64 EXCLUDED_ARCHS=x86_64 \
  build

Criterio “fatto”

  • Build iOS senza errori HaishinKit
  • Mute/unmute locale in overlay
  • Sync bidirezionale con la regia via Cable + REST
  • Mute sopravvive a pausa/ripresa
  • AAC silenzioso in onda (niente drop della traccia audio)
  • Stringhe IT/EN/FR/DE/ES visibili in overlay