From b0a4e2ea93fafc4d65d474f3ada616a0ad5e19d5 Mon Sep 17 00:00:00 2001
From: Ariver <ar@Arm1.local>
Date: Sun, 12 Jul 2026 11:39:35 +0800
Subject: [PATCH] feat: bind helper stream timing markers
---
run-local.sh | 387 +++++++++++++++++++++++++++++++++---------------------
1 files changed, 237 insertions(+), 150 deletions(-)
diff --git a/run-local.sh b/run-local.sh
index 2e304e7..337bd55 100755
--- a/run-local.sh
+++ b/run-local.sh
@@ -1,159 +1,246 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
+#!/usr/bin/env bash
+set -euo pipefail
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-WORKSPACE_ROOT="${COMBRABO_VOICE_RUNTIME_HELPER_WORKDIR:-${SCRIPT_DIR}}"
+WORKSPACE_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
MANIFEST_PATH="${SCRIPT_DIR}/Cargo.toml"
BINARY_PATH="${SCRIPT_DIR}/target/release/combrabo-voice-runtime-helper"
-DOCKERFILE_PATH="${SCRIPT_DIR}/Dockerfile"
-IMAGE_NAME="${COMBRABO_VOICE_RUNTIME_HELPER_IMAGE:-combrabo-voice-runtime-helper:local}"
-MODE="${COMBRABO_VOICE_RUNTIME_HELPER_MODE:-auto}"
-
-usage() {
- cat <<'EOF'
-Usage:
- run-local.sh [--prepare] [--rebuild]
-
-Options:
- --prepare Prepare the current helper runtime mode without starting the helper.
- --rebuild Force docker image rebuild in docker mode.
-EOF
-}
-
-PREPARE_ONLY=false
-FORCE_REBUILD=false
-
-while [[ $# -gt 0 ]]; do
- case "$1" in
- --prepare)
- PREPARE_ONLY=true
- shift
- ;;
- --rebuild)
- FORCE_REBUILD=true
- shift
- ;;
- -h|--help)
- usage
- exit 0
- ;;
- *)
- echo "Unknown argument: $1" >&2
- usage >&2
- exit 1
- ;;
- esac
-done
-
-resolve_mode() {
- local requested="$1"
- if [[ "${requested}" != "auto" ]]; then
- printf '%s' "${requested}"
- return 0
- fi
- if [[ "$(uname -s)" == "Darwin" ]] && command -v docker >/dev/null 2>&1; then
- printf '%s' "docker"
- return 0
- fi
- printf '%s' "host"
-}
-
-prepare_host() {
- (cd "${SCRIPT_DIR}" && cargo build --release)
-}
-
-prepare_docker() {
- if ! command -v docker >/dev/null 2>&1; then
- echo "Docker is required for docker helper mode, but docker is not installed." >&2
- exit 1
- fi
- if [[ "${FORCE_REBUILD}" == "true" ]] || ! docker image inspect "${IMAGE_NAME}" >/dev/null 2>&1; then
- docker build -t "${IMAGE_NAME}" "${SCRIPT_DIR}"
- fi
-}
-
-rewrite_livekit_url_for_docker() {
- local url="$1"
- if [[ "${url}" =~ ^(ws|wss)://(127\.0\.0\.1|localhost)([:/].*)?$ ]]; then
- printf '%s://host.docker.internal%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[3]:-}"
- return 0
- fi
- printf '%s' "${url}"
-}
-
-run_host() {
- if [[ ! -x "${BINARY_PATH}" ]]; then
- prepare_host
- fi
- exec "${BINARY_PATH}"
-}
-
-run_docker() {
- prepare_docker
- local livekit_url="${CV_LIVEKIT_URL:-}"
- local -a docker_args
- docker_args=(
- --rm
- --name "cv-helper-${CV_CALL_ID:-manual}"
+DOCKERFILE_PATH="${SCRIPT_DIR}/Dockerfile"
+IMAGE_NAME="${COMBRABO_VOICE_RUNTIME_HELPER_IMAGE:-combrabo-voice-runtime-helper:local}"
+MODE="${COMBRABO_VOICE_RUNTIME_HELPER_MODE:-auto}"
+
+usage() {
+ cat <<'EOF'
+Usage:
+ run-local.sh [--prepare] [--rebuild]
+
+Options:
+ --prepare Prepare the current helper runtime mode without starting the helper.
+ --rebuild Force docker image rebuild in docker mode.
+EOF
+}
+
+PREPARE_ONLY=false
+FORCE_REBUILD=false
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --prepare)
+ PREPARE_ONLY=true
+ shift
+ ;;
+ --rebuild)
+ FORCE_REBUILD=true
+ shift
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo "Unknown argument: $1" >&2
+ usage >&2
+ exit 1
+ ;;
+ esac
+done
+
+resolve_mode() {
+ local requested="$1"
+ if [[ "${requested}" != "auto" ]]; then
+ printf '%s' "${requested}"
+ return 0
+ fi
+ if [[ "$(uname -s)" == "Darwin" ]] && command -v docker >/dev/null 2>&1; then
+ printf '%s' "docker"
+ return 0
+ fi
+ printf '%s' "host"
+}
+
+prepare_host() {
+ (cd "${SCRIPT_DIR}" && cargo build --release)
+}
+
+prepare_docker() {
+ if ! command -v docker >/dev/null 2>&1; then
+ echo "Docker is required for docker helper mode, but docker is not installed." >&2
+ exit 1
+ fi
+ if [[ "${FORCE_REBUILD}" == "true" ]] || ! docker image inspect "${IMAGE_NAME}" >/dev/null 2>&1; then
+ docker build -t "${IMAGE_NAME}" "${SCRIPT_DIR}"
+ fi
+}
+
+rewrite_livekit_url_for_docker() {
+ local url="$1"
+ if [[ "${url}" =~ ^(ws|wss)://(127\.0\.0\.1|localhost)([:/].*)?$ ]]; then
+ printf '%s://host.docker.internal%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[3]:-}"
+ return 0
+ fi
+ printf '%s' "${url}"
+}
+
+rewrite_http_url_for_docker() {
+ local url="$1"
+ if [[ "${url}" =~ ^(http|https)://(127\.0\.0\.1|localhost)([:/].*)?$ ]]; then
+ printf '%s://host.docker.internal%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[3]:-}"
+ return 0
+ fi
+ printf '%s' "${url}"
+}
+
+is_truthy() {
+ case "${1:-}" in
+ true|TRUE|1|yes|YES|on|ON)
+ return 0
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+}
+
+docker_service_host() {
+ local bind_addr="${1:-127.0.0.1:18080}"
+ if [[ "${bind_addr}" =~ ^([^:]+):([0-9]+)$ ]]; then
+ printf '%s' "${BASH_REMATCH[1]}"
+ return 0
+ fi
+ printf '%s' "127.0.0.1"
+}
+
+docker_service_port() {
+ local bind_addr="${1:-127.0.0.1:18080}"
+ if [[ "${bind_addr}" =~ ^([^:]+):([0-9]+)$ ]]; then
+ printf '%s' "${BASH_REMATCH[2]}"
+ return 0
+ fi
+ printf '%s' "18080"
+}
+
+run_host() {
+ if [[ ! -x "${BINARY_PATH}" ]]; then
+ prepare_host
+ fi
+ exec "${BINARY_PATH}"
+}
+
+run_docker() {
+ prepare_docker
+ local livekit_url="${CV_LIVEKIT_URL:-}"
+ local -a docker_args
+ docker_args=(
+ --rm
+ --name "cv-helper-${CV_CALL_ID:-manual}"
-v "${WORKSPACE_ROOT}:${WORKSPACE_ROOT}"
- -w "${WORKSPACE_ROOT}"
- -e CV_CALL_ID
- -e CV_TRACE_ID
- -e CV_GAME_MATCH_ID
- -e CV_LIVEKIT_URL
- -e CV_LIVEKIT_ROOM_ID
- -e CV_LIVEKIT_BOT_TOKEN
- -e CV_LIVEKIT_BOT_PARTICIPANT_IDENTITY
- -e CV_LIVEKIT_USER_PARTICIPANT_IDENTITY
- -e CV_FIRST_AUDIO_SOURCE
+ -w "${WORKSPACE_ROOT}"
+ -e CV_CALL_ID
+ -e CV_TRACE_ID
+ -e CV_GAME_MATCH_ID
+ -e CV_LIVEKIT_URL
+ -e CV_LIVEKIT_ROOM_ID
+ -e CV_LIVEKIT_BOT_TOKEN
+ -e CV_LIVEKIT_BOT_PARTICIPANT_IDENTITY
+ -e CV_LIVEKIT_USER_PARTICIPANT_IDENTITY
+ -e CV_FIRST_AUDIO_SOURCE
-e CV_GREETING_SOURCE
-e CV_GREETING_AUDIO_FILE
-e CV_GREETING_AUDIO_URL
-e CV_GREETING_TEXT
- -e CV_ROLE_ID
- -e CV_DEVICE_OUTPUT_SMOKE_ENABLED
- -e CV_DEVICE_OUTPUT_DESTINATION_IDENTITIES
+ -e CV_AUDIO_DEBUG_DUMP_DIR
+ -e CV_ENABLE_USER_AUDIO_OBSERVER
+ -e CV_ENABLE_SIMPLE_VAD
+ -e CV_VAD_RMS_THRESHOLD
+ -e CV_VAD_PEAK_THRESHOLD
+ -e CV_VAD_START_FRAMES
+ -e CV_VAD_END_SILENCE_MS
+ -e CV_VAD_MIN_SPEECH_MS
+ -e CV_VAD_MAX_TURN_MS
+ -e CV_VAD_INITIAL_IGNORE_MS
+ -e CV_VAD_GATE_UNTIL_GREETING_DONE
+ -e CV_VAD_POST_GREETING_DELAY_MS
+ -e CV_RUNTIME_TURN_BRIDGE_URL
+ -e CV_RUNTIME_TURN_BRIDGE_TOKEN
+ -e CV_RUNTIME_TURN_ARTIFACT_DIR
+ -e CV_RUNTIME_SESSION_NONCE
+ -e CV_HELPER_SERVICE_ENABLED
+ -e CV_HELPER_SERVICE_BIND
+ -e CV_HELPER_AUTH_TOKEN
+ -e CV_HELPER_AUTH_TOKEN_LOCAL_DEV
+ -e CV_TURN_BRIDGE_TOKEN_LOCAL_DEV
+ -e CV_HELPER_AUTH_TOKEN_DEV
+ -e CV_TURN_BRIDGE_TOKEN_DEV
)
- if [[ -n "${livekit_url}" ]]; then
- export CV_LIVEKIT_URL="$(rewrite_livekit_url_for_docker "${livekit_url}")"
- fi
- if [[ -n "${CV_GREETING_AUDIO_FILE:-}" && -f "${CV_GREETING_AUDIO_FILE}" ]]; then
- local audio_parent
- audio_parent="$(cd "$(dirname "${CV_GREETING_AUDIO_FILE}")" && pwd)"
- if [[ "${audio_parent}" != "${WORKSPACE_ROOT}" && "${audio_parent}" != "${WORKSPACE_ROOT}/"* ]]; then
- docker_args+=(-v "${audio_parent}:${audio_parent}:ro")
- fi
- fi
- exec docker run "${docker_args[@]}" "${IMAGE_NAME}"
-}
-
-RUNTIME_MODE="$(resolve_mode "${MODE}")"
-
-if [[ "${PREPARE_ONLY}" == "true" ]]; then
- case "${RUNTIME_MODE}" in
- host)
- prepare_host
- ;;
- docker)
- prepare_docker
- ;;
- *)
- echo "Unsupported helper mode: ${RUNTIME_MODE}" >&2
- exit 1
- ;;
- esac
- exit 0
-fi
-
-case "${RUNTIME_MODE}" in
- host)
- run_host
- ;;
- docker)
- run_docker
- ;;
- *)
- echo "Unsupported helper mode: ${RUNTIME_MODE}" >&2
- exit 1
- ;;
-esac
+ if is_truthy "${CV_HELPER_SERVICE_ENABLED:-}"; then
+ local service_bind service_host service_port
+ service_bind="${CV_HELPER_SERVICE_BIND:-127.0.0.1:18080}"
+ service_host="$(docker_service_host "${service_bind}")"
+ service_port="$(docker_service_port "${service_bind}")"
+ export CV_HELPER_SERVICE_BIND="0.0.0.0:${service_port}"
+ docker_args+=(-p "${service_host}:${service_port}:${service_port}")
+ fi
+ if [[ -n "${livekit_url}" ]]; then
+ export CV_LIVEKIT_URL="$(rewrite_livekit_url_for_docker "${livekit_url}")"
+ fi
+ if [[ -n "${CV_RUNTIME_TURN_BRIDGE_URL:-}" ]]; then
+ export CV_RUNTIME_TURN_BRIDGE_URL="$(rewrite_http_url_for_docker "${CV_RUNTIME_TURN_BRIDGE_URL}")"
+ fi
+ if [[ -n "${CV_GREETING_AUDIO_FILE:-}" && -f "${CV_GREETING_AUDIO_FILE}" ]]; then
+ local audio_parent
+ audio_parent="$(cd "$(dirname "${CV_GREETING_AUDIO_FILE}")" && pwd)"
+ if [[ "${audio_parent}" != "${WORKSPACE_ROOT}" && "${audio_parent}" != "${WORKSPACE_ROOT}/"* ]]; then
+ docker_args+=(-v "${audio_parent}:${audio_parent}:ro")
+ fi
+ fi
+ if [[ -n "${CV_AUDIO_DEBUG_DUMP_DIR:-}" ]]; then
+ mkdir -p "${CV_AUDIO_DEBUG_DUMP_DIR}"
+ local debug_dump_dir
+ debug_dump_dir="$(cd "${CV_AUDIO_DEBUG_DUMP_DIR}" && pwd)"
+ export CV_AUDIO_DEBUG_DUMP_DIR="${debug_dump_dir}"
+ if [[ "${debug_dump_dir}" != "${WORKSPACE_ROOT}" && "${debug_dump_dir}" != "${WORKSPACE_ROOT}/"* ]]; then
+ docker_args+=(-v "${debug_dump_dir}:${debug_dump_dir}")
+ fi
+ fi
+ if [[ -n "${CV_RUNTIME_TURN_ARTIFACT_DIR:-}" ]]; then
+ mkdir -p "${CV_RUNTIME_TURN_ARTIFACT_DIR}"
+ local turn_artifact_dir
+ turn_artifact_dir="$(cd "${CV_RUNTIME_TURN_ARTIFACT_DIR}" && pwd)"
+ export CV_RUNTIME_TURN_ARTIFACT_DIR="${turn_artifact_dir}"
+ if [[ "${turn_artifact_dir}" != "${WORKSPACE_ROOT}" && "${turn_artifact_dir}" != "${WORKSPACE_ROOT}/"* ]]; then
+ docker_args+=(-v "${turn_artifact_dir}:${turn_artifact_dir}")
+ fi
+ fi
+ exec docker run "${docker_args[@]}" "${IMAGE_NAME}"
+}
+
+RUNTIME_MODE="$(resolve_mode "${MODE}")"
+
+if [[ "${PREPARE_ONLY}" == "true" ]]; then
+ case "${RUNTIME_MODE}" in
+ host)
+ prepare_host
+ ;;
+ docker)
+ prepare_docker
+ ;;
+ *)
+ echo "Unsupported helper mode: ${RUNTIME_MODE}" >&2
+ exit 1
+ ;;
+ esac
+ exit 0
+fi
+
+case "${RUNTIME_MODE}" in
+ host)
+ run_host
+ ;;
+ docker)
+ run_docker
+ ;;
+ *)
+ echo "Unsupported helper mode: ${RUNTIME_MODE}" >&2
+ exit 1
+ ;;
+esac
--
Gitblit v1.9.3