6.0 KiB
iOS — mute audio in diretta (allineamento da Mac)
Documento operativo per Cursor su Mac: verificare e, se serve, ritoccare l’app 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:
AudioMixerSettingspotrebbe non avereisMutednel memberwise usato oggi — in quel caso muta solosettings.tracks[0].isMuted.setAudioMixerSettingsèasync throwsnel codice attuale; se la signature è sync, toglitry await.- 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)
cd native/ios && python3 generate_xcodeproj.py- Build simulatore / device.
- Avvia una diretta (anche verso ambiente di staging).
- Overlay: tap mute → icona speaker.slash, snackbar «Audio silenziato», telemetria «Audio off».
- Apri la regia sullo stesso match → il pulsante deve dire «Riattiva audio».
- Dalla regia riattiva → il telefono mostra speaker.wave e snackbar «dalla regia».
- Mute, metti in pausa, riprendi: l’audio resta mutato (stato persistito).
- YouTube / player HLS: video continua, audio silenzioso (non assente).
- 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