@pipedog/core 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/NOTICE +9 -0
- package/dist/adapters/base_llm_adapter.d.ts +266 -0
- package/dist/adapters/base_llm_adapter.js +489 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +8 -0
- package/dist/adapters/schemas/direct_function.d.ts +202 -0
- package/dist/adapters/schemas/direct_function.js +302 -0
- package/dist/adapters/schemas/function_schema.d.ts +67 -0
- package/dist/adapters/schemas/function_schema.js +72 -0
- package/dist/adapters/schemas/index.d.ts +2 -0
- package/dist/adapters/schemas/index.js +8 -0
- package/dist/adapters/schemas/tools_schema.d.ts +57 -0
- package/dist/adapters/schemas/tools_schema.js +79 -0
- package/dist/adapters/services/index.d.ts +2 -0
- package/dist/adapters/services/index.js +8 -0
- package/dist/adapters/services/open_ai_adapter.d.ts +147 -0
- package/dist/adapters/services/open_ai_adapter.js +250 -0
- package/dist/audio/dtmf/dtmf_data.d.ts +3 -0
- package/dist/audio/dtmf/dtmf_data.js +22 -0
- package/dist/audio/dtmf/index.d.ts +2 -0
- package/dist/audio/dtmf/index.js +8 -0
- package/dist/audio/dtmf/types.d.ts +31 -0
- package/dist/audio/dtmf/types.js +35 -0
- package/dist/audio/dtmf/utils.d.ts +13 -0
- package/dist/audio/dtmf/utils.js +62 -0
- package/dist/audio/filters/base_audio_filter.d.ts +51 -0
- package/dist/audio/filters/base_audio_filter.js +17 -0
- package/dist/audio/filters/index.d.ts +2 -0
- package/dist/audio/filters/index.js +8 -0
- package/dist/audio/index.d.ts +2 -0
- package/dist/audio/index.js +8 -0
- package/dist/audio/krisp_instance.d.ts +95 -0
- package/dist/audio/krisp_instance.js +229 -0
- package/dist/audio/mixers/base_audio_mixer.d.ts +56 -0
- package/dist/audio/mixers/base_audio_mixer.js +16 -0
- package/dist/audio/mixers/index.d.ts +2 -0
- package/dist/audio/mixers/index.js +8 -0
- package/dist/audio/resamplers/base_audio_resampler.d.ts +46 -0
- package/dist/audio/resamplers/base_audio_resampler.js +35 -0
- package/dist/audio/resamplers/index.d.ts +2 -0
- package/dist/audio/resamplers/index.js +8 -0
- package/dist/audio/resamplers/resampy_resampler.d.ts +10 -0
- package/dist/audio/resamplers/resampy_resampler.js +15 -0
- package/dist/audio/resamplers/soxr_resampler.d.ts +54 -0
- package/dist/audio/resamplers/soxr_resampler.js +69 -0
- package/dist/audio/resamplers/soxr_stream_resampler.d.ts +75 -0
- package/dist/audio/resamplers/soxr_stream_resampler.js +146 -0
- package/dist/audio/turn/base_turn_analyzer.d.ts +84 -0
- package/dist/audio/turn/base_turn_analyzer.js +61 -0
- package/dist/audio/turn/index.d.ts +2 -0
- package/dist/audio/turn/index.js +8 -0
- package/dist/audio/turn/smart_turn/_whisper_features.d.ts +30 -0
- package/dist/audio/turn/smart_turn/_whisper_features.js +225 -0
- package/dist/audio/turn/smart_turn/base_smart_turn.d.ts +94 -0
- package/dist/audio/turn/smart_turn/base_smart_turn.js +239 -0
- package/dist/audio/turn/smart_turn/local_smart_turn_v3.d.ts +76 -0
- package/dist/audio/turn/smart_turn/local_smart_turn_v3.js +143 -0
- package/dist/audio/utils.d.ts +224 -0
- package/dist/audio/utils.js +634 -0
- package/dist/audio/vad/data/index.d.ts +2 -0
- package/dist/audio/vad/data/index.js +8 -0
- package/dist/audio/vad/index.d.ts +2 -0
- package/dist/audio/vad/index.js +8 -0
- package/dist/audio/vad/vad_analyzer.d.ts +126 -0
- package/dist/audio/vad/vad_analyzer.js +211 -0
- package/dist/audio/vad/vad_controller.d.ts +143 -0
- package/dist/audio/vad/vad_controller.js +228 -0
- package/dist/audio/volume.d.ts +38 -0
- package/dist/audio/volume.js +75 -0
- package/dist/bus/adapters/base.d.ts +35 -0
- package/dist/bus/adapters/base.js +19 -0
- package/dist/bus/adapters/index.d.ts +10 -0
- package/dist/bus/adapters/index.js +15 -0
- package/dist/bus/adapters/llm_context_adapter.d.ts +36 -0
- package/dist/bus/adapters/llm_context_adapter.js +82 -0
- package/dist/bus/adapters/tools_schema_adapter.d.ts +25 -0
- package/dist/bus/adapters/tools_schema_adapter.js +70 -0
- package/dist/bus/bridge_processor.d.ts +111 -0
- package/dist/bus/bridge_processor.js +166 -0
- package/dist/bus/bus.d.ts +110 -0
- package/dist/bus/bus.js +200 -0
- package/dist/bus/index.d.ts +22 -0
- package/dist/bus/index.js +27 -0
- package/dist/bus/local/async_queue.d.ts +12 -0
- package/dist/bus/local/async_queue.js +21 -0
- package/dist/bus/local/index.d.ts +3 -0
- package/dist/bus/local/index.js +8 -0
- package/dist/bus/messages.d.ts +378 -0
- package/dist/bus/messages.js +357 -0
- package/dist/bus/network/index.d.ts +16 -0
- package/dist/bus/network/index.js +21 -0
- package/dist/bus/queue.d.ts +27 -0
- package/dist/bus/queue.js +51 -0
- package/dist/bus/serializers/base.d.ts +25 -0
- package/dist/bus/serializers/base.js +14 -0
- package/dist/bus/serializers/index.d.ts +9 -0
- package/dist/bus/serializers/index.js +14 -0
- package/dist/bus/serializers/json.d.ts +77 -0
- package/dist/bus/serializers/json.js +367 -0
- package/dist/bus/subscriber.d.ts +46 -0
- package/dist/bus/subscriber.js +27 -0
- package/dist/bus/ui/index.d.ts +3 -0
- package/dist/bus/ui/index.js +8 -0
- package/dist/bus/ui/messages.d.ts +186 -0
- package/dist/bus/ui/messages.js +171 -0
- package/dist/classifiers/base_classifier.d.ts +263 -0
- package/dist/classifiers/base_classifier.js +346 -0
- package/dist/classifiers/cloudflare/clef/classifier.d.ts +70 -0
- package/dist/classifiers/cloudflare/clef/classifier.js +204 -0
- package/dist/classifiers/cloudflare/clef/client.d.ts +104 -0
- package/dist/classifiers/cloudflare/clef/client.js +234 -0
- package/dist/classifiers/cloudflare/clef/index.d.ts +2 -0
- package/dist/classifiers/cloudflare/clef/index.js +8 -0
- package/dist/classifiers/cloudflare/index.d.ts +2 -0
- package/dist/classifiers/cloudflare/index.js +8 -0
- package/dist/classifiers/http_client.d.ts +70 -0
- package/dist/classifiers/http_client.js +161 -0
- package/dist/classifiers/index.d.ts +2 -0
- package/dist/classifiers/index.js +8 -0
- package/dist/classifiers/jev/classifier.d.ts +8 -0
- package/dist/classifiers/jev/classifier.js +13 -0
- package/dist/classifiers/jev/client.d.ts +8 -0
- package/dist/classifiers/jev/client.js +13 -0
- package/dist/classifiers/jev/index.d.ts +2 -0
- package/dist/classifiers/jev/index.js +8 -0
- package/dist/classifiers/llm/classifier.d.ts +72 -0
- package/dist/classifiers/llm/classifier.js +304 -0
- package/dist/classifiers/llm/index.d.ts +2 -0
- package/dist/classifiers/llm/index.js +8 -0
- package/dist/classifiers/openai/decisions/classifier.d.ts +77 -0
- package/dist/classifiers/openai/decisions/classifier.js +241 -0
- package/dist/classifiers/openai/decisions/client.d.ts +108 -0
- package/dist/classifiers/openai/decisions/client.js +234 -0
- package/dist/classifiers/openai/decisions/index.d.ts +2 -0
- package/dist/classifiers/openai/decisions/index.js +8 -0
- package/dist/classifiers/openai/index.d.ts +2 -0
- package/dist/classifiers/openai/index.js +8 -0
- package/dist/classifiers/typesafe/index.d.ts +2 -0
- package/dist/classifiers/typesafe/index.js +8 -0
- package/dist/classifiers/typesafe/jev/classifier.d.ts +70 -0
- package/dist/classifiers/typesafe/jev/classifier.js +220 -0
- package/dist/classifiers/typesafe/jev/client.d.ts +105 -0
- package/dist/classifiers/typesafe/jev/client.js +233 -0
- package/dist/classifiers/typesafe/jev/index.d.ts +2 -0
- package/dist/classifiers/typesafe/jev/index.js +8 -0
- package/dist/clocks/base_clock.d.ts +13 -0
- package/dist/clocks/base_clock.js +14 -0
- package/dist/clocks/index.d.ts +2 -0
- package/dist/clocks/index.js +8 -0
- package/dist/clocks/system_clock.d.ts +15 -0
- package/dist/clocks/system_clock.js +25 -0
- package/dist/frames/frames.d.ts +2533 -0
- package/dist/frames/frames.js +2485 -0
- package/dist/frames/index.d.ts +2 -0
- package/dist/frames/index.js +8 -0
- package/dist/frames/protobufs/frames_pb2.d.ts +229 -0
- package/dist/frames/protobufs/frames_pb2.js +466 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +56 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.js +8 -0
- package/dist/metrics/metrics.d.ts +276 -0
- package/dist/metrics/metrics.js +254 -0
- package/dist/observers/base_observer.d.ts +173 -0
- package/dist/observers/base_observer.js +151 -0
- package/dist/observers/error_observer.d.ts +75 -0
- package/dist/observers/error_observer.js +87 -0
- package/dist/observers/function_call_observer.d.ts +157 -0
- package/dist/observers/function_call_observer.js +222 -0
- package/dist/observers/index.d.ts +2 -0
- package/dist/observers/index.js +8 -0
- package/dist/observers/loggers/debug_log_observer.d.ts +91 -0
- package/dist/observers/loggers/debug_log_observer.js +228 -0
- package/dist/observers/loggers/index.d.ts +2 -0
- package/dist/observers/loggers/index.js +8 -0
- package/dist/observers/loggers/llm_log_observer.d.ts +24 -0
- package/dist/observers/loggers/llm_log_observer.js +110 -0
- package/dist/observers/loggers/metrics_log_observer.d.ts +85 -0
- package/dist/observers/loggers/metrics_log_observer.js +186 -0
- package/dist/observers/loggers/transcription_log_observer.d.ts +23 -0
- package/dist/observers/loggers/transcription_log_observer.js +74 -0
- package/dist/observers/service_metrics_observer.d.ts +168 -0
- package/dist/observers/service_metrics_observer.js +236 -0
- package/dist/observers/speaking_observer.d.ts +104 -0
- package/dist/observers/speaking_observer.js +150 -0
- package/dist/observers/startup_timing_observer.d.ts +221 -0
- package/dist/observers/startup_timing_observer.js +365 -0
- package/dist/observers/turn_tracking_observer.d.ts +78 -0
- package/dist/observers/turn_tracking_observer.js +189 -0
- package/dist/observers/user_bot_latency_observer.d.ts +411 -0
- package/dist/observers/user_bot_latency_observer.js +974 -0
- package/dist/pipeline/base_pipeline.d.ts +49 -0
- package/dist/pipeline/base_pipeline.js +101 -0
- package/dist/pipeline/capabilities.d.ts +76 -0
- package/dist/pipeline/capabilities.js +97 -0
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +8 -0
- package/dist/pipeline/job_context.d.ts +317 -0
- package/dist/pipeline/job_context.js +450 -0
- package/dist/pipeline/job_decorator.d.ts +54 -0
- package/dist/pipeline/job_decorator.js +92 -0
- package/dist/pipeline/llm_switcher.d.ts +84 -0
- package/dist/pipeline/llm_switcher.js +107 -0
- package/dist/pipeline/llm_with_backend.d.ts +435 -0
- package/dist/pipeline/llm_with_backend.js +806 -0
- package/dist/pipeline/parallel_pipeline.d.ts +83 -0
- package/dist/pipeline/parallel_pipeline.js +200 -0
- package/dist/pipeline/pipeline.d.ts +126 -0
- package/dist/pipeline/pipeline.js +196 -0
- package/dist/pipeline/service_switcher.d.ts +287 -0
- package/dist/pipeline/service_switcher.js +509 -0
- package/dist/pipeline/sync_parallel_pipeline.d.ts +192 -0
- package/dist/pipeline/sync_parallel_pipeline.js +387 -0
- package/dist/pipeline/worker.d.ts +814 -0
- package/dist/pipeline/worker.js +1639 -0
- package/dist/pipeline/worker_observer.d.ts +131 -0
- package/dist/pipeline/worker_observer.js +274 -0
- package/dist/pipeline/worker_ready_decorator.d.ts +45 -0
- package/dist/pipeline/worker_ready_decorator.js +76 -0
- package/dist/processors/aggregators/async_tool_messages.d.ts +146 -0
- package/dist/processors/aggregators/async_tool_messages.js +291 -0
- package/dist/processors/aggregators/dtmf_aggregator.d.ts +61 -0
- package/dist/processors/aggregators/dtmf_aggregator.js +144 -0
- package/dist/processors/aggregators/gated.d.ts +43 -0
- package/dist/processors/aggregators/gated.js +86 -0
- package/dist/processors/aggregators/gated_llm_context.d.ts +42 -0
- package/dist/processors/aggregators/gated_llm_context.js +86 -0
- package/dist/processors/aggregators/index.d.ts +2 -0
- package/dist/processors/aggregators/index.js +8 -0
- package/dist/processors/aggregators/llm_context.d.ts +370 -0
- package/dist/processors/aggregators/llm_context.js +580 -0
- package/dist/processors/aggregators/llm_context_summarizer.d.ts +219 -0
- package/dist/processors/aggregators/llm_context_summarizer.js +447 -0
- package/dist/processors/aggregators/llm_response.d.ts +37 -0
- package/dist/processors/aggregators/llm_response.js +71 -0
- package/dist/processors/aggregators/llm_response_universal.d.ts +966 -0
- package/dist/processors/aggregators/llm_response_universal.js +2394 -0
- package/dist/processors/aggregators/llm_text_processor.d.ts +53 -0
- package/dist/processors/aggregators/llm_text_processor.js +102 -0
- package/dist/processors/aggregators/sentence.d.ts +39 -0
- package/dist/processors/aggregators/sentence.js +69 -0
- package/dist/processors/async_generator.d.ts +42 -0
- package/dist/processors/async_generator.js +76 -0
- package/dist/processors/audio/audio_buffer_processor.d.ts +241 -0
- package/dist/processors/audio/audio_buffer_processor.js +565 -0
- package/dist/processors/audio/index.d.ts +2 -0
- package/dist/processors/audio/index.js +8 -0
- package/dist/processors/audio/vad_processor.d.ts +67 -0
- package/dist/processors/audio/vad_processor.js +103 -0
- package/dist/processors/consumer_processor.d.ts +49 -0
- package/dist/processors/consumer_processor.js +85 -0
- package/dist/processors/filters/frame_filter.d.ts +39 -0
- package/dist/processors/filters/frame_filter.js +52 -0
- package/dist/processors/filters/function_filter.d.ts +59 -0
- package/dist/processors/filters/function_filter.js +78 -0
- package/dist/processors/filters/identity_filter.d.ts +33 -0
- package/dist/processors/filters/identity_filter.js +37 -0
- package/dist/processors/filters/index.d.ts +2 -0
- package/dist/processors/filters/index.js +8 -0
- package/dist/processors/filters/null_filter.d.ts +34 -0
- package/dist/processors/filters/null_filter.js +47 -0
- package/dist/processors/filters/wake_notifier_filter.d.ts +48 -0
- package/dist/processors/filters/wake_notifier_filter.js +49 -0
- package/dist/processors/frame_processor.d.ts +360 -0
- package/dist/processors/frame_processor.js +932 -0
- package/dist/processors/frameworks/index.d.ts +2 -0
- package/dist/processors/frameworks/index.js +8 -0
- package/dist/processors/frameworks/rtvi/frames.d.ts +241 -0
- package/dist/processors/frameworks/rtvi/frames.js +259 -0
- package/dist/processors/frameworks/rtvi/index.d.ts +6 -0
- package/dist/processors/frameworks/rtvi/index.js +11 -0
- package/dist/processors/frameworks/rtvi/models.d.ts +1741 -0
- package/dist/processors/frameworks/rtvi/models.js +1807 -0
- package/dist/processors/frameworks/rtvi/observer.d.ts +340 -0
- package/dist/processors/frameworks/rtvi/observer.js +1113 -0
- package/dist/processors/frameworks/rtvi/processor.d.ts +182 -0
- package/dist/processors/frameworks/rtvi/processor.js +676 -0
- package/dist/processors/idle_frame_processor.d.ts +44 -0
- package/dist/processors/idle_frame_processor.js +86 -0
- package/dist/processors/index.d.ts +2 -0
- package/dist/processors/index.js +8 -0
- package/dist/processors/logger.d.ts +42 -0
- package/dist/processors/logger.js +87 -0
- package/dist/processors/metrics/frame_processor_metrics.d.ts +172 -0
- package/dist/processors/metrics/frame_processor_metrics.js +372 -0
- package/dist/processors/metrics/index.d.ts +2 -0
- package/dist/processors/metrics/index.js +8 -0
- package/dist/processors/producer_processor.d.ts +68 -0
- package/dist/processors/producer_processor.js +78 -0
- package/dist/processors/text_transformer.d.ts +30 -0
- package/dist/processors/text_transformer.js +48 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.js +8 -0
- package/dist/registry/registry.d.ts +69 -0
- package/dist/registry/registry.js +149 -0
- package/dist/registry/types.d.ts +47 -0
- package/dist/registry/types.js +39 -0
- package/dist/serializers/base_serializer.d.ts +94 -0
- package/dist/serializers/base_serializer.js +82 -0
- package/dist/serializers/index.d.ts +2 -0
- package/dist/serializers/index.js +8 -0
- package/dist/services/ai_service.d.ts +145 -0
- package/dist/services/ai_service.js +262 -0
- package/dist/services/anthropic/index.d.ts +2 -0
- package/dist/services/anthropic/index.js +7 -0
- package/dist/services/assemblyai/index.d.ts +2 -0
- package/dist/services/assemblyai/index.js +7 -0
- package/dist/services/asyncai/index.d.ts +2 -0
- package/dist/services/asyncai/index.js +7 -0
- package/dist/services/aws/index.d.ts +2 -0
- package/dist/services/aws/index.js +7 -0
- package/dist/services/azure/index.d.ts +2 -0
- package/dist/services/azure/index.js +7 -0
- package/dist/services/baseten/index.d.ts +2 -0
- package/dist/services/baseten/index.js +7 -0
- package/dist/services/bland/index.d.ts +2 -0
- package/dist/services/bland/index.js +7 -0
- package/dist/services/camb/index.d.ts +2 -0
- package/dist/services/camb/index.js +7 -0
- package/dist/services/cartesia/index.d.ts +2 -0
- package/dist/services/cartesia/index.js +7 -0
- package/dist/services/cerebras/index.d.ts +2 -0
- package/dist/services/cerebras/index.js +7 -0
- package/dist/services/crusoe/index.d.ts +2 -0
- package/dist/services/crusoe/index.js +7 -0
- package/dist/services/deepgram/index.d.ts +2 -0
- package/dist/services/deepgram/index.js +7 -0
- package/dist/services/deepseek/index.d.ts +2 -0
- package/dist/services/deepseek/index.js +7 -0
- package/dist/services/elevenlabs/index.d.ts +2 -0
- package/dist/services/elevenlabs/index.js +7 -0
- package/dist/services/fal/index.d.ts +2 -0
- package/dist/services/fal/index.js +7 -0
- package/dist/services/fireworks/index.d.ts +2 -0
- package/dist/services/fireworks/index.js +7 -0
- package/dist/services/fish/index.d.ts +2 -0
- package/dist/services/fish/index.js +7 -0
- package/dist/services/funasr/index.d.ts +3 -0
- package/dist/services/funasr/index.js +8 -0
- package/dist/services/gladia/index.d.ts +2 -0
- package/dist/services/gladia/index.js +7 -0
- package/dist/services/google/index.d.ts +2 -0
- package/dist/services/google/index.js +7 -0
- package/dist/services/gradium/index.d.ts +2 -0
- package/dist/services/gradium/index.js +7 -0
- package/dist/services/grok/index.d.ts +2 -0
- package/dist/services/grok/index.js +7 -0
- package/dist/services/groq/index.d.ts +2 -0
- package/dist/services/groq/index.js +7 -0
- package/dist/services/heygen/index.d.ts +2 -0
- package/dist/services/heygen/index.js +7 -0
- package/dist/services/hume/index.d.ts +2 -0
- package/dist/services/hume/index.js +7 -0
- package/dist/services/image_service.d.ts +52 -0
- package/dist/services/image_service.js +60 -0
- package/dist/services/inception/index.d.ts +2 -0
- package/dist/services/inception/index.js +7 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +8 -0
- package/dist/services/inworld/index.d.ts +2 -0
- package/dist/services/inworld/index.js +7 -0
- package/dist/services/keenable/index.d.ts +2 -0
- package/dist/services/keenable/index.js +7 -0
- package/dist/services/kokoro/index.d.ts +2 -0
- package/dist/services/kokoro/index.js +7 -0
- package/dist/services/llm_service.d.ts +1106 -0
- package/dist/services/llm_service.js +2231 -0
- package/dist/services/mem0/index.d.ts +2 -0
- package/dist/services/mem0/index.js +7 -0
- package/dist/services/meta/index.d.ts +2 -0
- package/dist/services/meta/index.js +7 -0
- package/dist/services/minimax/index.d.ts +2 -0
- package/dist/services/minimax/index.js +7 -0
- package/dist/services/mistral/index.d.ts +2 -0
- package/dist/services/mistral/index.js +7 -0
- package/dist/services/moondream/index.d.ts +2 -0
- package/dist/services/moondream/index.js +7 -0
- package/dist/services/moonshine/index.d.ts +2 -0
- package/dist/services/moonshine/index.js +7 -0
- package/dist/services/nebius/index.d.ts +2 -0
- package/dist/services/nebius/index.js +7 -0
- package/dist/services/neuphonic/index.d.ts +2 -0
- package/dist/services/neuphonic/index.js +7 -0
- package/dist/services/novita/index.d.ts +2 -0
- package/dist/services/novita/index.js +7 -0
- package/dist/services/nvidia/index.d.ts +2 -0
- package/dist/services/nvidia/index.js +11 -0
- package/dist/services/ollama/index.d.ts +2 -0
- package/dist/services/ollama/index.js +7 -0
- package/dist/services/openai/index.d.ts +2 -0
- package/dist/services/openai/index.js +7 -0
- package/dist/services/openclaw/index.d.ts +14 -0
- package/dist/services/openclaw/index.js +19 -0
- package/dist/services/openrouter/index.d.ts +2 -0
- package/dist/services/openrouter/index.js +7 -0
- package/dist/services/perplexity/index.d.ts +2 -0
- package/dist/services/perplexity/index.js +7 -0
- package/dist/services/piper/index.d.ts +2 -0
- package/dist/services/piper/index.js +7 -0
- package/dist/services/pocket_tts/index.d.ts +2 -0
- package/dist/services/pocket_tts/index.js +7 -0
- package/dist/services/qwen/index.d.ts +2 -0
- package/dist/services/qwen/index.js +7 -0
- package/dist/services/resembleai/index.d.ts +2 -0
- package/dist/services/resembleai/index.js +7 -0
- package/dist/services/rime/index.d.ts +2 -0
- package/dist/services/rime/index.js +7 -0
- package/dist/services/sambanova/index.d.ts +2 -0
- package/dist/services/sambanova/index.js +7 -0
- package/dist/services/sarvam/index.d.ts +2 -0
- package/dist/services/sarvam/index.js +7 -0
- package/dist/services/settings.d.ts +356 -0
- package/dist/services/settings.js +437 -0
- package/dist/services/simli/index.d.ts +2 -0
- package/dist/services/simli/index.js +7 -0
- package/dist/services/smallest/index.d.ts +2 -0
- package/dist/services/smallest/index.js +7 -0
- package/dist/services/soniox/index.d.ts +2 -0
- package/dist/services/soniox/index.js +7 -0
- package/dist/services/speechify/index.d.ts +2 -0
- package/dist/services/speechify/index.js +7 -0
- package/dist/services/speechmatics/index.d.ts +2 -0
- package/dist/services/speechmatics/index.js +7 -0
- package/dist/services/stt_latency.d.ts +62 -0
- package/dist/services/stt_latency.js +71 -0
- package/dist/services/stt_service.d.ts +601 -0
- package/dist/services/stt_service.js +1044 -0
- package/dist/services/tavus/index.d.ts +2 -0
- package/dist/services/tavus/index.js +7 -0
- package/dist/services/together/index.d.ts +2 -0
- package/dist/services/together/index.js +7 -0
- package/dist/services/tts_service.d.ts +909 -0
- package/dist/services/tts_service.js +2018 -0
- package/dist/services/ultravox/index.d.ts +2 -0
- package/dist/services/ultravox/index.js +9 -0
- package/dist/services/vision_service.d.ts +56 -0
- package/dist/services/vision_service.js +62 -0
- package/dist/services/websocket_service.d.ts +213 -0
- package/dist/services/websocket_service.js +654 -0
- package/dist/services/whisper/index.d.ts +2 -0
- package/dist/services/whisper/index.js +9 -0
- package/dist/services/xai/index.d.ts +2 -0
- package/dist/services/xai/index.js +9 -0
- package/dist/services/xtts/index.d.ts +2 -0
- package/dist/services/xtts/index.js +9 -0
- package/dist/tests/index.d.ts +2 -0
- package/dist/tests/index.js +8 -0
- package/dist/tests/utils.d.ts +123 -0
- package/dist/tests/utils.js +212 -0
- package/dist/transcriptions/index.d.ts +2 -0
- package/dist/transcriptions/index.js +8 -0
- package/dist/transcriptions/language.d.ts +376 -0
- package/dist/transcriptions/language.js +543 -0
- package/dist/transports/base_input.d.ts +149 -0
- package/dist/transports/base_input.js +338 -0
- package/dist/transports/base_output.d.ts +432 -0
- package/dist/transports/base_output.js +1231 -0
- package/dist/transports/base_transport.d.ts +223 -0
- package/dist/transports/base_transport.js +167 -0
- package/dist/transports/daily/index.d.ts +2 -0
- package/dist/transports/daily/index.js +9 -0
- package/dist/transports/heygen/index.d.ts +2 -0
- package/dist/transports/heygen/index.js +9 -0
- package/dist/transports/index.d.ts +2 -0
- package/dist/transports/index.js +8 -0
- package/dist/transports/lemonslice/index.d.ts +2 -0
- package/dist/transports/lemonslice/index.js +9 -0
- package/dist/transports/livekit/index.d.ts +2 -0
- package/dist/transports/livekit/index.js +9 -0
- package/dist/transports/local/index.d.ts +2 -0
- package/dist/transports/local/index.js +9 -0
- package/dist/transports/moq/index.d.ts +2 -0
- package/dist/transports/moq/index.js +9 -0
- package/dist/transports/smallwebrtc/index.d.ts +2 -0
- package/dist/transports/smallwebrtc/index.js +9 -0
- package/dist/transports/tavus/index.d.ts +2 -0
- package/dist/transports/tavus/index.js +9 -0
- package/dist/transports/video_in_sampler.d.ts +157 -0
- package/dist/transports/video_in_sampler.js +258 -0
- package/dist/transports/vonage/index.d.ts +2 -0
- package/dist/transports/vonage/index.js +9 -0
- package/dist/transports/websocket/index.d.ts +2 -0
- package/dist/transports/websocket/index.js +9 -0
- package/dist/transports/whatsapp/index.d.ts +2 -0
- package/dist/transports/whatsapp/index.js +9 -0
- package/dist/turns/eager_end_of_turn_mixin.d.ts +70 -0
- package/dist/turns/eager_end_of_turn_mixin.js +122 -0
- package/dist/turns/empty_user_turn.d.ts +55 -0
- package/dist/turns/empty_user_turn.js +53 -0
- package/dist/turns/index.d.ts +2 -0
- package/dist/turns/index.js +8 -0
- package/dist/turns/speculation_gate.d.ts +169 -0
- package/dist/turns/speculation_gate.js +294 -0
- package/dist/turns/types.d.ts +33 -0
- package/dist/turns/types.js +35 -0
- package/dist/turns/user_idle_controller.d.ts +94 -0
- package/dist/turns/user_idle_controller.js +187 -0
- package/dist/turns/user_mute/always_user_mute_strategy.d.ts +15 -0
- package/dist/turns/user_mute/always_user_mute_strategy.js +29 -0
- package/dist/turns/user_mute/base_user_mute_strategy.d.ts +39 -0
- package/dist/turns/user_mute/base_user_mute_strategy.js +45 -0
- package/dist/turns/user_mute/first_speech_user_mute_strategy.d.ts +28 -0
- package/dist/turns/user_mute/first_speech_user_mute_strategy.js +52 -0
- package/dist/turns/user_mute/function_call_user_mute_strategy.d.ts +25 -0
- package/dist/turns/user_mute/function_call_user_mute_strategy.js +50 -0
- package/dist/turns/user_mute/index.d.ts +6 -0
- package/dist/turns/user_mute/index.js +11 -0
- package/dist/turns/user_mute/mute_until_first_bot_complete_user_mute_strategy.d.ts +43 -0
- package/dist/turns/user_mute/mute_until_first_bot_complete_user_mute_strategy.js +76 -0
- package/dist/turns/user_start/base_user_turn_start_strategy.d.ts +135 -0
- package/dist/turns/user_start/base_user_turn_start_strategy.js +134 -0
- package/dist/turns/user_start/external_user_turn_start_strategy.d.ts +45 -0
- package/dist/turns/user_start/external_user_turn_start_strategy.js +64 -0
- package/dist/turns/user_start/index.d.ts +8 -0
- package/dist/turns/user_start/index.js +16 -0
- package/dist/turns/user_start/krisp_viva_ip_user_turn_start_strategy.d.ts +133 -0
- package/dist/turns/user_start/krisp_viva_ip_user_turn_start_strategy.js +243 -0
- package/dist/turns/user_start/min_words_user_turn_start_strategy.d.ts +60 -0
- package/dist/turns/user_start/min_words_user_turn_start_strategy.js +102 -0
- package/dist/turns/user_start/transcription_user_turn_start_strategy.d.ts +29 -0
- package/dist/turns/user_start/transcription_user_turn_start_strategy.js +43 -0
- package/dist/turns/user_start/vad_user_turn_start_strategy.d.ts +20 -0
- package/dist/turns/user_start/vad_user_turn_start_strategy.js +31 -0
- package/dist/turns/user_start/wake_phrase_user_turn_start_strategy.d.ts +141 -0
- package/dist/turns/user_start/wake_phrase_user_turn_start_strategy.js +238 -0
- package/dist/turns/user_stop/base_user_turn_stop_strategy.d.ts +174 -0
- package/dist/turns/user_stop/base_user_turn_stop_strategy.js +170 -0
- package/dist/turns/user_stop/deferred_user_turn_stop_strategy.d.ts +81 -0
- package/dist/turns/user_stop/deferred_user_turn_stop_strategy.js +107 -0
- package/dist/turns/user_stop/eager_match_policy.d.ts +36 -0
- package/dist/turns/user_stop/eager_match_policy.js +50 -0
- package/dist/turns/user_stop/eager_user_turn_stop_strategy.d.ts +84 -0
- package/dist/turns/user_stop/eager_user_turn_stop_strategy.js +154 -0
- package/dist/turns/user_stop/external_user_turn_completion_stop_strategy.d.ts +45 -0
- package/dist/turns/user_stop/external_user_turn_completion_stop_strategy.js +55 -0
- package/dist/turns/user_stop/external_user_turn_stop_strategy.d.ts +115 -0
- package/dist/turns/user_stop/external_user_turn_stop_strategy.js +205 -0
- package/dist/turns/user_stop/index.d.ts +10 -0
- package/dist/turns/user_stop/index.js +15 -0
- package/dist/turns/user_stop/llm_turn_completion_user_turn_stop_strategy.d.ts +44 -0
- package/dist/turns/user_stop/llm_turn_completion_user_turn_stop_strategy.js +59 -0
- package/dist/turns/user_stop/speech_timeout_user_turn_stop_strategy.d.ts +140 -0
- package/dist/turns/user_stop/speech_timeout_user_turn_stop_strategy.js +303 -0
- package/dist/turns/user_stop/turn_analyzer_user_turn_stop_strategy.d.ts +135 -0
- package/dist/turns/user_stop/turn_analyzer_user_turn_stop_strategy.js +332 -0
- package/dist/turns/user_turn_completion_mixin.d.ts +147 -0
- package/dist/turns/user_turn_completion_mixin.js +591 -0
- package/dist/turns/user_turn_controller.d.ts +134 -0
- package/dist/turns/user_turn_controller.js +335 -0
- package/dist/turns/user_turn_processor.d.ts +87 -0
- package/dist/turns/user_turn_processor.js +180 -0
- package/dist/turns/user_turn_strategies.d.ts +187 -0
- package/dist/turns/user_turn_strategies.js +222 -0
- package/dist/utils/async_tool_cancellation.d.ts +35 -0
- package/dist/utils/async_tool_cancellation.js +78 -0
- package/dist/utils/asyncio/index.d.ts +2 -0
- package/dist/utils/asyncio/index.js +7 -0
- package/dist/utils/asyncio/task_manager.d.ts +49 -0
- package/dist/utils/asyncio/task_manager.js +105 -0
- package/dist/utils/base_object.d.ts +76 -0
- package/dist/utils/base_object.js +154 -0
- package/dist/utils/context/aggregated_frame_sequencer.d.ts +491 -0
- package/dist/utils/context/aggregated_frame_sequencer.js +803 -0
- package/dist/utils/context/audio_context_manager.d.ts +131 -0
- package/dist/utils/context/audio_context_manager.js +233 -0
- package/dist/utils/context/index.d.ts +2 -0
- package/dist/utils/context/index.js +7 -0
- package/dist/utils/context/llm_context_summarization.d.ts +258 -0
- package/dist/utils/context/llm_context_summarization.js +511 -0
- package/dist/utils/context/text_segment_map.d.ts +611 -0
- package/dist/utils/context/text_segment_map.js +1132 -0
- package/dist/utils/context/word_completion_tracker.d.ts +273 -0
- package/dist/utils/context/word_completion_tracker.js +441 -0
- package/dist/utils/context/word_timestamp_tracker.d.ts +72 -0
- package/dist/utils/context/word_timestamp_tracker.js +98 -0
- package/dist/utils/deprecation.d.ts +28 -0
- package/dist/utils/deprecation.js +75 -0
- package/dist/utils/env.d.ts +38 -0
- package/dist/utils/env.js +62 -0
- package/dist/utils/errors.d.ts +75 -0
- package/dist/utils/errors.js +202 -0
- package/dist/utils/file_resolver.d.ts +126 -0
- package/dist/utils/file_resolver.js +314 -0
- package/dist/utils/file_storage.d.ts +99 -0
- package/dist/utils/file_storage.js +100 -0
- package/dist/utils/frame_queue.d.ts +36 -0
- package/dist/utils/frame_queue.js +66 -0
- package/dist/utils/http.d.ts +39 -0
- package/dist/utils/http.js +53 -0
- package/dist/utils/image.d.ts +15 -0
- package/dist/utils/image.js +30 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/log_config.d.ts +34 -0
- package/dist/utils/log_config.js +76 -0
- package/dist/utils/logger.d.ts +40 -0
- package/dist/utils/logger.js +91 -0
- package/dist/utils/mixin.d.ts +14 -0
- package/dist/utils/mixin.js +63 -0
- package/dist/utils/network.d.ts +73 -0
- package/dist/utils/network.js +90 -0
- package/dist/utils/security/allowed_origins.d.ts +16 -0
- package/dist/utils/security/allowed_origins.js +34 -0
- package/dist/utils/security/index.d.ts +2 -0
- package/dist/utils/security/index.js +7 -0
- package/dist/utils/security/redaction.d.ts +27 -0
- package/dist/utils/security/redaction.js +123 -0
- package/dist/utils/security/ssrf.d.ts +79 -0
- package/dist/utils/security/ssrf.js +327 -0
- package/dist/utils/shared.d.ts +35 -0
- package/dist/utils/shared.js +136 -0
- package/dist/utils/startup.d.ts +17 -0
- package/dist/utils/startup.js +130 -0
- package/dist/utils/string.d.ts +116 -0
- package/dist/utils/string.js +460 -0
- package/dist/utils/sync/base_notifier.d.ts +25 -0
- package/dist/utils/sync/base_notifier.js +16 -0
- package/dist/utils/sync/event_notifier.d.ts +38 -0
- package/dist/utils/sync/event_notifier.js +51 -0
- package/dist/utils/sync/index.d.ts +2 -0
- package/dist/utils/sync/index.js +7 -0
- package/dist/utils/text/alnum_utils.d.ts +109 -0
- package/dist/utils/text/alnum_utils.js +262 -0
- package/dist/utils/text/base_text_aggregator.d.ts +146 -0
- package/dist/utils/text/base_text_aggregator.js +102 -0
- package/dist/utils/text/base_text_filter.d.ts +54 -0
- package/dist/utils/text/base_text_filter.js +50 -0
- package/dist/utils/text/index.d.ts +2 -0
- package/dist/utils/text/index.js +7 -0
- package/dist/utils/text/markdown_text_filter.d.ts +147 -0
- package/dist/utils/text/markdown_text_filter.js +522 -0
- package/dist/utils/text/markup_utils.d.ts +54 -0
- package/dist/utils/text/markup_utils.js +156 -0
- package/dist/utils/text/pattern_pair_aggregator.d.ts +251 -0
- package/dist/utils/text/pattern_pair_aggregator.js +478 -0
- package/dist/utils/text/phonemes.d.ts +60 -0
- package/dist/utils/text/phonemes.js +172 -0
- package/dist/utils/text/simple_text_aggregator.d.ts +135 -0
- package/dist/utils/text/simple_text_aggregator.js +254 -0
- package/dist/utils/text/skip_tags_aggregator.d.ts +85 -0
- package/dist/utils/text/skip_tags_aggregator.js +147 -0
- package/dist/utils/text/transforms/pronunciations.d.ts +65 -0
- package/dist/utils/text/transforms/pronunciations.js +111 -0
- package/dist/utils/text/word_timestamp_utils.d.ts +26 -0
- package/dist/utils/text/word_timestamp_utils.js +52 -0
- package/dist/utils/time.d.ts +13 -0
- package/dist/utils/time.js +31 -0
- package/dist/utils/tracing/index.d.ts +3 -0
- package/dist/utils/tracing/index.js +8 -0
- package/dist/utils/tracing/service_attributes.d.ts +198 -0
- package/dist/utils/tracing/service_attributes.js +434 -0
- package/dist/utils/tracing/service_decorators.d.ts +214 -0
- package/dist/utils/tracing/service_decorators.js +1662 -0
- package/dist/utils/tracing/setup.d.ts +95 -0
- package/dist/utils/tracing/setup.js +110 -0
- package/dist/utils/tracing/tracing_context.d.ts +55 -0
- package/dist/utils/tracing/tracing_context.js +97 -0
- package/dist/utils/tracing/turn_trace_observer.d.ts +90 -0
- package/dist/utils/tracing/turn_trace_observer.js +219 -0
- package/dist/utils/types.d.ts +80 -0
- package/dist/utils/types.js +108 -0
- package/dist/utils/utils.d.ts +5 -0
- package/dist/utils/utils.js +22 -0
- package/dist/utils/yaml.d.ts +64 -0
- package/dist/utils/yaml.js +41 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +7 -0
- package/dist/workers/base_worker.d.ts +772 -0
- package/dist/workers/base_worker.js +1298 -0
- package/dist/workers/index.d.ts +2 -0
- package/dist/workers/index.js +8 -0
- package/dist/workers/llm/backend_llm_worker.d.ts +552 -0
- package/dist/workers/llm/backend_llm_worker.js +1055 -0
- package/dist/workers/llm/index.d.ts +6 -0
- package/dist/workers/llm/index.js +11 -0
- package/dist/workers/llm/llm_context_worker.d.ts +66 -0
- package/dist/workers/llm/llm_context_worker.js +72 -0
- package/dist/workers/llm/llm_worker.d.ts +180 -0
- package/dist/workers/llm/llm_worker.js +323 -0
- package/dist/workers/llm/tool_decorator.d.ts +68 -0
- package/dist/workers/llm/tool_decorator.js +60 -0
- package/dist/workers/runner.d.ts +268 -0
- package/dist/workers/runner.js +489 -0
- package/dist/workers/ui/index.d.ts +22 -0
- package/dist/workers/ui/index.js +27 -0
- package/dist/workers/ui/ui_event_decorator.d.ts +43 -0
- package/dist/workers/ui/ui_event_decorator.js +75 -0
- package/dist/workers/ui/ui_job_context.d.ts +12 -0
- package/dist/workers/ui/ui_job_context.js +17 -0
- package/dist/workers/ui/ui_prompts.d.ts +21 -0
- package/dist/workers/ui/ui_prompts.js +41 -0
- package/dist/workers/ui/ui_tools.d.ts +32 -0
- package/dist/workers/ui/ui_tools.js +112 -0
- package/dist/workers/ui/ui_worker.d.ts +571 -0
- package/dist/workers/ui/ui_worker.js +1156 -0
- package/package.json +46 -3
- package/README.md +0 -5
|
@@ -0,0 +1,1231 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026, Daily
|
|
2
|
+
// Copyright (c) 2026, pipedog contributors
|
|
3
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
//
|
|
5
|
+
// Ported from pipecat: src/pipecat/transports/base_output.py
|
|
6
|
+
/**
|
|
7
|
+
* Base output transport implementation for Pipecat.
|
|
8
|
+
*
|
|
9
|
+
* This module provides the BaseOutputTransport class which handles audio and
|
|
10
|
+
* video output processing, including frame buffering, mixing, timing, and
|
|
11
|
+
* media streaming.
|
|
12
|
+
*/
|
|
13
|
+
import { abortable, pythonCompare, Queue, QueueEmpty, rethrowIfCancelled, sleep, TimeoutError, time, waitFor, } from "@pipedog/asyncio";
|
|
14
|
+
import { loadDtmfAudio } from "../audio/dtmf/utils.js";
|
|
15
|
+
import { createStreamResampler, isSilence, scaleAudio } from "../audio/utils.js";
|
|
16
|
+
import { AssistantImageRawFrame, BotSpeakingFrame, BotStartedSpeakingFrame, BotStoppedSpeakingFrame, CancelFrame, EndFrame, FilterControlFrame, InterruptionFrame, MixerControlFrame, OutputAudioRawFrame, OutputDTMFFrame, OutputDTMFUrgentFrame, OutputImageRawFrame, OutputTransportMessageFrame, OutputTransportMessageUrgentFrame, OutputTransportReadyFrame, SpeechOutputAudioRawFrame, SpriteFrame, StartFrame, SystemFrame, TTSAudioRawFrame, TTSStoppedFrame, VolumeFrame, VolumeGainFrame, } from "../frames/frames.js";
|
|
17
|
+
import { BotCapabilities } from "../pipeline/capabilities.js";
|
|
18
|
+
import { FrameDirection, FrameProcessor, } from "../processors/frame_processor.js";
|
|
19
|
+
import { FramePriorityQueue, FrameQueue } from "../utils/frame_queue.js";
|
|
20
|
+
import { logger } from "../utils/logger.js";
|
|
21
|
+
import { nanosecondsToSeconds } from "../utils/time.js";
|
|
22
|
+
export const BOT_VAD_STOP_SECS = 0.35;
|
|
23
|
+
/** Only used as a fallback. */
|
|
24
|
+
export const BOT_VAD_STOP_FALLBACK_SECS = 3;
|
|
25
|
+
function concatBytes(a, b) {
|
|
26
|
+
const out = new Uint8Array(a.length + b.length);
|
|
27
|
+
out.set(a, 0);
|
|
28
|
+
out.set(b, a.length);
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/** itertools.cycle() */
|
|
32
|
+
function* cycle(items) {
|
|
33
|
+
if (items.length === 0)
|
|
34
|
+
return;
|
|
35
|
+
for (;;) {
|
|
36
|
+
for (const item of items)
|
|
37
|
+
yield item;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function errorMessage(e) {
|
|
41
|
+
return e instanceof Error ? e.message : String(e);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Handles media streaming for a specific destination.
|
|
45
|
+
*
|
|
46
|
+
* Manages audio and video output processing including buffering, timing,
|
|
47
|
+
* mixing, and frame delivery for a single output destination.
|
|
48
|
+
*
|
|
49
|
+
* Upstream this is the nested class `BaseOutputTransport.MediaSender`, which
|
|
50
|
+
* is also reachable here as `BaseOutputTransport.MediaSender`.
|
|
51
|
+
*/
|
|
52
|
+
export class MediaSender {
|
|
53
|
+
_transport;
|
|
54
|
+
_destination;
|
|
55
|
+
_sampleRate;
|
|
56
|
+
_audioChunkSize;
|
|
57
|
+
_params;
|
|
58
|
+
// Incoming audio waiting to be cut into chunks, as runs of audio that came
|
|
59
|
+
// from frames with the same `interruptible` flag, so a chunk keeps the flag
|
|
60
|
+
// of the audio it holds, along with the type of the frames it was built
|
|
61
|
+
// from (so a flushed partial chunk can be reconstructed as the same frame
|
|
62
|
+
// type).
|
|
63
|
+
_audioRuns = [];
|
|
64
|
+
_audioBufferCls = OutputAudioRawFrame;
|
|
65
|
+
// This will be used to resample incoming audio to the output sample rate.
|
|
66
|
+
// Speech boundaries are signalled explicitly (see
|
|
67
|
+
// `_enqueueFlushedAudioBuffer` and `_botStoppedSpeaking`), so the resampler
|
|
68
|
+
// must not discard its filter tail on its own: it runs ahead of playback and
|
|
69
|
+
// would otherwise treat the pause between two TTS chunks as the end of the
|
|
70
|
+
// stream.
|
|
71
|
+
_resampler = createStreamResampler({ clearAfterSecs: null });
|
|
72
|
+
// The user can provide a single mixer, to be used by the default
|
|
73
|
+
// destination, or a destination/mixer mapping.
|
|
74
|
+
_mixer = null;
|
|
75
|
+
// The output filter belongs to the default destination, like a single mixer.
|
|
76
|
+
_filter;
|
|
77
|
+
// The output volume (VolumeFrame) and the gain on it (VolumeGainFrame),
|
|
78
|
+
// updated in order with the audio.
|
|
79
|
+
_volume;
|
|
80
|
+
_volumeGain = 1.0;
|
|
81
|
+
// These are the images that we should send at our desired framerate.
|
|
82
|
+
_videoImages = null;
|
|
83
|
+
// Indicates if the bot is currently speaking.
|
|
84
|
+
_botSpeaking = false;
|
|
85
|
+
// Indicates if TTS audio has been received since the last stop.
|
|
86
|
+
_ttsAudioReceived = false;
|
|
87
|
+
// Last time a BotSpeakingFrame was pushed.
|
|
88
|
+
_botSpeakingFrameTime = 0;
|
|
89
|
+
// How often a BotSpeakingFrame should be pushed (value should be greater
|
|
90
|
+
// than the audio chunks to have any effect).
|
|
91
|
+
_botSpeakingFramePeriod = 0.2;
|
|
92
|
+
// Last time the bot actually spoke.
|
|
93
|
+
_botSpeechLastTime = 0;
|
|
94
|
+
_audioTask = null;
|
|
95
|
+
_videoTask = null;
|
|
96
|
+
_clockTask = null;
|
|
97
|
+
// Created with their tasks (see `_createAudioTask` / `_createVideoTask`).
|
|
98
|
+
_audioQueue = new FrameQueue();
|
|
99
|
+
_videoQueue = new Queue();
|
|
100
|
+
// Timed frames as (timestamp, counter, frame), ordered by presentation
|
|
101
|
+
// time. It outlives the clock task so an interruption can keep its
|
|
102
|
+
// uninterruptible frames.
|
|
103
|
+
_clockQueue = new FramePriorityQueue((item) => item[2], (a, b) => pythonCompare([a[0], a[1]], [b[0], b[1]]));
|
|
104
|
+
// The frame the clock task is waiting to push.
|
|
105
|
+
_clockFrame = null;
|
|
106
|
+
// If timestamps are equal, use this count to preserve the insertion order.
|
|
107
|
+
_clockQueueCounter = 0;
|
|
108
|
+
// Live video timing state, initialized by the video task.
|
|
109
|
+
_videoStartTime = null;
|
|
110
|
+
_videoFrameIndex = 0;
|
|
111
|
+
_videoFrameDuration = 0;
|
|
112
|
+
_videoFrameReset = 0;
|
|
113
|
+
/**
|
|
114
|
+
* Initialize the media sender.
|
|
115
|
+
*
|
|
116
|
+
* @param transport The parent transport instance.
|
|
117
|
+
* @param options.destination The destination identifier for this sender.
|
|
118
|
+
* @param options.sampleRate The audio sample rate in Hz.
|
|
119
|
+
* @param options.audioChunkSize The size of audio chunks in bytes.
|
|
120
|
+
* @param options.params Transport configuration parameters.
|
|
121
|
+
*/
|
|
122
|
+
constructor(transport, options) {
|
|
123
|
+
this._transport = transport;
|
|
124
|
+
this._destination = options.destination;
|
|
125
|
+
this._sampleRate = options.sampleRate;
|
|
126
|
+
this._audioChunkSize = options.audioChunkSize;
|
|
127
|
+
this._params = options.params;
|
|
128
|
+
// Upstream resizes images in a single-worker thread pool; there are no
|
|
129
|
+
// threads here, so resizing runs inline (see `_resizeImage`).
|
|
130
|
+
this._filter = options.destination ? null : options.params.audioOutFilter;
|
|
131
|
+
this._volume = options.params.audioOutVolume;
|
|
132
|
+
}
|
|
133
|
+
/** The audio sample rate in Hz. */
|
|
134
|
+
get sampleRate() {
|
|
135
|
+
return this._sampleRate;
|
|
136
|
+
}
|
|
137
|
+
/** The size of audio chunks in bytes. */
|
|
138
|
+
get audioChunkSize() {
|
|
139
|
+
return this._audioChunkSize;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Start the media sender and initialize components.
|
|
143
|
+
*
|
|
144
|
+
* @param frame The start frame containing initialization parameters.
|
|
145
|
+
*/
|
|
146
|
+
async start(_frame) {
|
|
147
|
+
this._clearAudioBuffer();
|
|
148
|
+
// Create all tasks.
|
|
149
|
+
this._createVideoTask();
|
|
150
|
+
this._createClockTask();
|
|
151
|
+
this._createAudioTask();
|
|
152
|
+
// Check if we have an audio mixer for our destination.
|
|
153
|
+
const mixer = this._params.audioOutMixer;
|
|
154
|
+
if (mixer) {
|
|
155
|
+
if (mixer instanceof Map) {
|
|
156
|
+
this._mixer = mixer.get(this._destination) ?? null;
|
|
157
|
+
}
|
|
158
|
+
else if (!this._destination) {
|
|
159
|
+
// Only use the default mixer if we are the default destination.
|
|
160
|
+
this._mixer = mixer;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Start audio mixer.
|
|
164
|
+
if (this._mixer) {
|
|
165
|
+
await this._mixer.start(this._sampleRate);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Stop the media sender and cleanup resources.
|
|
170
|
+
*
|
|
171
|
+
* @param frame The end frame signaling sender shutdown.
|
|
172
|
+
*/
|
|
173
|
+
async stop(frame) {
|
|
174
|
+
// Let the sink tasks process the queue until they reach this EndFrame.
|
|
175
|
+
await this._clockQueue.put([Number.POSITIVE_INFINITY, this._clockQueueCounter++, frame]);
|
|
176
|
+
await this._audioQueue.put(frame);
|
|
177
|
+
// At this point we have enqueued an EndFrame and we need to wait for that
|
|
178
|
+
// EndFrame to be processed by the audio and clock tasks. We also need to
|
|
179
|
+
// wait for these tasks before cancelling the video task because it might
|
|
180
|
+
// be still rendering.
|
|
181
|
+
if (this._audioTask) {
|
|
182
|
+
await this._audioTask;
|
|
183
|
+
}
|
|
184
|
+
if (this._clockTask) {
|
|
185
|
+
await this._clockTask;
|
|
186
|
+
}
|
|
187
|
+
// Stop audio mixer.
|
|
188
|
+
if (this._mixer) {
|
|
189
|
+
await this._mixer.stop();
|
|
190
|
+
}
|
|
191
|
+
// We can now cancel the video task.
|
|
192
|
+
await this._cancelVideoTask();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Cancel the media sender and stop all processing.
|
|
196
|
+
*
|
|
197
|
+
* @param frame The cancel frame signaling immediate cancellation.
|
|
198
|
+
*/
|
|
199
|
+
async cancel(_frame) {
|
|
200
|
+
await this.cleanup();
|
|
201
|
+
}
|
|
202
|
+
/** Release media sender resources at teardown. */
|
|
203
|
+
async cleanup() {
|
|
204
|
+
// Since we are cancelling everything it doesn't matter what task we cancel first.
|
|
205
|
+
await this._cancelAudioTask();
|
|
206
|
+
await this._cancelClockTask();
|
|
207
|
+
await this._cancelVideoTask();
|
|
208
|
+
// Stop audio mixer so it doesn't keep generating frames after cancellation.
|
|
209
|
+
if (this._mixer) {
|
|
210
|
+
await this._mixer.stop();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Handle interruption events by restarting tasks and clearing buffers.
|
|
215
|
+
*
|
|
216
|
+
* @param _ The start interruption frame (unused).
|
|
217
|
+
*/
|
|
218
|
+
async handleInterruptions(_) {
|
|
219
|
+
// Keep the audio task running but drain all interruptible frames, so the
|
|
220
|
+
// frame being written and the pending uninterruptible ones are still
|
|
221
|
+
// delivered. With a mixer, cancelling the task would also stop mixer-only
|
|
222
|
+
// output during the restart, causing an audible gap in the background
|
|
223
|
+
// audio (made worse by telephony serializers that clear the playout
|
|
224
|
+
// buffer on interruptions).
|
|
225
|
+
this._audioQueue.reset();
|
|
226
|
+
// The same goes for the audio still waiting to fill a chunk.
|
|
227
|
+
this._audioRuns = this._audioRuns.filter(([, flag]) => flag);
|
|
228
|
+
// Likewise, keep the clock task running but drain its interruptible
|
|
229
|
+
// frames, and cancel it if it's waiting to push one.
|
|
230
|
+
this._clockQueue.reset();
|
|
231
|
+
if (this._clockFrame?.interruptible) {
|
|
232
|
+
await this._cancelClockTask();
|
|
233
|
+
}
|
|
234
|
+
await this._cancelVideoTask();
|
|
235
|
+
// Create tasks.
|
|
236
|
+
this._createVideoTask();
|
|
237
|
+
this._createClockTask();
|
|
238
|
+
// Let's send a bot stopped speaking if we have to. The bot keeps speaking
|
|
239
|
+
// while there's uninterruptible audio still to play (after the reset,
|
|
240
|
+
// everything left in the queue is uninterruptible).
|
|
241
|
+
if (!(this._audioRuns.length > 0 || this._audioQueue.hasFrame(OutputAudioRawFrame))) {
|
|
242
|
+
await this._botStoppedSpeaking();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Handle incoming audio frames by buffering and chunking.
|
|
247
|
+
*
|
|
248
|
+
* @param frame The output audio frame to handle.
|
|
249
|
+
*/
|
|
250
|
+
async handleAudioFrame(frame) {
|
|
251
|
+
if (!this._params.audioOutEnabled)
|
|
252
|
+
return;
|
|
253
|
+
// We might need to resample if incoming audio doesn't match the transport
|
|
254
|
+
// sample rate.
|
|
255
|
+
const resampled = await this._resampler.resample(frame.audio, frame.sampleRate, this._sampleRate);
|
|
256
|
+
const cls = frame.constructor;
|
|
257
|
+
this._audioBufferCls = cls;
|
|
258
|
+
this._bufferAudio(resampled, { uninterruptible: !frame.interruptible });
|
|
259
|
+
while (this._bufferedAudioBytes >= this._audioChunkSize) {
|
|
260
|
+
const [audio, uninterruptible] = this._takeAudioChunk();
|
|
261
|
+
const chunk = new cls({
|
|
262
|
+
audio,
|
|
263
|
+
sampleRate: this._sampleRate,
|
|
264
|
+
numChannels: frame.numChannels,
|
|
265
|
+
});
|
|
266
|
+
chunk.transportDestination = this._destination;
|
|
267
|
+
chunk.interruptible = !uninterruptible;
|
|
268
|
+
await this._audioQueue.put(chunk);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Handle incoming image frames for video output.
|
|
273
|
+
*
|
|
274
|
+
* @param frame The output image or sprite frame to handle.
|
|
275
|
+
*/
|
|
276
|
+
async handleImageFrame(frame) {
|
|
277
|
+
if (!this._params.videoOutEnabled)
|
|
278
|
+
return;
|
|
279
|
+
if (frame instanceof OutputImageRawFrame && frame.syncWithAudio) {
|
|
280
|
+
// Route through the audio queue so the image is only displayed after
|
|
281
|
+
// all preceding audio has been sent.
|
|
282
|
+
await this._audioQueue.put(frame);
|
|
283
|
+
}
|
|
284
|
+
else if (this._params.videoOutIsLive && frame instanceof OutputImageRawFrame) {
|
|
285
|
+
await this._videoQueue.put(frame);
|
|
286
|
+
}
|
|
287
|
+
else if (frame instanceof OutputImageRawFrame) {
|
|
288
|
+
await this._setVideoImage(frame);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
await this._setVideoImages(frame.images);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Handle frames with presentation timestamps.
|
|
296
|
+
*
|
|
297
|
+
* @param frame The frame with timing information to handle.
|
|
298
|
+
*/
|
|
299
|
+
async handleTimedFrame(frame) {
|
|
300
|
+
await this._clockQueue.put([frame.pts, this._clockQueueCounter++, frame]);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Handle frames that need synchronized processing.
|
|
304
|
+
*
|
|
305
|
+
* @param frame The frame to handle synchronously.
|
|
306
|
+
*/
|
|
307
|
+
async handleSyncFrame(frame) {
|
|
308
|
+
await this._audioQueue.put(frame);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Queue a TTSStoppedFrame, flushing any trailing buffered audio first.
|
|
312
|
+
*
|
|
313
|
+
* `handleAudioFrame` only queues complete `audioChunkSize` chunks, so up to
|
|
314
|
+
* one chunk's worth of trailing audio can still be sitting in `_audioRuns`.
|
|
315
|
+
* Queue it now (padded to a full chunk with silence) so it plays before the
|
|
316
|
+
* stop frame is handled, instead of being discarded when the buffer is
|
|
317
|
+
* cleared in `_botStoppedSpeaking`.
|
|
318
|
+
*
|
|
319
|
+
* @param frame The TTS stopped frame to queue.
|
|
320
|
+
*/
|
|
321
|
+
async handleTtsStopped(frame) {
|
|
322
|
+
await this._enqueueFlushedAudioBuffer();
|
|
323
|
+
await this._audioQueue.put(frame);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Handle audio mixer control frames.
|
|
327
|
+
*
|
|
328
|
+
* @param frame The mixer control frame to handle.
|
|
329
|
+
*/
|
|
330
|
+
async handleMixerControlFrame(frame) {
|
|
331
|
+
if (this._mixer) {
|
|
332
|
+
await this._mixer.processFrame(frame);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
//
|
|
336
|
+
// Audio handling
|
|
337
|
+
//
|
|
338
|
+
/** Create the audio processing task. */
|
|
339
|
+
_createAudioTask() {
|
|
340
|
+
if (!this._audioTask) {
|
|
341
|
+
this._audioQueue = new FrameQueue();
|
|
342
|
+
this._audioTask = this._transport.createTask(() => this._audioTaskHandler(), "_audio_task_handler");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** Cancel and cleanup the audio processing task. */
|
|
346
|
+
async _cancelAudioTask() {
|
|
347
|
+
if (this._audioTask) {
|
|
348
|
+
await this._transport.cancelTask(this._audioTask);
|
|
349
|
+
this._audioTask = null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/** Handle bot started speaking event. */
|
|
353
|
+
async _botStartedSpeaking() {
|
|
354
|
+
if (this._botSpeaking)
|
|
355
|
+
return;
|
|
356
|
+
this._botSpeaking = true;
|
|
357
|
+
logger.debug(`Bot${this._destination ? ` [${this._destination}]` : ""} started speaking`);
|
|
358
|
+
const downstreamFrame = new BotStartedSpeakingFrame();
|
|
359
|
+
downstreamFrame.transportDestination = this._destination;
|
|
360
|
+
const upstreamFrame = new BotStartedSpeakingFrame();
|
|
361
|
+
upstreamFrame.transportDestination = this._destination;
|
|
362
|
+
// Setting the siblings id
|
|
363
|
+
upstreamFrame.broadcastSiblingId = downstreamFrame.id;
|
|
364
|
+
downstreamFrame.broadcastSiblingId = upstreamFrame.id;
|
|
365
|
+
await this._transport.pushFrame(downstreamFrame);
|
|
366
|
+
await this._transport.pushFrame(upstreamFrame, FrameDirection.UPSTREAM);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Queue every bit of audio still held back at the end of a speech run.
|
|
370
|
+
*
|
|
371
|
+
* That means both what the resampler is holding in its filter and what is
|
|
372
|
+
* left in `_audioRuns`, padded with silence to a full chunk. The audio goes
|
|
373
|
+
* out as frames of the same type (e.g. TTSAudioRawFrame) as the audio it
|
|
374
|
+
* was buffered from, on `_audioQueue`, so it takes the normal playback path
|
|
375
|
+
* (write, error handling, bot speaking tracking) like any other chunk, in
|
|
376
|
+
* order relative to whatever is queued after it (e.g. a TTSStoppedFrame).
|
|
377
|
+
*/
|
|
378
|
+
async _enqueueFlushedAudioBuffer() {
|
|
379
|
+
// The resampler holds the tail of the audio it was fed, which belongs at
|
|
380
|
+
// the end of this speech run, with the last run's flag.
|
|
381
|
+
const tail = await this._resampler.flush();
|
|
382
|
+
const lastRun = this._audioRuns[this._audioRuns.length - 1];
|
|
383
|
+
const lastUninterruptible = lastRun ? lastRun[1] : false;
|
|
384
|
+
this._bufferAudio(tail, { uninterruptible: lastUninterruptible });
|
|
385
|
+
if (this._audioRuns.length === 0)
|
|
386
|
+
return;
|
|
387
|
+
// The flushed tail can be longer than a chunk, so send whole chunks first
|
|
388
|
+
// and pad only what's left over.
|
|
389
|
+
while (this._bufferedAudioBytes >= this._audioChunkSize) {
|
|
390
|
+
const [audio, uninterruptible] = this._takeAudioChunk();
|
|
391
|
+
await this._enqueueAudioChunk(audio, uninterruptible);
|
|
392
|
+
}
|
|
393
|
+
if (this._audioRuns.length > 0) {
|
|
394
|
+
const [audio, uninterruptible] = this._takeAudioChunk();
|
|
395
|
+
const padded = new Uint8Array(this._audioChunkSize);
|
|
396
|
+
padded.set(audio, 0);
|
|
397
|
+
await this._enqueueAudioChunk(padded, uninterruptible);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
get _bufferedAudioBytes() {
|
|
401
|
+
let total = 0;
|
|
402
|
+
for (const [audio] of this._audioRuns)
|
|
403
|
+
total += audio.length;
|
|
404
|
+
return total;
|
|
405
|
+
}
|
|
406
|
+
/** Add output-rate audio to the buffer, noting whether it came from uninterruptible frames. */
|
|
407
|
+
_bufferAudio(audio, options) {
|
|
408
|
+
if (audio.length === 0)
|
|
409
|
+
return;
|
|
410
|
+
const last = this._audioRuns[this._audioRuns.length - 1];
|
|
411
|
+
if (last && last[1] === options.uninterruptible) {
|
|
412
|
+
last[0] = concatBytes(last[0], audio);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this._audioRuns.push([audio.slice(), options.uninterruptible]);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/** Cut up to a chunk from the front of the buffer, and whether any of it is uninterruptible. */
|
|
419
|
+
_takeAudioChunk() {
|
|
420
|
+
const chunk = new Uint8Array(this._audioChunkSize);
|
|
421
|
+
let length = 0;
|
|
422
|
+
let uninterruptible = false;
|
|
423
|
+
while (length < this._audioChunkSize && this._audioRuns.length > 0) {
|
|
424
|
+
const run = this._audioRuns[0];
|
|
425
|
+
const [audio, runUninterruptible] = run;
|
|
426
|
+
uninterruptible = uninterruptible || runUninterruptible;
|
|
427
|
+
const needed = this._audioChunkSize - length;
|
|
428
|
+
if (audio.length <= needed) {
|
|
429
|
+
chunk.set(audio, length);
|
|
430
|
+
length += audio.length;
|
|
431
|
+
this._audioRuns.shift();
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
chunk.set(audio.subarray(0, needed), length);
|
|
435
|
+
length += needed;
|
|
436
|
+
run[0] = audio.slice(needed);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return [chunk.slice(0, length), uninterruptible];
|
|
440
|
+
}
|
|
441
|
+
_clearAudioBuffer() {
|
|
442
|
+
this._audioRuns = [];
|
|
443
|
+
}
|
|
444
|
+
/** Queue one full chunk of output-rate audio for playback. */
|
|
445
|
+
async _enqueueAudioChunk(audio, uninterruptible) {
|
|
446
|
+
const frame = new this._audioBufferCls({
|
|
447
|
+
audio,
|
|
448
|
+
sampleRate: this._sampleRate,
|
|
449
|
+
numChannels: this._params.audioOutChannels,
|
|
450
|
+
});
|
|
451
|
+
frame.transportDestination = this._destination;
|
|
452
|
+
frame.interruptible = !uninterruptible;
|
|
453
|
+
await this._audioQueue.put(frame);
|
|
454
|
+
}
|
|
455
|
+
/** Handle bot stopped speaking event. */
|
|
456
|
+
async _botStoppedSpeaking() {
|
|
457
|
+
if (!this._botSpeaking)
|
|
458
|
+
return;
|
|
459
|
+
this._botSpeaking = false;
|
|
460
|
+
this._ttsAudioReceived = false;
|
|
461
|
+
// Any remaining leftover here (e.g. from an interruption) is discarded
|
|
462
|
+
// rather than flushed, since it's no longer wanted. The same goes for the
|
|
463
|
+
// audio still inside the resampler, which would otherwise be prepended to
|
|
464
|
+
// whatever the bot says next.
|
|
465
|
+
this._clearAudioBuffer();
|
|
466
|
+
await this._resampler.reset();
|
|
467
|
+
logger.debug(`Bot${this._destination ? ` [${this._destination}]` : ""} stopped speaking`);
|
|
468
|
+
const downstreamFrame = new BotStoppedSpeakingFrame();
|
|
469
|
+
downstreamFrame.transportDestination = this._destination;
|
|
470
|
+
const upstreamFrame = new BotStoppedSpeakingFrame();
|
|
471
|
+
upstreamFrame.transportDestination = this._destination;
|
|
472
|
+
// Setting the siblings id
|
|
473
|
+
upstreamFrame.broadcastSiblingId = downstreamFrame.id;
|
|
474
|
+
downstreamFrame.broadcastSiblingId = upstreamFrame.id;
|
|
475
|
+
await this._transport.pushFrame(downstreamFrame);
|
|
476
|
+
await this._transport.pushFrame(upstreamFrame, FrameDirection.UPSTREAM);
|
|
477
|
+
}
|
|
478
|
+
/** Handle bot speaking event. */
|
|
479
|
+
async _botCurrentlySpeaking() {
|
|
480
|
+
await this._botStartedSpeaking();
|
|
481
|
+
const diffTime = time() - this._botSpeakingFrameTime;
|
|
482
|
+
if (diffTime >= this._botSpeakingFramePeriod) {
|
|
483
|
+
await this._transport.broadcastFrame(BotSpeakingFrame);
|
|
484
|
+
this._botSpeakingFrameTime = time();
|
|
485
|
+
}
|
|
486
|
+
this._botSpeechLastTime = time();
|
|
487
|
+
}
|
|
488
|
+
async _maybeBotCurrentlySpeaking(frame) {
|
|
489
|
+
if (!isSilence(frame.audio)) {
|
|
490
|
+
await this._botCurrentlySpeaking();
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
const silenceDuration = time() - this._botSpeechLastTime;
|
|
494
|
+
if (silenceDuration > BOT_VAD_STOP_SECS) {
|
|
495
|
+
await this._botStoppedSpeaking();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async _handleBotSpeech(frame) {
|
|
500
|
+
// TTS case.
|
|
501
|
+
if (frame instanceof TTSAudioRawFrame) {
|
|
502
|
+
// We will only trigger bot stopped speaking based on the
|
|
503
|
+
// TTSStoppedFrame, if we have received audio from TTS
|
|
504
|
+
this._ttsAudioReceived = true;
|
|
505
|
+
await this._botCurrentlySpeaking();
|
|
506
|
+
}
|
|
507
|
+
// Speech stream case.
|
|
508
|
+
else if (frame instanceof SpeechOutputAudioRawFrame) {
|
|
509
|
+
await this._maybeBotCurrentlySpeaking(frame);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Handle various frame types with appropriate processing.
|
|
514
|
+
*
|
|
515
|
+
* @param frame The frame to handle.
|
|
516
|
+
*/
|
|
517
|
+
async _handleFrame(frame) {
|
|
518
|
+
if (frame instanceof OutputAudioRawFrame) {
|
|
519
|
+
await this._handleBotSpeech(frame);
|
|
520
|
+
}
|
|
521
|
+
else if (frame instanceof OutputImageRawFrame) {
|
|
522
|
+
await this._setVideoImage(frame);
|
|
523
|
+
}
|
|
524
|
+
else if (frame instanceof SpriteFrame) {
|
|
525
|
+
await this._setVideoImages(frame.images);
|
|
526
|
+
}
|
|
527
|
+
else if (frame instanceof OutputTransportMessageFrame) {
|
|
528
|
+
await this._transport.sendMessage(frame);
|
|
529
|
+
}
|
|
530
|
+
else if (frame instanceof OutputDTMFFrame) {
|
|
531
|
+
await this._transport.writeDtmf(frame);
|
|
532
|
+
}
|
|
533
|
+
else if (frame instanceof TTSStoppedFrame) {
|
|
534
|
+
// We will only trigger bot stopped speaking based on the
|
|
535
|
+
// TTSStoppedFrame, if we have received audio from TTS
|
|
536
|
+
if (this._ttsAudioReceived) {
|
|
537
|
+
logger.debug("Bot stopped speaking based on TTSStoppedFrame");
|
|
538
|
+
await this._botStoppedSpeaking();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else if (frame instanceof FilterControlFrame && this._filter) {
|
|
542
|
+
await this._filter.processFrame(frame);
|
|
543
|
+
}
|
|
544
|
+
else if (frame instanceof VolumeFrame) {
|
|
545
|
+
this._volume = frame.volume;
|
|
546
|
+
}
|
|
547
|
+
else if (frame instanceof VolumeGainFrame) {
|
|
548
|
+
this._volumeGain = frame.gain;
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
await this._transport.writeTransportFrame(frame);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Run the output filter over a frame taken from the audio queue.
|
|
556
|
+
*
|
|
557
|
+
* @param frame The queued frame. Only audio frames are filtered.
|
|
558
|
+
*/
|
|
559
|
+
async _filterAudio(frame) {
|
|
560
|
+
if (this._filter && frame instanceof OutputAudioRawFrame) {
|
|
561
|
+
frame.audio = await this._filter.filter(frame.audio);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/** Scale an audio frame taken from the audio queue by the volume and its gain. */
|
|
565
|
+
_applyVolume(frame) {
|
|
566
|
+
const volume = this._volume * this._volumeGain;
|
|
567
|
+
if (volume !== 1.0 && frame instanceof OutputAudioRawFrame) {
|
|
568
|
+
frame.audio = scaleAudio(frame.audio, volume);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Generate the next frame for audio processing.
|
|
573
|
+
*
|
|
574
|
+
* @returns An async generator yielding frames for processing.
|
|
575
|
+
*/
|
|
576
|
+
_nextFrame() {
|
|
577
|
+
const withoutMixer = async function* (vadStopSecs) {
|
|
578
|
+
for (;;) {
|
|
579
|
+
let frame;
|
|
580
|
+
try {
|
|
581
|
+
frame = await waitFor(() => this._audioQueue.get(), vadStopSecs);
|
|
582
|
+
}
|
|
583
|
+
catch (e) {
|
|
584
|
+
if (!(e instanceof TimeoutError))
|
|
585
|
+
throw e;
|
|
586
|
+
// Fallback: notify the bot stopped speaking upstream if necessary
|
|
587
|
+
// based on timeout.
|
|
588
|
+
await this._botStoppedSpeaking();
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
await this._filterAudio(frame);
|
|
592
|
+
this._applyVolume(frame);
|
|
593
|
+
yield frame;
|
|
594
|
+
this._audioQueue.taskDone();
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
const withMixer = async function* (vadStopSecs) {
|
|
598
|
+
// Caller below only invokes this when `this._mixer` is set.
|
|
599
|
+
const mixer = this._mixer;
|
|
600
|
+
let lastFrameTime = 0;
|
|
601
|
+
const silence = new Uint8Array(this._audioChunkSize);
|
|
602
|
+
for (;;) {
|
|
603
|
+
let frame;
|
|
604
|
+
try {
|
|
605
|
+
frame = this._audioQueue.getNowait();
|
|
606
|
+
}
|
|
607
|
+
catch (e) {
|
|
608
|
+
if (!(e instanceof QueueEmpty))
|
|
609
|
+
throw e;
|
|
610
|
+
// Fallback: notify the bot stopped speaking upstream if necessary
|
|
611
|
+
// based on timeout.
|
|
612
|
+
const diffTime = time() - lastFrameTime;
|
|
613
|
+
if (diffTime > vadStopSecs) {
|
|
614
|
+
await this._botStoppedSpeaking();
|
|
615
|
+
}
|
|
616
|
+
// Generate an audio frame with only the mixer's part.
|
|
617
|
+
const mixed = new OutputAudioRawFrame({
|
|
618
|
+
audio: await mixer.mix(silence),
|
|
619
|
+
sampleRate: this._sampleRate,
|
|
620
|
+
numChannels: this._params.audioOutChannels,
|
|
621
|
+
});
|
|
622
|
+
yield mixed;
|
|
623
|
+
// Allow other tasks to execute by adding a small sleep. Without
|
|
624
|
+
// this sleep, in task cancellation scenarios, this loop would
|
|
625
|
+
// continuously return without any delay, leading to 100% CPU
|
|
626
|
+
// utilization and preventing cancel/stop signals from being
|
|
627
|
+
// processed properly.
|
|
628
|
+
await sleep(0);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
await this._filterAudio(frame);
|
|
632
|
+
this._applyVolume(frame);
|
|
633
|
+
if (frame instanceof OutputAudioRawFrame) {
|
|
634
|
+
frame.audio = await mixer.mix(frame.audio);
|
|
635
|
+
lastFrameTime = time();
|
|
636
|
+
}
|
|
637
|
+
yield frame;
|
|
638
|
+
this._audioQueue.taskDone();
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
if (this._mixer) {
|
|
642
|
+
return withMixer.call(this, BOT_VAD_STOP_FALLBACK_SECS);
|
|
643
|
+
}
|
|
644
|
+
return withoutMixer.call(this, BOT_VAD_STOP_FALLBACK_SECS);
|
|
645
|
+
}
|
|
646
|
+
async _sendSilence(secs) {
|
|
647
|
+
if (secs <= 0)
|
|
648
|
+
return;
|
|
649
|
+
const sampleWidth = 2;
|
|
650
|
+
const silence = new Uint8Array(this.sampleRate * sampleWidth * secs);
|
|
651
|
+
const silenceFrame = new OutputAudioRawFrame({
|
|
652
|
+
audio: silence,
|
|
653
|
+
sampleRate: this.sampleRate,
|
|
654
|
+
numChannels: 1,
|
|
655
|
+
});
|
|
656
|
+
await this._internalWriteAudioFrame(silenceFrame);
|
|
657
|
+
}
|
|
658
|
+
/** Main audio processing task handler. */
|
|
659
|
+
async _audioTaskHandler() {
|
|
660
|
+
for await (const frame of this._nextFrame()) {
|
|
661
|
+
// No need to push EndFrame, it's pushed from processFrame().
|
|
662
|
+
if (frame instanceof EndFrame) {
|
|
663
|
+
// Send some final silence so words don't cut out.
|
|
664
|
+
await this._sendSilence(this._params.audioOutEndSilenceSecs);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
// Skip frames with no audio data (e.g. filter is buffering).
|
|
668
|
+
if (frame instanceof OutputAudioRawFrame && frame.audio.length === 0) {
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
// Handle frame.
|
|
672
|
+
await this._handleFrame(frame);
|
|
673
|
+
// If we are not able to write to the transport we shouldn't push
|
|
674
|
+
// downstream.
|
|
675
|
+
let pushDownstream = true;
|
|
676
|
+
// Try to send audio to the transport.
|
|
677
|
+
try {
|
|
678
|
+
if (frame instanceof OutputAudioRawFrame) {
|
|
679
|
+
pushDownstream = await this._internalWriteAudioFrame(frame);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
catch (e) {
|
|
683
|
+
rethrowIfCancelled(e);
|
|
684
|
+
logger.error(`${this} Error writing ${frame} to transport: ${errorMessage(e)}`);
|
|
685
|
+
pushDownstream = false;
|
|
686
|
+
}
|
|
687
|
+
// If we were able to send to the transport, push the frame downstream
|
|
688
|
+
// in case anyone else needs it.
|
|
689
|
+
if (pushDownstream) {
|
|
690
|
+
await this._transport.pushFrame(frame);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Write a frame to the transport, giving up if the write never returns.
|
|
696
|
+
*
|
|
697
|
+
* A client that stops reading is the common way to get there: the
|
|
698
|
+
* connection stays up and nothing fails, so the transport has neither an
|
|
699
|
+
* error to report nor state to check. That leaves how long the write takes
|
|
700
|
+
* as the only signal available.
|
|
701
|
+
*
|
|
702
|
+
* @returns Whether the transport took the frame.
|
|
703
|
+
*/
|
|
704
|
+
async _internalWriteAudioFrame(frame) {
|
|
705
|
+
// Reporting the timeout below costs the transport its usability, so a
|
|
706
|
+
// peer is only written off once however much audio is still queued.
|
|
707
|
+
if (!this._transport.isUsable)
|
|
708
|
+
return false;
|
|
709
|
+
const timeout = this._params.audioOutWriteTimeoutSecs;
|
|
710
|
+
try {
|
|
711
|
+
// The write usually ends in a foreign await (a WebSocket, an SDK), which
|
|
712
|
+
// is not a cancellation point. `abortable` makes it one, so the timeout
|
|
713
|
+
// can actually stop waiting on a write that never returns.
|
|
714
|
+
return await waitFor(() => abortable(this._transport.writeAudioFrame(frame)), timeout);
|
|
715
|
+
}
|
|
716
|
+
catch (e) {
|
|
717
|
+
if (!(e instanceof TimeoutError))
|
|
718
|
+
throw e;
|
|
719
|
+
await this._transport.pushError(`${this._transport} timed out after ${timeout}s writing audio to the ` +
|
|
720
|
+
"transport; the peer has stopped reading", { exception: e, forceTreatAsPermanent: true });
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
//
|
|
725
|
+
// Video handling
|
|
726
|
+
//
|
|
727
|
+
/** Create the video processing task if video output is enabled. */
|
|
728
|
+
_createVideoTask() {
|
|
729
|
+
if (!this._videoTask && this._params.videoOutEnabled) {
|
|
730
|
+
this._videoQueue = new Queue();
|
|
731
|
+
this._videoTask = this._transport.createTask(() => this._videoTaskHandler(), "_video_task_handler");
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/** Cancel and cleanup the video processing task. */
|
|
735
|
+
async _cancelVideoTask() {
|
|
736
|
+
// Stop video output task.
|
|
737
|
+
if (this._videoTask) {
|
|
738
|
+
await this._transport.cancelTask(this._videoTask);
|
|
739
|
+
this._videoTask = null;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Set a single video image for cycling output.
|
|
744
|
+
*
|
|
745
|
+
* @param image The image frame to cycle for video output.
|
|
746
|
+
*/
|
|
747
|
+
async _setVideoImage(image) {
|
|
748
|
+
this._videoImages = cycle([image]);
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Set multiple video images for cycling output.
|
|
752
|
+
*
|
|
753
|
+
* @param images The list of image frames to cycle for video output.
|
|
754
|
+
*/
|
|
755
|
+
async _setVideoImages(images) {
|
|
756
|
+
this._videoImages = cycle([...images]);
|
|
757
|
+
}
|
|
758
|
+
/** Main video processing task handler. */
|
|
759
|
+
async _videoTaskHandler() {
|
|
760
|
+
this._videoStartTime = null;
|
|
761
|
+
this._videoFrameIndex = 0;
|
|
762
|
+
this._videoFrameDuration = 1 / this._params.videoOutFramerate;
|
|
763
|
+
this._videoFrameReset = this._videoFrameDuration * 5;
|
|
764
|
+
for (;;) {
|
|
765
|
+
if (this._params.videoOutIsLive) {
|
|
766
|
+
await this._videoIsLiveHandler();
|
|
767
|
+
}
|
|
768
|
+
else if (this._videoImages) {
|
|
769
|
+
const next = this._videoImages.next();
|
|
770
|
+
if (!next.done) {
|
|
771
|
+
await this._drawImage(next.value);
|
|
772
|
+
}
|
|
773
|
+
await sleep(this._videoFrameDuration);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
await sleep(this._videoFrameDuration);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
/** Handle live video streaming with frame timing. */
|
|
781
|
+
async _videoIsLiveHandler() {
|
|
782
|
+
const image = await this._videoQueue.get();
|
|
783
|
+
// We get the start time as soon as we get the first image.
|
|
784
|
+
if (!this._videoStartTime) {
|
|
785
|
+
this._videoStartTime = time();
|
|
786
|
+
this._videoFrameIndex = 0;
|
|
787
|
+
}
|
|
788
|
+
// Calculate how much time we need to wait before rendering next image.
|
|
789
|
+
const realElapsedTime = time() - this._videoStartTime;
|
|
790
|
+
const realRenderTime = this._videoFrameIndex * this._videoFrameDuration;
|
|
791
|
+
const delayTime = this._videoFrameDuration + realRenderTime - realElapsedTime;
|
|
792
|
+
if (Math.abs(delayTime) > this._videoFrameReset) {
|
|
793
|
+
this._videoStartTime = time();
|
|
794
|
+
this._videoFrameIndex = 0;
|
|
795
|
+
}
|
|
796
|
+
else if (delayTime > 0) {
|
|
797
|
+
await sleep(delayTime);
|
|
798
|
+
this._videoFrameIndex += 1;
|
|
799
|
+
}
|
|
800
|
+
// Render image
|
|
801
|
+
await this._drawImage(image);
|
|
802
|
+
this._videoQueue.taskDone();
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Draw/render an image frame with resizing if needed.
|
|
806
|
+
*
|
|
807
|
+
* @param frame The image frame to draw.
|
|
808
|
+
*/
|
|
809
|
+
async _drawImage(frame) {
|
|
810
|
+
const resizeFrame = (frame) => {
|
|
811
|
+
// Without a format we can't decode the bytes, so leave the frame as-is
|
|
812
|
+
// and let the transport pass it through unchanged.
|
|
813
|
+
if (frame.format === null)
|
|
814
|
+
return frame;
|
|
815
|
+
const desiredSize = [
|
|
816
|
+
this._params.videoOutWidth,
|
|
817
|
+
this._params.videoOutHeight,
|
|
818
|
+
];
|
|
819
|
+
// TODO: we should refactor in the future to support dynamic resolutions
|
|
820
|
+
// which is kind of what happens in P2P connections. We need to add
|
|
821
|
+
// support for that inside the DailyTransport
|
|
822
|
+
if (frame.size[0] !== desiredSize[0] || frame.size[1] !== desiredSize[1]) {
|
|
823
|
+
return this._resizeImage(frame, desiredSize);
|
|
824
|
+
}
|
|
825
|
+
return frame;
|
|
826
|
+
};
|
|
827
|
+
await this._transport.writeVideoFrame(resizeFrame(frame));
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Resize an image frame to the transport's output size.
|
|
831
|
+
*
|
|
832
|
+
* Upstream decodes the raw bytes with PIL (`Image.frombytes`), resizes them
|
|
833
|
+
* and re-encodes them in the image's mode. There is no edge-compatible PIL
|
|
834
|
+
* equivalent yet; override this (or provide a resizer) to support images
|
|
835
|
+
* whose size differs from `videoOutWidth` x `videoOutHeight`.
|
|
836
|
+
*
|
|
837
|
+
* @param frame The image frame to resize (its `format` is set).
|
|
838
|
+
* @param desiredSize The output size as [width, height].
|
|
839
|
+
* @returns A new OutputImageRawFrame with the resized image.
|
|
840
|
+
*/
|
|
841
|
+
_resizeImage(_frame, _desiredSize) {
|
|
842
|
+
// PORT-STUB: PIL Image.resize has no edge-compatible equivalent yet.
|
|
843
|
+
throw new Error("not ported yet: image resizing (PIL Image.resize) in MediaSender._drawImage");
|
|
844
|
+
}
|
|
845
|
+
//
|
|
846
|
+
// Clock handling
|
|
847
|
+
//
|
|
848
|
+
/** Create the clock/timing processing task. */
|
|
849
|
+
_createClockTask() {
|
|
850
|
+
if (!this._clockTask) {
|
|
851
|
+
this._clockTask = this._transport.createTask(() => this._clockTaskHandler(), "_clock_task_handler");
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
/** Cancel and cleanup the clock processing task. */
|
|
855
|
+
async _cancelClockTask() {
|
|
856
|
+
if (this._clockTask) {
|
|
857
|
+
await this._transport.cancelTask(this._clockTask);
|
|
858
|
+
this._clockTask = null;
|
|
859
|
+
this._clockFrame = null;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/** Main clock/timing task handler for timed frame delivery. */
|
|
863
|
+
async _clockTaskHandler() {
|
|
864
|
+
let running = true;
|
|
865
|
+
while (running) {
|
|
866
|
+
const [timestamp, , frame] = await this._clockQueue.get();
|
|
867
|
+
// If we hit an EndFrame, we can finish right away.
|
|
868
|
+
running = !(frame instanceof EndFrame);
|
|
869
|
+
// If we have a frame we check it's presentation timestamp. If it has
|
|
870
|
+
// already passed we process it, otherwise we wait until it's time to
|
|
871
|
+
// process it.
|
|
872
|
+
if (running) {
|
|
873
|
+
this._clockFrame = frame;
|
|
874
|
+
const currentTime = this._transport.getClock().getTime();
|
|
875
|
+
if (timestamp > currentTime) {
|
|
876
|
+
const waitTime = nanosecondsToSeconds(timestamp - currentTime);
|
|
877
|
+
await sleep(waitTime);
|
|
878
|
+
}
|
|
879
|
+
// Push frame downstream.
|
|
880
|
+
await this._transport.pushFrame(frame);
|
|
881
|
+
this._clockFrame = null;
|
|
882
|
+
}
|
|
883
|
+
this._clockQueue.taskDone();
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
toString() {
|
|
887
|
+
return `${this.constructor.name}(${this._transport}, ${this._destination})`;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Base class for output transport implementations.
|
|
892
|
+
*
|
|
893
|
+
* Handles audio and video output processing including frame buffering, audio
|
|
894
|
+
* mixing, timing coordination, and media streaming. Supports multiple output
|
|
895
|
+
* destinations and provides interruption handling for real-time
|
|
896
|
+
* communication.
|
|
897
|
+
*
|
|
898
|
+
* The deprecated `TransportParams.video_out_bitrate` (and its deprecation
|
|
899
|
+
* warning) is not ported.
|
|
900
|
+
*/
|
|
901
|
+
export class BaseOutputTransport extends FrameProcessor {
|
|
902
|
+
/** Upstream's nested class `BaseOutputTransport.MediaSender`. */
|
|
903
|
+
static MediaSender = MediaSender;
|
|
904
|
+
_params;
|
|
905
|
+
// Output sample rate. It will be initialized during setup.
|
|
906
|
+
_sampleRate = 0;
|
|
907
|
+
// We write 10ms*CHUNKS of audio at a time (where CHUNKS is the
|
|
908
|
+
// `audioOut10msChunks` parameter). If we receive long audio frames we will
|
|
909
|
+
// chunk them. This helps with interruption handling. It will be initialized
|
|
910
|
+
// during setup.
|
|
911
|
+
_audioChunkSize = 0;
|
|
912
|
+
// We will have one media sender per output frame destination. This allow us
|
|
913
|
+
// to send multiple streams at the same time if the transport allows it.
|
|
914
|
+
_mediaSenders = new Map();
|
|
915
|
+
/**
|
|
916
|
+
* Initialize the base output transport.
|
|
917
|
+
*
|
|
918
|
+
* @param params Transport configuration parameters.
|
|
919
|
+
* @param options Additional options passed to the parent class.
|
|
920
|
+
*/
|
|
921
|
+
constructor(params, options = {}) {
|
|
922
|
+
super(options);
|
|
923
|
+
this._params = params;
|
|
924
|
+
}
|
|
925
|
+
/** The current audio sample rate in Hz. */
|
|
926
|
+
get sampleRate() {
|
|
927
|
+
return this._sampleRate;
|
|
928
|
+
}
|
|
929
|
+
/** The size of audio chunks for output processing, in bytes. */
|
|
930
|
+
get audioChunkSize() {
|
|
931
|
+
return this._audioChunkSize;
|
|
932
|
+
}
|
|
933
|
+
/** The media this transport sends to the user: the `audio_out` and `video_out` capabilities. */
|
|
934
|
+
get capabilities() {
|
|
935
|
+
return new BotCapabilities({
|
|
936
|
+
audioOut: this._params.audioOutEnabled,
|
|
937
|
+
videoOut: this._params.videoOutEnabled,
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Set up the transport.
|
|
942
|
+
*
|
|
943
|
+
* @param setup Configuration object containing setup parameters.
|
|
944
|
+
*/
|
|
945
|
+
async setup(setup) {
|
|
946
|
+
await super.setup(setup);
|
|
947
|
+
this._sampleRate = this._params.audioOutSampleRate || setup.audioOutSampleRate;
|
|
948
|
+
// We will write 10ms*CHUNKS of audio at a time (where CHUNKS is the
|
|
949
|
+
// `audioOut10msChunks` parameter). If we receive long audio frames we will
|
|
950
|
+
// chunk them. This will help with interruption handling.
|
|
951
|
+
const audioBytes10ms = Math.trunc(this._sampleRate / 100) * this._params.audioOutChannels * 2;
|
|
952
|
+
this._audioChunkSize = audioBytes10ms * this._params.audioOut10msChunks;
|
|
953
|
+
// Start audio filter. Paired with the stop in cleanup(), which is the only
|
|
954
|
+
// other call guaranteed to happen exactly once.
|
|
955
|
+
if (this._params.audioOutFilter) {
|
|
956
|
+
await this._params.audioOutFilter.start(this._sampleRate);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
/** Release output transport resources at teardown. */
|
|
960
|
+
async cleanup() {
|
|
961
|
+
await super.cleanup();
|
|
962
|
+
for (const sender of this._mediaSenders.values()) {
|
|
963
|
+
await sender.cleanup();
|
|
964
|
+
}
|
|
965
|
+
if (this._params.audioOutFilter) {
|
|
966
|
+
await this._params.audioOutFilter.stop();
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Start the output transport.
|
|
971
|
+
*
|
|
972
|
+
* Base hook for subclasses, which reach it through `super`. The transport's
|
|
973
|
+
* own audio configuration is resolved in `setup`.
|
|
974
|
+
*
|
|
975
|
+
* @param frame The start frame containing initialization parameters.
|
|
976
|
+
*/
|
|
977
|
+
async start(_frame) { }
|
|
978
|
+
/**
|
|
979
|
+
* Stop the output transport and cleanup resources.
|
|
980
|
+
*
|
|
981
|
+
* @param frame The end frame signaling transport shutdown.
|
|
982
|
+
*/
|
|
983
|
+
async stop(frame) {
|
|
984
|
+
for (const sender of this._mediaSenders.values()) {
|
|
985
|
+
await sender.stop(frame);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Cancel the output transport and stop all processing.
|
|
990
|
+
*
|
|
991
|
+
* @param frame The cancel frame signaling immediate cancellation.
|
|
992
|
+
*/
|
|
993
|
+
async cancel(frame) {
|
|
994
|
+
for (const sender of this._mediaSenders.values()) {
|
|
995
|
+
await sender.cancel(frame);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Called when the transport is ready to stream.
|
|
1000
|
+
*
|
|
1001
|
+
* @param frame The start frame containing initialization parameters.
|
|
1002
|
+
*/
|
|
1003
|
+
async setTransportReady(frame) {
|
|
1004
|
+
// Register destinations.
|
|
1005
|
+
for (const destination of this._params.audioOutDestinations) {
|
|
1006
|
+
await this.registerAudioDestination(destination);
|
|
1007
|
+
}
|
|
1008
|
+
for (const destination of this._params.videoOutDestinations) {
|
|
1009
|
+
await this.registerVideoDestination(destination);
|
|
1010
|
+
}
|
|
1011
|
+
// Start default media sender.
|
|
1012
|
+
const defaultSender = new BaseOutputTransport.MediaSender(this, {
|
|
1013
|
+
destination: null,
|
|
1014
|
+
sampleRate: this.sampleRate,
|
|
1015
|
+
audioChunkSize: this.audioChunkSize,
|
|
1016
|
+
params: this._params,
|
|
1017
|
+
});
|
|
1018
|
+
this._mediaSenders.set(null, defaultSender);
|
|
1019
|
+
await defaultSender.start(frame);
|
|
1020
|
+
// Media senders already send both audio and video, so make sure we only
|
|
1021
|
+
// have one media server per shared name.
|
|
1022
|
+
const destinations = [
|
|
1023
|
+
...new Set([...this._params.audioOutDestinations, ...this._params.videoOutDestinations]),
|
|
1024
|
+
];
|
|
1025
|
+
// Start media senders.
|
|
1026
|
+
for (const destination of destinations) {
|
|
1027
|
+
const sender = new BaseOutputTransport.MediaSender(this, {
|
|
1028
|
+
destination,
|
|
1029
|
+
sampleRate: this.sampleRate,
|
|
1030
|
+
audioChunkSize: this.audioChunkSize,
|
|
1031
|
+
params: this._params,
|
|
1032
|
+
});
|
|
1033
|
+
this._mediaSenders.set(destination, sender);
|
|
1034
|
+
await sender.start(frame);
|
|
1035
|
+
}
|
|
1036
|
+
// Sending a frame indicating that the output transport is ready and able
|
|
1037
|
+
// to receive frames.
|
|
1038
|
+
await this.pushFrame(new OutputTransportReadyFrame(), FrameDirection.UPSTREAM);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Send a transport message.
|
|
1042
|
+
*
|
|
1043
|
+
* @param frame The transport message frame to send.
|
|
1044
|
+
*/
|
|
1045
|
+
async sendMessage(_frame) { }
|
|
1046
|
+
/**
|
|
1047
|
+
* Register a video output destination.
|
|
1048
|
+
*
|
|
1049
|
+
* @param destination The destination identifier to register.
|
|
1050
|
+
*/
|
|
1051
|
+
async registerVideoDestination(_destination) { }
|
|
1052
|
+
/**
|
|
1053
|
+
* Register an audio output destination.
|
|
1054
|
+
*
|
|
1055
|
+
* @param destination The destination identifier to register.
|
|
1056
|
+
*/
|
|
1057
|
+
async registerAudioDestination(_destination) { }
|
|
1058
|
+
/**
|
|
1059
|
+
* Write a video frame to the transport.
|
|
1060
|
+
*
|
|
1061
|
+
* @param frame The output video frame to write.
|
|
1062
|
+
* @returns True if the video frame was written successfully, false otherwise.
|
|
1063
|
+
*/
|
|
1064
|
+
async writeVideoFrame(_frame) {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Write an audio frame to the transport.
|
|
1069
|
+
*
|
|
1070
|
+
* @param frame The output audio frame to write.
|
|
1071
|
+
* @returns Whether the transport took the frame. A serializer that emits no
|
|
1072
|
+
* payload has still taken it; see `FrameSerializer.serialize`.
|
|
1073
|
+
*/
|
|
1074
|
+
async writeAudioFrame(_frame) {
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Write a DTMF tone using the transport's preferred method.
|
|
1079
|
+
*
|
|
1080
|
+
* @param frame The DTMF frame to write.
|
|
1081
|
+
*/
|
|
1082
|
+
async writeDtmf(frame) {
|
|
1083
|
+
if (this._supportsNativeDtmf()) {
|
|
1084
|
+
await this._writeDtmfNative(frame);
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
await this._writeDtmfAudio(frame);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Handle a queued frame after preceding audio has been sent.
|
|
1092
|
+
*
|
|
1093
|
+
* Override in transport subclasses to handle custom frame types that flow
|
|
1094
|
+
* through the audio queue. Called by the media sender after the frame has
|
|
1095
|
+
* waited for any preceding audio to finish.
|
|
1096
|
+
*
|
|
1097
|
+
* @param frame The frame to handle.
|
|
1098
|
+
*/
|
|
1099
|
+
async writeTransportFrame(_frame) { }
|
|
1100
|
+
/**
|
|
1101
|
+
* Override in transport implementations that support native DTMF.
|
|
1102
|
+
*
|
|
1103
|
+
* @returns True if the transport supports native DTMF, false otherwise.
|
|
1104
|
+
*/
|
|
1105
|
+
_supportsNativeDtmf() {
|
|
1106
|
+
return false;
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Override in transport implementations for native DTMF.
|
|
1110
|
+
*
|
|
1111
|
+
* @param frame The DTMF frame to write.
|
|
1112
|
+
*/
|
|
1113
|
+
async _writeDtmfNative(_frame) {
|
|
1114
|
+
throw new Error("Transport claims native DTMF support but doesn't implement it");
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Generate and send audio tones for DTMF.
|
|
1118
|
+
*
|
|
1119
|
+
* @param frame The DTMF frame to write.
|
|
1120
|
+
*/
|
|
1121
|
+
async _writeDtmfAudio(frame) {
|
|
1122
|
+
if (!frame.buttons || frame.buttons.length === 0)
|
|
1123
|
+
return;
|
|
1124
|
+
for (const button of frame.buttons) {
|
|
1125
|
+
const dtmfAudio = await loadDtmfAudio(button, { sampleRate: this._sampleRate });
|
|
1126
|
+
const dtmfAudioFrame = new OutputAudioRawFrame({
|
|
1127
|
+
audio: dtmfAudio,
|
|
1128
|
+
sampleRate: this._sampleRate,
|
|
1129
|
+
numChannels: 1,
|
|
1130
|
+
});
|
|
1131
|
+
await this.writeAudioFrame(dtmfAudioFrame);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Send an audio frame downstream.
|
|
1136
|
+
*
|
|
1137
|
+
* @param frame The audio frame to send.
|
|
1138
|
+
*/
|
|
1139
|
+
async sendAudio(frame) {
|
|
1140
|
+
await this.queueFrame(frame, FrameDirection.DOWNSTREAM);
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Send an image frame downstream.
|
|
1144
|
+
*
|
|
1145
|
+
* @param frame The image frame to send.
|
|
1146
|
+
*/
|
|
1147
|
+
async sendImage(frame) {
|
|
1148
|
+
await this.queueFrame(frame, FrameDirection.DOWNSTREAM);
|
|
1149
|
+
}
|
|
1150
|
+
//
|
|
1151
|
+
// Frame processor
|
|
1152
|
+
//
|
|
1153
|
+
/**
|
|
1154
|
+
* Process incoming frames and handle transport-specific logic.
|
|
1155
|
+
*
|
|
1156
|
+
* @param frame The frame to process.
|
|
1157
|
+
* @param direction The direction of frame flow in the pipeline.
|
|
1158
|
+
*/
|
|
1159
|
+
async processFrame(frame, direction) {
|
|
1160
|
+
await super.processFrame(frame, direction);
|
|
1161
|
+
if (frame instanceof StartFrame) {
|
|
1162
|
+
// Push StartFrame before start(), because we want StartFrame to be
|
|
1163
|
+
// processed by every processor before any other frame is processed.
|
|
1164
|
+
await this.pushFrame(frame, direction);
|
|
1165
|
+
await this.start(frame);
|
|
1166
|
+
}
|
|
1167
|
+
else if (frame instanceof EndFrame) {
|
|
1168
|
+
await this.stop(frame);
|
|
1169
|
+
// Keep pushing EndFrame down so all the pipeline stops nicely.
|
|
1170
|
+
await this.pushFrame(frame, direction);
|
|
1171
|
+
}
|
|
1172
|
+
else if (frame instanceof CancelFrame) {
|
|
1173
|
+
await this.cancel(frame);
|
|
1174
|
+
await this.pushFrame(frame, direction);
|
|
1175
|
+
}
|
|
1176
|
+
else if (frame instanceof InterruptionFrame) {
|
|
1177
|
+
await this.pushFrame(frame, direction);
|
|
1178
|
+
await this._handleFrame(frame);
|
|
1179
|
+
}
|
|
1180
|
+
else if (frame instanceof OutputTransportMessageUrgentFrame) {
|
|
1181
|
+
await this.sendMessage(frame);
|
|
1182
|
+
}
|
|
1183
|
+
else if (frame instanceof OutputDTMFUrgentFrame) {
|
|
1184
|
+
await this.writeDtmf(frame);
|
|
1185
|
+
}
|
|
1186
|
+
else if (frame instanceof SystemFrame) {
|
|
1187
|
+
await this.pushFrame(frame, direction);
|
|
1188
|
+
}
|
|
1189
|
+
else if (direction === FrameDirection.UPSTREAM) {
|
|
1190
|
+
await this.pushFrame(frame, direction);
|
|
1191
|
+
}
|
|
1192
|
+
else {
|
|
1193
|
+
await this._handleFrame(frame);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
/** Handle frames by routing them to appropriate media senders. */
|
|
1197
|
+
async _handleFrame(frame) {
|
|
1198
|
+
const sender = this._mediaSenders.get(frame.transportDestination);
|
|
1199
|
+
if (!sender) {
|
|
1200
|
+
logger.warning(`${this} destination [${frame.transportDestination}] not registered for frame ${frame}`);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (frame instanceof InterruptionFrame) {
|
|
1204
|
+
await sender.handleInterruptions(frame);
|
|
1205
|
+
}
|
|
1206
|
+
else if (frame instanceof OutputAudioRawFrame) {
|
|
1207
|
+
await sender.handleAudioFrame(frame);
|
|
1208
|
+
}
|
|
1209
|
+
else if (frame instanceof OutputImageRawFrame || frame instanceof SpriteFrame) {
|
|
1210
|
+
await sender.handleImageFrame(frame);
|
|
1211
|
+
if (frame instanceof AssistantImageRawFrame) {
|
|
1212
|
+
// This will push it further, to be handled by the assistant
|
|
1213
|
+
// aggregator, say
|
|
1214
|
+
await sender.handleSyncFrame(frame);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
else if (frame instanceof MixerControlFrame) {
|
|
1218
|
+
await sender.handleMixerControlFrame(frame);
|
|
1219
|
+
}
|
|
1220
|
+
else if (frame instanceof TTSStoppedFrame) {
|
|
1221
|
+
await sender.handleTtsStopped(frame);
|
|
1222
|
+
}
|
|
1223
|
+
else if (frame.pts) {
|
|
1224
|
+
await sender.handleTimedFrame(frame);
|
|
1225
|
+
}
|
|
1226
|
+
else {
|
|
1227
|
+
await sender.handleSyncFrame(frame);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
//# sourceMappingURL=base_output.js.map
|