forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRealtimeHubController+VoiceOutput.swift
More file actions
132 lines (126 loc) · 5.13 KB
/
Copy pathRealtimeHubController+VoiceOutput.swift
File metadata and controls
132 lines (126 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import AppKit
import CoreGraphics
import Foundation
import OmiSupport
import VoiceTurnDomain
extension RealtimeHubController {
func makePCMPlayer() -> StreamingPCMPlayer {
let player = StreamingPCMPlayer(sampleRate: 24000)
player.onPlaybackScheduled = { [weak self] playbackEpoch in
Task { @MainActor in
guard let self else { return }
self.realtimePlaybackEpoch = playbackEpoch
}
}
player.onPlaybackProgress = { [weak self, weak player] progress in
Task { @MainActor in
guard let self, let player, self.pcmPlayer === player,
player.playbackQueueGeneration == progress.queueGeneration,
let lease = VoiceTurnCoordinator.shared.outputSnapshot.activeLease,
lease.lane == .nativeRealtime
else { return }
_ = VoiceTurnCoordinator.shared.noteOutputProgress(lease)
if progress.isIdle {
log("StreamingPCMPlayer: physical playback tail drained")
}
}
}
player.onPlaybackIdle = { [weak self] playbackEpoch in
Task { @MainActor in
guard let self, self.realtimePlaybackEpoch == playbackEpoch else { return }
if let lease = VoiceTurnCoordinator.shared.outputSnapshot.activeLease,
lease.lane == .nativeRealtime
{
if VoiceTurnCoordinator.shared.releaseOutput(lease) {
if VoiceTurnCoordinator.shared.model.turn?.phase.isTerminal == true {
self.exitVoiceUI()
self.applyPendingSessionRefreshIfIdle()
}
}
}
self.clearResponseGlowIfRealtimeAudioIdle()
}
}
return player
}
/// A verified, fail-closed screen result supersedes provider narration for
/// this turn. Keep physical preemption and reducer lease release together so
/// that local error can acquire its own deterministic lease.
func takeOverVoiceOutputForAuthoritativeLocalResult() {
if let activeLease = VoiceTurnCoordinator.shared.outputSnapshot.activeLease {
FloatingBarVoicePlaybackService.shared.interruptCurrentResponse(leaseID: activeLease.id)
_ = VoiceTurnCoordinator.shared.releaseOutput(activeLease)
}
pcmPlayer?.stop()
responseGlowGate.clearImmediately()
}
/// Slow-tool acknowledgements replace any speculative provider wait-line.
/// The admitted tool identity selects the canned phrase; transcript text is
/// never inspected here. Filler already has a dedicated yielding policy.
func prepareVoiceOutputForDeterministicSlowToolAcknowledgement() {
guard let activeLease = VoiceTurnCoordinator.shared.outputSnapshot.activeLease else {
assistantText = ""
externalRunAuthorityState?.answer.replace(with: "")
return
}
switch activeLease.lane {
case .nativeRealtime, .selectedVoiceFallback, .systemVoiceFallback:
takeOverVoiceOutputForAuthoritativeLocalResult()
case .filler, .deterministicAgentAck, .deterministicScreenEvidence:
break
}
// A provider-authored pre-tool status must not be journaled beside the
// final answer even when it raced ahead of the function call.
assistantText = ""
externalRunAuthorityState?.answer.replace(with: "")
}
func acquireVoiceOutput(_ lane: VoiceOutputLane, reason: String) -> VoiceOutputLease? {
guard let turnID = VoiceTurnCoordinator.shared.activeTurnID else {
log(
"RealtimeHub[\(providerTag)]: dropping \(lane.rawValue) output with no active PTT turn reason=\(reason)"
)
return nil
}
_ = FloatingBarVoicePlaybackService.shared.preemptFillerIfNeeded(
for: lane,
turnID: turnID)
switch VoiceTurnCoordinator.shared.acquireOutput(lane, turnID: turnID) {
case .acquired(let lease):
return lease
case .denied(let active):
log(
"RealtimeHub[\(providerTag)]: dropping \(lane.rawValue) output reason=\(reason) "
+ "active_lane=\(active.lane.rawValue)"
)
return nil
case .staleTurn:
log("RealtimeHub[\(providerTag)]: dropping stale \(lane.rawValue) output reason=\(reason)")
return nil
}
}
func releaseVoiceOutputIfActive(_ lane: VoiceOutputLane) {
guard let lease = VoiceTurnCoordinator.shared.outputSnapshot.activeLease, lease.lane == lane else {
return
}
_ = VoiceTurnCoordinator.shared.releaseOutput(lease)
}
/// Executes the reducer's exact native-audio stop effect. Terminal reduction
/// clears the logical lease before effects run, so the terminal record is the
/// authoritative fallback fence for this synchronous physical cleanup.
@discardableResult
func stopNativePlayback(lease: VoiceOutputLease) -> Bool {
guard lease.lane == .nativeRealtime else { return false }
let ownsActiveLease = VoiceTurnCoordinator.shared.outputSnapshot.activeLease == lease
let ownsTerminalTurn =
VoiceTurnCoordinator.shared.activeTurnID == nil
&& VoiceTurnCoordinator.shared.model.lastTerminal?.turnID == lease.turnID
guard ownsActiveLease || ownsTerminalTurn else {
log("RealtimeHub: ignored stale native playback stop lease=\(lease.id)")
return false
}
realtimePlaybackEpoch += 1
pcmPlayer?.stop()
responseGlowGate.clearImmediately()
return true
}
}