Skip to content

feat(audio): add mute option for playback during recording - #952

Open
emilevictorportenart wants to merge 3 commits into
altic-dev:mainfrom
emilevictorportenart:feat/908-mute-audio-during-recording
Open

feat(audio): add mute option for playback during recording#952
emilevictorportenart wants to merge 3 commits into
altic-dev:mainfrom
emilevictorportenart:feat/908-mute-audio-during-recording

Conversation

@emilevictorportenart

@emilevictorportenart emilevictorportenart commented Sep 8, 2026

Copy link
Copy Markdown

Description

Pausing media only reaches apps that publish Now Playing state. Audio from a browser tab, a game, or a conferencing app keeps playing over a recording with no way to stop it, which is what #908 reports.

This replaces the Pause Media During Transcription toggle with a Playback When Recording choice of Keep Playing, Pause, or Mute, matching the shape of the setting the issue asks for. Mute silences the default output device through CoreAudio, so it covers any audio reaching the speakers regardless of whether the source exposes media controls. It works on Intel too, unlike Pause, which needs MediaRemoteAdapter on Apple Silicon.

How mute behaves

One rule holds the whole thing together: a device is recorded only while it is silent because of this service. Everything else follows from that.

  • An output that was already quiet, that refuses the write, that cannot be read, or that the user has since changed is not ours. Nothing is written to it, and no stale value survives to be handed back later.
  • Ownership is keyed by device UID, not by AudioObjectID, which is not stable across a disconnect and can be reissued to different hardware. An output with no UID is left alone, since silencing it would be a promise that could not be kept.
  • The default output is followed for the length of the recording, so connecting AirPods part-way through silences those too, and both are restored at the end. Watching continues while any device is still owed a restore, so one unplugged during teardown is handed back when it returns.
  • A held device's own mute and volume are watched. A change made from anywhere else releases it as it happens, rather than being inferred at teardown, which cannot see a user unmuting and muting again.
  • Silencing happens before the capture pipeline starts retaining audio, and restoring only after the acquisition boundary is frozen, so playback cannot reach either end of the recording.
  • Prefers the device mute switch, which leaves the volume slider where the user put it. Falls back to zeroing and restoring the volume on outputs without one, common on USB and HDMI devices.
  • Reads and writes that hardware refuses while it is initialising are re-attempted a bounded number of times, since a property becoming ready again raises no notification of its own.

Migration

Existing users who had pausing enabled get Pause; everyone else gets Keep Playing. Backups still write the old boolean field, so a backup made by this build restores correctly on an older build, and older backups restore through the boolean.

Type of Change

  • 🐞 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 🧹 Chore
  • 📝 Documentation update

Related Issue or Discussion

Closes #908

Testing

  • Tested on Intel Mac
  • Tested on Apple Silicon Mac
  • Tested on macOS version: 26.6 (Xcode 26.6)
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml Sources Tests Package.swift
  • Ran formatter locally: swiftformat --config .swiftformat Sources
  • Ran tests locally: xcodebuild test -project Fluid.xcodeproj -scheme Fluid -destination 'platform=macOS,arch=arm64'

SystemAudioMuteServiceTests runs 44 tests against a fake CoreAudio layer. They cover ownership and user takeover, the volume fallback, output changes mid-recording, reconnects under a new object id, refused reads and writes and their bounded retries, devices without a UID, audio-service restarts, and the interaction with the recording cues. 480 tests pass overall.

The linter reports the same 7 legacy_swiftui_aspect_ratio violations on this branch and on main; they come from a newer local SwiftLint (0.65.1) than CI pins (0.63.2) and are untouched by this change. The formatter was run at --swift-version 5.0 and reports no changes to any file in this diff.

One pre-existing test, HotkeyShortcutTests.testKeyboardPayloadIgnoresStrayMouseButtonField, fails identically on this branch and on a clean main checkout on my machine. It asserts key code 0 renders as A, which is Q on the AZERTY layout this Mac uses. It is unrelated to this change and passes on QWERTY CI runners.

Screenshots / Video

Settings > Dictation, with the new dropdown in its default state:

Playback When Recording set to Keep Playing

And with Mute selected, reached through settings search:

Playback When Recording set to Mute

And a video showing the 3 states with Music and video in the background

CleanShot.2026-09-08.at.09.54.11.mp4
  • No UI/visual changes; screenshots/video are not applicable.

How this PR got here

Worth reading before the diff, because the shape of the branch reflects it.

