Found while fixing the Mac-as-camera recording hang (PR #157). That bug's root cause is fixed, but the three things that turned a settings rejection into a permanent two-device freeze are all still present. The next stop-side failure — from any cause — will hang exactly the same way.
Three silent-failure amplifiers, in the order they bite:
1. configureAudioForRecording reports success when it failed
RemoteCam/CaptureEngine.swift:213-219
if captureSession.canAddInput(audioDeviceInput) {
captureSession.addInput(audioDeviceInput)
} else {
print("Could not add audio device input to the session") // ← prints, then falls through
}
if captureSession.canAddOutput(audioDataOutput) { ... } // ← no else at all
...
return true // ← unconditional
RecordingPipeline.swift:107's guard self.configureAudio(...) therefore passes, and the deliberate MicrophoneAccessDenied bail-out at :107-114 never fires. Fix: return false when the input or the output can't be added. That alone routes it to the already-wired clean failure (error ack + "Unable to record audio" alert).
2. stopRecording silently drops the whole protocol
RemoteCam/RecordingPipeline.swift:147
if self.recordingWillBeStopped || !self.isRecordingStorage {
return // no finishWriting, no StopRecordingVideoResp, no error, no log
}
The guard conflates two different states. Split them:
recordingWillBeStopped → a stop is in flight, ignore (correct today).
!isRecordingStorage with recordingWillBeStarted == true → the start never completed. Tear down the pending writer, clear recordingWillBeStarted (today it stays true forever, poisoning every future startRecording via the guard at :97), and emit StopRecordingVideoResp(error:) so the monitor unblocks.
Also guard the assetWriter? optional-chain at :157: a nil writer means the completion never runs and recordingWillBeStopped stays true forever — the same permanent poison. Never call finishWriting on a .unknown-status writer (it throws).
3. .monitorWaitingForVideo is the only monitor wait state with no timeout
RemoteCam/SessionCoordinator.swift:1540
Every other wait state arms a scheduleTimeout (:1187, :1200, :1209, :1229, :1258, :1507), and the watch path recovers from the identical underlying no-op via :1739/:1753. The multipeer path is the outlier.
⚠️ A flat 10s would break large transfers, which legitimately take minutes. Needs to be transfer-aware: arm until the transfer starts, then rely on progress.
Why it matters
Any one of these alone is survivable. Together they convert any recording-side failure into both devices hanging forever with no error and no log. Worth fixing as one PR.
Found while fixing the Mac-as-camera recording hang (PR #157). That bug's root cause is fixed, but the three things that turned a settings rejection into a permanent two-device freeze are all still present. The next stop-side failure — from any cause — will hang exactly the same way.
Three silent-failure amplifiers, in the order they bite:
1.
configureAudioForRecordingreports success when it failedRemoteCam/CaptureEngine.swift:213-219RecordingPipeline.swift:107'sguard self.configureAudio(...)therefore passes, and the deliberateMicrophoneAccessDeniedbail-out at:107-114never fires. Fix: returnfalsewhen the input or the output can't be added. That alone routes it to the already-wired clean failure (error ack + "Unable to record audio" alert).2.
stopRecordingsilently drops the whole protocolRemoteCam/RecordingPipeline.swift:147The guard conflates two different states. Split them:
recordingWillBeStopped→ a stop is in flight, ignore (correct today).!isRecordingStoragewithrecordingWillBeStarted == true→ the start never completed. Tear down the pending writer, clearrecordingWillBeStarted(today it staystrueforever, poisoning every futurestartRecordingvia the guard at:97), and emitStopRecordingVideoResp(error:)so the monitor unblocks.Also guard the
assetWriter?optional-chain at:157: a nil writer means the completion never runs andrecordingWillBeStoppedstaystrueforever — the same permanent poison. Never callfinishWritingon a.unknown-status writer (it throws).3.
.monitorWaitingForVideois the only monitor wait state with no timeoutRemoteCam/SessionCoordinator.swift:1540Every other wait state arms a
scheduleTimeout(:1187, :1200, :1209, :1229, :1258, :1507), and the watch path recovers from the identical underlying no-op via:1739/:1753. The multipeer path is the outlier.Why it matters
Any one of these alone is survivable. Together they convert any recording-side failure into both devices hanging forever with no error and no log. Worth fixing as one PR.