feat(record): P4 暂停/继续 —— record pause/resume 子命令 + CaptureHandle.pause/resume
SPEC_RealtimeTranscript_UX_V1 §五: - cli.py:新增 / (状态文件 pid 定位 + SIGUSR1 通知前台进程);前台进程注册 SIGUSR1 handler 切换 handle.pause()/resume();状态文件记录 paused 供 status 显示 - audio_capture.py:CaptureHandle.pause()/resume() —— 采集回调丢弃帧 + 向 Cloud WS 发 pause/resume 控制消息(云端计费暂停);is_paused 属性 - record status 显示 ⏸️/🎙️ 状态
This commit is contained in:
@@ -99,6 +99,10 @@ class CaptureHandle:
|
||||
self._ws = None
|
||||
self._start_time = 0.0
|
||||
|
||||
# P4 暂停/继续(SPEC_RealtimeTranscript_UX_V1 §五):暂停时采集回调丢弃帧
|
||||
# + 向 Cloud 发 pause 控制消息(云端停推帧/计费暂停)
|
||||
self._paused = False
|
||||
|
||||
# 音频缓冲区(线程安全)
|
||||
self._audio_buf: bytearray = bytearray()
|
||||
self._buf_lock = threading.Lock()
|
||||
@@ -212,6 +216,8 @@ class CaptureHandle:
|
||||
def _callback(indata, frames, time_info, status):
|
||||
if status:
|
||||
logger.debug("[AudioCapture] mic status: %s", status)
|
||||
if self._paused:
|
||||
return # P4:暂停期间丢弃采集帧
|
||||
with self._buf_lock:
|
||||
self._audio_buf.extend(indata.tobytes())
|
||||
|
||||
@@ -300,6 +306,8 @@ class CaptureHandle:
|
||||
|
||||
def _on_system_audio(self, raw_bytes: bytes):
|
||||
"""系统音频回调。SCStream 输出 float32 PCM,需要转为 int16。"""
|
||||
if self._paused:
|
||||
return # P4:暂停期间丢弃采集帧
|
||||
import struct
|
||||
# float32: 每个样本 4 字节;int16: 每个样本 2 字节
|
||||
n_samples = len(raw_bytes) // 4
|
||||
@@ -314,6 +322,36 @@ class CaptureHandle:
|
||||
with self._buf_lock:
|
||||
self._audio_buf.extend(int16_data)
|
||||
|
||||
# ── P4 暂停/继续(SPEC_RealtimeTranscript_UX_V1 §五)────────────────
|
||||
|
||||
def pause(self):
|
||||
"""暂停:丢弃采集帧 + 通知 Cloud 暂停(云端计费同步暂停)。"""
|
||||
self._paused = True
|
||||
if self._ws:
|
||||
try:
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self._ws.send(json.dumps({"type": "pause"})), self._loop
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.info("[AudioCapture] 已暂停 chatId=%s", self._chat_id)
|
||||
|
||||
def resume(self):
|
||||
"""继续:恢复采集推帧 + 通知 Cloud 恢复。"""
|
||||
self._paused = False
|
||||
if self._ws:
|
||||
try:
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self._ws.send(json.dumps({"type": "resume"})), self._loop
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.info("[AudioCapture] 已继续 chatId=%s", self._chat_id)
|
||||
|
||||
@property
|
||||
def is_paused(self) -> bool:
|
||||
return self._paused
|
||||
|
||||
# ── 停止采集 ─────────────────────────────────────────────
|
||||
|
||||
def _stop_capture(self):
|
||||
|
||||
Reference in New Issue
Block a user