The feature itself is small: a setting, a CoreAudio service, and four call sites in the dictation lifecycle. Almost everything after that came from twelve rounds of automated review, which surfaced real edge cases in how the mute behaves against actual hardware: outputs changing mid-recording, devices reconnecting under a new id or with their state reset, CoreAudio refusing reads and writes while a device initialises, the audio service restarting, and the recording cues contending for the same system volume.

Three things a reviewer should know about that process:

  • The branch was rewritten. It reached fifteen commits, including a revert, working through findings one at a time. It is now three: the feature, the screenshots, and one commit folding in the hardening, grouped by cause rather than by the order things were found. The resolved threads therefore point at commits that no longer exist, so their diff context renders oddly. Each thread still carries the discussion and the reasoning.
  • Several rounds fixed consequences of earlier fixes. Moving the mute earlier exposed that the restore was mis-ordered; adding reconnect handling contradicted a rule established two commits before. Those are noted honestly on the threads rather than presented as clean progress.
  • The two reviewers disagreed on one point, described below. That one is a design decision rather than a defect, and I would rather you settled it.

If any of this reads as more machinery than a settings change should carry, I am happy to scale it back. The hardening is separable from the feature.

Notes

  • Persistence across quit is deliberately out. Both reviewers asked for opposite things here. Codex asked that unresolved ownership survive termination; I implemented it, and Greptile raised it as a P1, because at the next launch there is no way to tell a mute FluidVoice applied from one the user set in the meantime, so restoring silently overrides their newer choice. I reverted it. Every check that makes restoring safe within a session tells you nothing across a process boundary, and the feature's rule everywhere else is that the user's choice wins. What remains uncovered is a device that is absent when the app quits and never returns while it runs. Closing that safely means asking the user rather than guessing, which belongs in its own change.
  • The start cue is suppressed under Mute. It fires after the output is silenced, so it cannot be heard, and playing it would put the cue's independent-volume handling in contention with the mute over the same system volume. The stop cue is unaffected: the output is restored before it plays. That gating is decided by whether the recording is actually silencing the output, not by the setting, so a setting changed mid-recording cannot confuse it.
  • Two pre-existing issues in TranscriptionSoundPlayer are left alone. With Mute off, overlapping cues hand the volume back when the first finishes rather than the last. Neither can bite under Mute, since the start cue no longer plays while the output is silenced. Both predate this PR and are not caused by the mute feature, so I would rather raise them separately than widen this further. Say the word and I will fold them in.
  • Pause and Mute are mutually exclusive by design, which is why this is a dropdown rather than a second toggle. Happy to split it back into two toggles if you would rather leave the existing setting untouched.
  • I deliberately left out a Lower mode, even though the app in the issue offers one, to keep this focused on what [FEATURE] Mute sounds instead of Pause #908 asks for. It is a small addition on top of the volume fallback if you want it later.
  • Build and Test is sitting at action_required. That is GitHub holding workflows for a first-time fork contributor, and approving it needs write access to this repo, so it needs a maintainer to click Approve and run.

🤖 Generated with Claude Code

Pausing media only reaches apps that publish Now Playing state, so audio
from browser tabs, games, and conferencing apps kept playing over
recordings with no way to stop it (altic-dev#908).

Replace the "Pause Media During Transcription" toggle with a
"Playback When Recording" choice of Keep Playing, Pause, or Mute. Mute
silences the default output device through CoreAudio, which covers any
audio reaching the speakers.

- Add SystemAudioMuteService, which takes ownership of the mute only when
  the output is audible, restores the exact device it silenced even if the
  default output changed mid-recording, and falls back to zeroing the
  volume on devices without a mute switch
- Mute just before the start cue and restore as capture ends, so the Mac
  is not held silent during transcription
- Migrate existing users: a stored pause preference maps to Pause, and
  backups keep the legacy field so older builds still restore

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the needs screenshots Pull request needs screenshot or video evidence. label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

The PR Policy check is blocking this PR because required template information is missing.

Please update the PR description with:

  • Screenshots / Video

Visual files detected:

  • .github/screenshots/playback-when-recording-mute.png
  • .github/screenshots/playback-when-recording-setting.png
  • Sources/Fluid/Persistence/SettingsStore.swift
  • Sources/Fluid/UI/SettingsSearch.swift
  • Sources/Fluid/UI/SettingsView.swift

Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template.

If this remains incomplete for 48 hours after opening, the PR may be closed.

Settings > Dictation showing the new dropdown in its default state and
with Mute selected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot removed the needs screenshots Pull request needs screenshot or video evidence. label Sep 8, 2026
@emilevictorportenart
emilevictorportenart marked this pull request as ready for review September 8, 2026 07:58
@greptile-apps

greptile-apps Bot commented Sep 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the recording-time media pause toggle with Keep Playing, Pause, and Mute choices. It adds CoreAudio output muting with device-aware ownership and restoration, integrates the behavior with recording lifecycle boundaries and sound cues, migrates settings and backups, updates settings search and UI, and adds focused service tests. Since the previous review, restoration now retries when a connected output is temporarily unreadable in both the mute service and cue-volume restoration path.

Confidence Score: 5/5

The PR appears safe to merge because the latest changes preserve pending restoration state and add bounded retries without introducing a concrete regression.

All previous review threads are resolved, and the changes since the prior review address temporarily unreadable CoreAudio devices while retaining device identity, ownership, and generation checks. No new blocking or actionable failure remains.

Reviews (33): Last reviewed commit: "fix(audio): harden the mute lifecycle ar..." | Re-trigger Greptile

Comment thread Sources/Fluid/Services/ASRService.swift Outdated
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe57065ab3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1bb229b481

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/ASRService.swift Outdated
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d689cd52a7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/ASRService.swift Outdated
Comment thread Sources/Fluid/Services/ASRService.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 503bcb2f7d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated
Comment thread Sources/Fluid/Services/ASRService.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0068fe2ad9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated
Comment thread Sources/Fluid/Services/ASRService.swift Outdated
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated
Comment thread Sources/Fluid/AppDelegate.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cb4371e68

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift
@emilevictorportenart

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60a51a70ff

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/ASRService.swift Outdated
Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80bd41606a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db12916c4c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e3dfbf3dd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated
Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c23d44d10c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c86b71b06e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated
Comment thread Sources/Fluid/Services/ASRService.swift Outdated
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from c86b71b to 45fd283 Compare September 8, 2026 12:46
@emilevictorportenart

Copy link
Copy Markdown
Author

I have rewritten this branch to make it reviewable. The tree is byte for byte what it was before the rewrite, so nothing about the code changed.

It was fifteen commits, including an add-and-revert pair, because I worked through the automated review findings one at a time. That history is a record of how I got here, not something worth reading. It is now three commits:

  • ff91dc1 adds the feature: a Playback When Recording choice of Keep Playing, Pause or Mute, replacing the old pause toggle, with the CoreAudio mute service behind it.
  • fe57065 adds the settings screenshots.
  • 45fd283 folds in everything the review surfaced, grouped by cause rather than by the order it was found: capture-boundary ordering, device identity and lifetime, deferring to the user, and the interaction with the recording cues.

Side effect worth knowing: the resolved review threads now point at commits that no longer exist, so their diff context will render oddly. The discussion on each is intact and each explains what changed and why. Twenty three threads, all resolved.

Two things I deliberately did not fix, both predating this PR and neither caused by the mute feature. In TranscriptionSoundPlayer, the deferred volume restore targets whichever output is current rather than the one whose volume it saved, and overlapping cues hand the volume back when the first finishes rather than the last. Under Mute neither can bite, since the start cue no longer plays. I would rather raise them separately than widen this change further, but say the word and I will fold them in.

Also unresolved and outside my control: Build and Test is sitting at action_required, waiting on a maintainer to approve workflows for a first-time fork contribution.

@greptile-apps

greptile-apps Bot commented Sep 8, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Start a greploop in Codex and it will work through the open comments and keep going until this PR reviews clean.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 45fd2839f1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from 45fd283 to 75dddb1 Compare September 8, 2026 12:51
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from f309d71 to 4bbb6d7 Compare September 8, 2026 14:08
Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bbb6d71c9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from 4bbb6d7 to 9504244 Compare September 8, 2026 14:14
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift Outdated
Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9504244fe3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from 9504244 to 148f13b Compare September 8, 2026 14:21

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 148f13bdba

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated
Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch 3 times, most recently from 59de7eb to c2974cc Compare September 8, 2026 14:34

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2974cc61d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from c2974cc to 580a038 Compare September 8, 2026 14:44
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 580a038a5c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from 580a038 to 4e4d50c Compare September 8, 2026 14:54
Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e4d50c3f3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift
Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from 4e4d50c to 1ddfd63 Compare September 8, 2026 15:02
Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from 1ddfd63 to aa5fd60 Compare September 8, 2026 15:08
@emilevictorportenart

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa5fd601b6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/SystemAudioMuteService.swift
Comment thread Sources/Fluid/Services/TranscriptionSoundPlayer.swift Outdated
Review surfaced a series of edge cases where the mute either leaked audio
into a recording or failed to hand a device back. They shared two causes:
the mute was applied and released at the wrong points relative to the
capture boundary, and ownership was tracked by a value that does not
survive a device disconnecting.

Capture boundary
- Silence the output before the pipeline starts retaining PCM, not after
  the first packet, so playback cannot reach the opening of a transcript.
- Restore only after the acquisition boundary is frozen, so re-enabling the
  speakers cannot contaminate the final packet. Both stop paths and the
  termination path follow the same rule.

Device identity and lifetime
- Hold a record only while a device is silent because of this service. A
  device that was already quiet, rejected the write, cannot be read, or has
  since been changed by the user is not ours, so nothing is written to it and
  no stale value survives to be handed back later.
- Silence only outputs that publish a UID. Doing it is a promise to put the
  device back, an object id can be reissued to other hardware once the
  original goes away, and without a stable identity that promise cannot be
  kept.
- Follow the default output for the length of the recording, and keep
  observing while any device is still owed a restore, so one unplugged during
  teardown is handed back when it returns rather than at the next recording.
  Observation starts before the first sample, since CoreAudio does not replay
  a change that predates its listener.
- Re-attempt a device that would not be silenced, or would not be handed
  back, after a short delay as well as on any output change. Hardware that is
  still initialising refuses reads and writes, and becoming ready again
  raises no notification of its own. The attempts are bounded, so a device
  that simply will not take the write is not polled for the session, and each
  one reads the device afresh, so the value captured is always the one the
  user has at that moment. Refused reads are re-attempted on the same terms
  as refused writes, in both directions. A change of output gets its own allowance, and an
  output whose UID is not readable yet is re-attempted rather than taken as
  permanently unidentifiable.
- Re-register every listener after the audio service restarts, which discards
  them, re-resolving each held device rather than only the current default,
  and treat each as recreated, since a restart can reset what we applied and
  the user has not touched anything.
- Watch a held device's own mute and volume, so a change made from elsewhere
  releases it as it happens. The record and its listener follow the device
  when it returns under a new object id. Comparing only at teardown cannot see a round
  trip: unmuting and muting again leaves the device looking exactly as we
  left it, and undoing that would reverse the user's latest choice.
- Decide whether a device is still ours by whether it still holds exactly
  what we applied, rather than by its object id. A device that reconnects
  still carrying our mute keeps its record, and one that comes back reset is
  silenced again with its own current state captured.
- Tell a reconnect reset apart from a user's own change by whether the device
  has been seen absent or has come back under a different object id. Either
  is proof on its own: the id covers a reconnect whose disappearance was
  never observed because the notifications coalesced, and the absence covers
  one that returned under the same id. A device that no longer holds what we
  applied is released before being silenced again, since nothing of ours is
  in effect on it and keeping the record would let teardown undo a silence
  the user created afterwards.

Deferring to the user
- Release ownership, without writing, when the device is no longer in the
  state FluidVoice left it in. A mute the user lifts or a volume they set
  during a recording is newer than the value captured at the start.
- Keep ownership when a write fails and retry it, rather than forgetting a
  device that is briefly unreachable.

Recording cues
- Suppress the start cue under Mute. It fires after the output is silenced,
  so it cannot be heard, and playing it put the cue's independent-volume
  handling in contention with the mute over the same system volume. The
  stop cue still plays, because the output is restored before it.
- Settle a pending cue volume restore before muting, so the value captured
  is the one the user had rather than the level a cue lowered it to.
- Only the first cue of an overlapping run records the volume, so the value
  handed back is the one from before any of them played.

Applied policy
- Read the setting once per recording. The mute and the pause are applied
  either side of the wait for first PCM, so reading it twice let a change
  made during that wait produce a recording with both policies or neither.
- Keep a device whose write was rejected, marked as not silenced, so the
  recording retries it rather than giving up on hardware that was merely
  busy initialising. A device that cannot be read yet is indeterminate, not
  silent, so it is retried rather than recorded as done, and one that reads
  as silent after a rejected write was silenced by the user rather than by
  us, so it is not claimed.
- Track whether a device is silent right now separately from whether its
  state is still owed a restore. A rejected write leaves the device owned
  but audible, so the original value survives for a retry, an audible
  reading is not mistaken for the user taking over, and teardown does not
  write to a device nothing of ours was applied to.
- Put a cue's saved volume back on the output it was read from, identified
  by UID and resolved to a current object id at the time of the write.
  Writing to whichever device is current would overwrite a setting that
  never belonged to it, skipping the write would strand the original device
  at the cue's temporary volume, and an object id can be reissued to other
  hardware once the original is unplugged. A cue whose output publishes no
  UID plays at the requested volume rather than taking the system volume
  over, since there would be no way to promise it back.
- Decide the start cue on whether the recording in progress is silencing the
  output, rather than on the setting, which can change between a recording
  starting and the cue firing, or on whether any device is still owed a
  restore, which a leftover from an earlier session would satisfy.
- Resolve the cue's output once and read, lower and restore that same device,
  so a route change between separate lookups cannot read one device's volume
  and write it to another. The saved value is kept when a write is rejected,
  rather than discarded before the write is known to have landed.
- Hold a pending cue volume per output rather than one shared slot, so cues
  that overlap across an output change each hand their own device back, and
  restore one only while the device still reads the level the cue applied,
  which each cue updates so overlapping cues asking for different levels do
  not read as somebody else having changed the device. That level is recorded
  only once the device accepts the write, since a rejected one leaves it
  where it was, and the level recorded is the one the device settled on
  rather than the one requested, since some outputs quantise it. An output
  that exposes no volume at all, common for HDMI and other fixed-volume
  devices, plays the cue at the requested level rather than suppressing it. A
  device that refuses to take its volume back is re-attempted a bounded
  number of times, since becoming writable raises no notification. Delayed
  callbacks carry the run of cues they were scheduled for, so one left over
  cannot hand back a volume a later cue is still using.
- Watch the device list while a cue volume is owed back, so an output
  unplugged before its cue finished is restored when it returns rather than
  at some later cue.
- Leave the termination restore to a stop already in flight, which restores
  after freezing its own capture boundary. Running it here would make
  playback audible while that pipeline was still retaining audio.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@emilevictorportenart
emilevictorportenart force-pushed the feat/908-mute-audio-during-recording branch from aa5fd60 to 8a0a496 Compare September 8, 2026 15:26
@emilevictorportenart

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a0a496084

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/Fluid/Services/ASRService.swift
@emilevictorportenart

Copy link
Copy Markdown
Author

Ready for review.

What it does. Replaces the Pause Media During Transcription toggle with a Playback When Recording choice of Keep Playing, Pause or Mute, which is the shape #908 asks for. Pause only reaches apps that publish Now Playing state, so a browser tab, a game or a call keeps playing over a recording with nothing able to stop it. Mute silences the default output through CoreAudio instead, which covers anything reaching the speakers, and works on Intel as well.

Existing users who had pausing on get Pause; everyone else gets Keep Playing. Backups still write the old boolean, so a backup made by this build restores correctly on an older one.

Three commits. The feature, the screenshots, then one commit folding in everything the automated review surfaced. The branch was rewritten partway through to make it readable, so the resolved threads point at commits that no longer exist and their diff context renders oddly. Each thread still carries the discussion and the reasoning.

Review status. Greptile is at 5/5, PR Policy passes, and all 68 threads are resolved. Locally: 480 tests pass, SwiftLint strict adds no violations over main, SwiftFormat reports no changes to anything in the diff.

One decision I would rather you made than me. Both automated reviewers asked for opposite things on whether unresolved mute ownership should survive the app quitting. I implemented persistence, Greptile raised it as a P1 because restoring at launch silently overrides whatever the user chose in the meantime, and I reverted it. The provenance of a device's audio state is unknowable across a process boundary, so the two positions cannot both be satisfied. I left it out, because the feature's rule everywhere else is that a choice the user makes wins. The residual gap is a device that is absent when the app quits and never returns while it runs. If you want that closed, I think asking the user beats guessing, and that belongs in its own change.

Two things I deliberately did not fix, both predating this PR and neither caused by the mute feature. In TranscriptionSoundPlayer, when Mute is off, overlapping cues hand the volume back when the first finishes rather than the last. Under Mute neither can bite, since the start cue no longer plays while the output is silenced. Happy to raise them separately or fold them in, whichever you prefer.

One thing I cannot do myself. Build and Test is sitting at action_required. That is GitHub holding workflows for a first-time fork contributor, and approving needs write access to this repo, so it needs a maintainer to click Approve and run. Everything else is green.

Happy to split this up, drop parts of the hardening, or take it in a different direction if any of it goes further than you want in a settings change.

@github-actions

Copy link
Copy Markdown

This pull request has been marked stale because it has had no activity for 5 days. It will be closed in 2 days if there is no further activity. Add the keep-open or pinned label to prevent automatic closure.

@github-actions github-actions Bot added the stale label Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Mute sounds instead of Pause

1 participant