o
    \jI                     @   s  d dl Z d dlZd dlZd dlZd dlZd dlZd dlZd dlZd dlZd dl	Z	d dl
mZ d dlmZ d dlmZmZ d dlZd dlZddlmZmZ d2dd	Ze  d dlZd dlm Z d dlZd d
lmZ d dlmZmZm Z  d dl!m"Z" d dl#m$Z$ d dl%m&Z' ddl(m)Z) ddl*m+Z+m,Z, ddl-m.Z.m/Z/m0Z0 ddl1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8m9Z9 ddl:m;Z; e<dZ=e)  e, Z>e?e@ddZAe@ddZBe@ddZCe>DdpdEddZFe>DdpdZGe>Dd pdH ZIe>Dd!pdZJe>Dd"pdH ZKe>Dd#pdZLe>Dd$pd%ZMe>Dd&pdH ZNe>Dd'p!dZOe>Dd(p)dH ZPeG d)d* d*ZQG d+d, d,ZRd-d. ZSd/d0 ZTeUd1krQe VeT  dS dS )3    N)	dataclass)deque)AnyOptional   )detect_language_preferenceresolve_detected_languagereturnc                  C   sL   d} | t jv r	dS G dd d}t| }|jdg|d |t j| < dS )a  
    LiveKit Agents currently imports `livekit.agents.tokenize.blingfire`, which loads a native
    extension (`lk_blingfire`). On some Windows setups (WDAC / App Control) that extension is blocked
    and the entire `livekit.agents` import fails.

    The MCube WS bridge doesn't depend on blingfire, so we provide a minimal stub module to keep
    `livekit.agents` importable.
    z!livekit.agents.tokenize.blingfireNc                	   @   sl   e Zd Zdddddedededd	fd
dZd	ddeded	B dee fddZd	dded	B fddZ	d	S )z:_install_livekit_blingfire_stub.<locals>.SentenceTokenizer   
   F)min_sentence_lenstream_context_lenretain_formatr   r   r   r	   Nc                S   s   t || _t|| _d S N)int_min_sentence_lenbool_retain_format)selfr   r   r    r   T/var/www/html/livekitdocker/backend/agent_runtime/src/mcube_integration/ws_bridge.py__init__#   s   
zC_install_livekit_blingfire_stub.<locals>.SentenceTokenizer.__init__languagetextr   c                   s,   dd t d|p	dD } fdd|D S )Nc                 S   s   g | ]
}|  r|  qS r   )strip.0pr   r   r   
<listcomp>/   s    zW_install_livekit_blingfire_stub.<locals>.SentenceTokenizer.tokenize.<locals>.<listcomp>z(?<=[.!?])\s+ c                    s   g | ]}t | jkr|qS r   )lenr   r   r   r   r   r   0   s    )resplit)r   r   r   partsr   r"   r   tokenize-   s   zC_install_livekit_blingfire_stub.<locals>.SentenceTokenizer.tokenizec                S   s   t d)Nz2Streaming tokenizer not supported in fallback stub)NotImplementedErrorr   r   r   r   r   stream2   s   zA_install_livekit_blingfire_stub.<locals>.SentenceTokenizer.stream)
__name__
__module____qualname__r   r   r   strlistr&   r)   r   r   r   r   SentenceTokenizer"   s    
$
r/   )__all__r/   )sysmodulestypes
ModuleType__dict__update)module_namer/   stubr   r   r   _install_livekit_blingfire_stub   s   


r9   )SpeechEventType)cartesiadeepgram
elevenlabs)rtc)StreamAdapter)VAD)load_agent_runtime_dotenv)apply_voice_defaults_to_dictget_default_mcube_call_config)MCUBE_AUDIO_SPECfrom_base64_strpcm16_to_mulaw)AI_UTTERANCES_QUEUERABBITMQ_URLcontrol_queue_namedeclare_durable_queueconnectget_channelpublish_jsontts_queue_name)agent_debug_logzmcube.ws_bridgeMCUBE_WS_BRIDGE_PORT9001MCUBE_WS_BRIDGE_HOSTz0.0.0.0	REDIS_URLzredis://localhost:6379/0system_promptr    z\n
	llm_modelllm_provider	tts_modeltts_providertts_voice_idtts_encoding	pcm_16000stt_language_codestt_model_idstt_providerc                   @   s$   e Zd ZU eed< ejd ed< dS )PendingPlaynameNfuture)r*   r+   r,   r-   __annotations__asyncioFuturer   r   r   r   r`   k   s   
 r`   c                   @   sR  e Zd ZdedejdejfddZdd Z	dd	 Z
d
d ZdefddZd=ddZd=ddZdeddfddZdefddZdefddZdd Zdefdd Zdd!d"ded#edB defd$d%Zd&d' Zd=d(d)Zd*edee fd+d,Zdd-d.ee ddfd/d0Zd=d1d2Zd*eddfd3d4Zd5d6 Zdd7d8ee ddfd9d:Zd;d< Z dS )>McubeCallSessioncall_id	websocketredis_clientc             
   C   sJ  || _ || _|| _|| _d | _d | _d | _d| _d | _d| _	i | _
t | _d| _d | _d | _t| _t| _t| _tdp>d | _d| _t| _t| _t | _!d | _"t#| _$t%| _&t'| _(t)| _*zt+dt,dt-t.tdd| _/W n t0y|   d| _/Y nw zt.td	d
| _1W n t0y   d| _1Y nw zt.tdd| _2W n t0y   d| _2Y nw zt+dt-t.tdd| _3W n t0y   d| _3Y nw d | _4d | _5d | _6d | _7d | _8d | _9d | _:d | _;t< | _=d | _>d | _?d | _@d | _Ad | _Bd| _Cd| _Dd| _Ed | _FtG | _Hd| _I| jId | _JtK | _Ld| _Md| _Nd| _Od| _Pd| _Qd S )NFfirst_messager         tts_chunk_ms200   tts_gainz0.35gffffff?playback_pace_factorz1.0g      ?r   checkpoint_every10r   i@     Td   )Rrg   wsrabbit_channelri   	stream_idactive_sequence_idcancelled_sequence_idbot_playing_bot_playing_started_atresponse_pendingpending_playsrd   Queuetts_segments_tts_done_received_tts_final_chunk_seq_last_tts_chunk_seqSYSTEM_PROMPTrT   	LLM_MODELrV   LLM_PROVIDERrW   
_MCUBE_DEFgetr   rj   _first_message_sentSTT_PROVIDERr_   STT_LANGUAGE_CODEr]   STT_MODEL_IDr^   detected_languageTTS_PROVIDERrY   	TTS_MODELrX   TTS_VOICE_IDrZ   TTS_ENCODINGr[   maxminr   floatrm   	Exceptionrp   rq   rr   business_idbot_idagent_iduser_idagent_email
agent_namestt
stt_streamEvent_stt_ready_event	_stt_task_ws_reader_task_playback_task_tts_consumer_task_control_consumer_task_stt_reconnect_count_stt_max_reconnect_attempts_stt_reconnecting_resample_state	bytearray_pcm16_16k_buffer_frame_16k_samples_frame_16k_bytesLock_sequence_lock_tts_inbound_chunksnoise_cancellation_enablednoise_gate_thresholdnoise_gate_enabledbackground_audio_enabled)r   rg   rh   rw   ri   r   r   r   r   r   s   
&


 



zMcubeCallSession.__init__c                    sd   t j|  dd| _t j|  dd| _t j|  dd| _t j|  dd| _	| 
 I d H  d S )Nzmcube.stt_events)ra   zmcube.playback_loopzmcube.tts_consumerzmcube.control_consumer)rd   create_task_consume_stt_eventsr   _playback_loopr   _consume_tts_queuer   _consume_control_queuer   _ws_reader_loopr"   r   r   r   start   s   zMcubeCallSession.startc                    s  z| j 2 zj3 d H W }zt|}W n ty"   td| j Y qw z8|d dkr6 | |I d H  n$ dkrD | 	|I d H  n dkrR | 
|I d H  ndkrXW  n#	 	 W q tyo   td| j|d Y qw 6 W |  I d H  d S W |  I d H  d S |  I d H  w )Nz$ws non-json frame ignored call_id=%seventr   mediaplayedStreamstopz,ws event handling failed call_id=%s event=%s)rv   jsonloadsr   logwarningrg   r   	_on_start	_on_media_on_played_stream	exception_cleanup)r   rawmsgr   r   r   r      s:   


"z McubeCallSession._ws_reader_loopc                    s  z| j r| jtd| j dI d H  W n	 ty   Y nw t| j D ]}|j	
 s3|j	  q'| j  z| jd urD| j  W n	 tyN   Y nw zt| dd d ur`| j I d H  W n	 tyj   Y nw | j| j| j| jfD ]}|r|
 s|  qud S )N	terminater   streamId_http_session)rx   rv   sendr   dumpsr   r.   r~   valuesrb   donecancelclearr   	end_inputgetattrr   closer   r   r   r   )r   pptr   r   r   r     s@    




zMcubeCallSession._cleanupr   c                    s   |d d | _ |d d d}t|d d dtj}|tjks(|tjkr1td| j|| t	d| j| j  | 
 I d H  |  I d H  | jr`| jsbd| _| jd	| j| jd
I d H  d S d S d S )Nr   r   mediaFormatencoding
sampleRatezNMCube audio format mismatch call_id=%s encoding=%s sr=%s (expected mulaw@8kHz)z#mcube start call_id=%s stream_id=%sTr    bot_say_textr   )rx   r   r   rD   sample_rater   r   r   rg   info_load_call_config	_init_sttrj   r   _publish_utterancer]   )r   r   r   r   r   r   r   r   -  s$   zMcubeCallSession._on_startr	   Nc           	   	      s"  z0| j d| j I dH }|sW dS t|ttfr#|jddd}nt|}t	|}t
| W n tyB   td| j Y dS w |d| j| _|dpT| jpTd	 | _|d
| j| _|d| jphd	| _|d| jps| j| _|d| jp~| j| _|d| jp| j| _|d| jp| j| _|d| jp| j| _|d| jp| j| _|d| jp| j| _dddd fdddd fdddd fdddd ffD ]#\}}}||durzt| |||| W q ty   Y qw qd}|D ]\}}||durt| ||| qdS )z
        Load per-call config from Redis (written by the /api/mcube/outbound-call endpoint).
        If not present, fall back to env defaults.
        zmcube_call_config:Nutf-8replace)errorsz+failed to load mcube call config call_id=%srT   rj   r    rV   rW   r_   r]   r^   rY   rX   rZ   r[   rm   c                 S   s   t dtdtt| S )Nrk   rl   )r   r   r   r   xr   r   r   <lambda>f  s    z4McubeCallSession._load_call_config.<locals>.<lambda>rp   c                 S      t | S r   r   r   r   r   r   r   g      rq   c                 S   r   r   r   r   r   r   r   r   h  r   rr   c                 S   s   t dtt| S )Nr   )r   r   r   r   r   r   r   r   i  s    ))r   r   )r   r   )r   r   )r   r   )r   r   )emailr   )r   r   )ri   r   rg   
isinstancebytesr   decoder-   r   r   rB   r   r   r   rT   rj   r   rV   rW   r_   r]   r^   rY   rX   rZ   r[   setattr)	r   r   raw_strcfgckattrcasterid_mapjson_keyr   r   r   r   D  sV   
	z"McubeCallSession._load_call_configc              	      s"  d| _ t | _| j  tdptdpd}tdpd}tdp&d}t | _	t
d| j| j| j| j | jdkry|sFt
d	 n3tj| jpLd
| jpPd|| j	d| _| j | _| j  tddd| jdt| jpnd| jdud dS | jdkr|st
d n5tj| jpd| jpddd|| j	d| _| j | _| j  tddd| jdt| jpd| jdud dS tjd| j|| j	d}z|| _| j | _W n ty   tjddd}t||d| _| j | _Y nw | j  tddd| jt| jpdt| jpd| jdud dS )zD
        Initialize STT stream for the configured provider.
        NELEVENLABS_API_KEYELEVEN_API_KEYr    CARTESIA_API_KEYDEEPGRAM_API_KEYzDmcube stt init provider=%s model_id=%s lang=%s noise_cancellation=%sr<   z8DEEPGRAM_API_KEY not set; falling back to elevenlabs STTznova-2en)modelr   api_keyhttp_sessionH6zws_bridge.py:_init_sttstt_init_complete)rg   providermodel_id
has_streamr;   z8CARTESIA_API_KEY not set; falling back to elevenlabs STTzink-whisper	pcm_s16le>  )r   r   r   r   r   r   scribe_v2_realtime)r  language_coder   r   T)r   	force_cpu)r   vad) r   r   r   r   r   osgetenvaiohttpClientSessionr   r   r   r_   r^   r]   r   r   r<   STTr   r)   r   setrO   rg   r-   r;   r=   r'   	SileroVADloadr?   )r   elevenlabs_keycartesia_keydeepgram_keybase_sttr
  r   r   r   r   ~  s   







zMcubeCallSession._init_sttr   c                    s   t d| j| z
| jr| j  W n	 ty   Y nw zt| dddur.| j I dH  W n	 ty8   Y nw d| _	t
 | _| j  || _|  I dH  dS )zG
        Reinitialize STT with a new language after detection.
        z+mcube reinit_stt call_id=%s new_language=%sr   N)r   r   rg   r   r   r   r   r   r   r   r   r   r   r   r]   r   r(   r   r   r   _reinit_stt_with_language  s*   

z*McubeCallSession._reinit_stt_with_languagec                    s  | j sd S | j sd S |dpi }|ddkrd S |dp#d}zt|}t|d}W n tyD   t	d| j
t| Y d S w t|ddd	d
| j\}| _| jrvtd|}|rvtdd |D t| }|| jk rvdt| }| j| t| j| jkrt| jd | j }	| jd | j= tj|	d
d| jd}
| jd usJ z| j|
 W n/ tjtjttfy } zt	d| j
| |  | j!p| j"pdI d H  W Y d }~d S d }~ww t| j| jksd S d S )Nr   trackinboundpayloadr    rt   z1invalid media payload ignored call_id=%s bytes=%sr   i@  r  hc                 s   s    | ]}t |V  qd S r   )abs)r   sr   r   r   	<genexpr>5      z-McubeCallSession._on_media.<locals>.<genexpr>    )r   num_channelssamples_per_channelz7stt push failed call_id=%s error=%s; reinitializing STTr   )#rx   r   is_setr   rE   audioopulaw2linr   r   r   rg   r!   ratecvr   r   arraysumr   r   extendr   r   r>   
AudioFramer   r   
push_framer  ClientConnectionResetErrorClientConnectionErrorConnectionResetErrorRuntimeErrorr  r   r]   )r   r   r   b64mulaw_bytespcm16_8k	pcm16_16ksamplesrmsframe_bytesframeer   r   r   r     sd   



zMcubeCallSession._on_mediac                    sT   | d}|s
d S | j |}|r&|j s(|jd  | j|d  d S d S d S )Nra   )r   r~   rb   r   
set_resultpop)r   r   ra   r   r   r   r   r   M  s   
z"McubeCallSession._on_played_streamc                    s  | j | jk ruz| j I d H  | jd usJ | j2 z3 d H W }|jtjkr| jr|j	r2|j	d nd }|r9|j
nd   h d}t fdd|D }| jd urZt | j nd}t  }t dkpk|dk} r|s|r|d	krtd
| j  |  I d H  q|jtjkr|j	r|j	d nd }|r|r|j
nd nd}|r| |I d H  q6 W n tjtjtfyO }	 z|  j d7  _ | j | jkrtd| j | jt|	 W Y d }	~	d S td| j d  d}
td| j | j| j|
t|	 t !|
I d H  z(| j"r| j"j#s| j"$ I d H  t% | _"| & I d H  td| j| j  W n t'yD } ztd| jt| W Y d }~nd }~ww W Y d }	~	n"d }	~	w t'yl }	 ztd| jt|	 W Y d }	~	d S d }	~	ww | j | jk sd S d S )Nr   r    >
   mm-hmmuh-huhokmhmyesokayyeahmmhmmrightuhhuhc                 3   s    | ]}| v V  qd S r   r   )r   wordpartialr   r   r  g  r  z7McubeCallSession._consume_stt_events.<locals>.<genexpr>g     8@r      g?z(barge-in triggered call_id=%s partial=%rr   z=STT reconnection failed after %d attempts call_id=%s error=%srt      zJSTT connection reset attempt=%d/%d call_id=%s reconnecting in %ds error=%sz1STT reconnection successful call_id=%s attempt=%dz/STT reinitialization failed call_id=%s error=%sz.STT event processing error call_id=%s error=%s)(r   r   r   waitr   typer:   INTERIM_TRANSCRIPTr{   alternativesr   r   loweranyr|   timer!   r$   r   r   rg   _barge_in_clearFINAL_TRANSCRIPT_handle_final_transcriptr  r,  r-  r.  errorr-   r   r   rd   sleepr   closedr   r  r   r   )r   evaltbackchannel_wordsis_backchannelplayback_age
word_countis_substantial_interruptr   r8  backoff_delay
init_errorr   rF  r   r   X  s~   
#

z$McubeCallSession._consume_stt_eventsr   c              	      s  | j rtd| j|d d  |  I d H  n?| jrWt|}tdd |p&dD }|d u rE|sEtddd| jt	|p:d| j| j d	 d S td
| j|||d d  | 
  |rt| jp^d|| _t| jpgdt| jkrtd| j| jpwd| j|d d  td| j| j|d d  | jdv r| j| jkr| | jI d H  tddd| jt	|pdt| jp| jpdd | j|| jp| jdI d H  d S )Nz!barge-in final call_id=%s text=%rP   c                 s   s@    | ]}d |  kodkn  pd|  kodkn  V  qdS )u   ऀu   ॿu   ಀu   ೿Nr   )r   cr   r   r   r    s    0
z<McubeCallSession._handle_final_transcript.<locals>.<genexpr>r    H1z%ws_bridge.py:_handle_final_transcript#dropped_transcript_response_pending)rg   text_lenr}   r{   zGmcube language_reply_while_pending call_id=%s pref=%s script=%s text=%rr   zLmcube language_detect_override call_id=%s stt_default=%s resolved=%s text=%rz6mcube language_resolved call_id=%s language=%s text=%r2   )r   hipublish_utterance_from_stt)rg   rd  r   r   )r{   r   r   rg   rQ  r}   r   rO  rO   r!   _force_stop_playbackr   r]   r   r-   r  r   )r   r   prefhas_indic_scriptr   r   r   rS    sr   




 z)McubeCallSession._handle_final_transcriptr   r   r   c                   s  | j 4 I d H 8 z| jd| j I d H }| jd| j dI d H  W n ty5   tt }Y nw W d   I d H  n1 I d H sFw   Y  t|| _d| _	d | _
d| _d| _d | _d | _d| _| jt||| j| j| j| j| j| j| j| j|| j| jd}|r||d< dD ]}t| |d }|d ur|||< qt| jt|d	I d H  td
dd| jt|t|pdt |t!| jpdt!| jpdd t"#d| j||| j| j| j| j|	 d S )Nz
mcube:seq:i  FTr   )rg   sequence_id
transcriptrT   rV   rW   rY   rX   rZ   r[   rm   r]   r_   r^   r   )r   r   r   r   r   r   )channel
queue_namer  H2zws_bridge.py:_publish_utterancerabbit_publish_utterancer    )rg   seqtranscript_lenhas_bot_sayrY   rX   zvpublished utterance call_id=%s seq=%s text=%r tts_provider=%s tts_model=%s tts_voice_id=%s tts_encoding=%s language=%s)$r   ri   incrrg   expirer   r   rP  ry   r{   r|   r}   r   r   r   r   rT   rV   rW   rY   rX   rZ   r[   rm   r_   r^   r   rM   rw   rG   rO   r!   r   r-   r   r   )r   r   r   r   rq  r  kvr   r   r   r     s    (

z#McubeCallSession._publish_utterancec                    s  | j s| j stdI d H  | j rt| j}| jj|ddI d H }| 4 I d H C}|2 z23 d H W }|jdd4 I d H  |j	
d}zt|}W n ty`   Y W d   I d H  q.w t|dd}| jd ur~|| jkr~	 W d   I d H  q.|d	d
kr| jd u s|| jkrtddd| j|| jd
d 	 W d   I d H  q.t|d }|| _t|d }	|  jd7  _|dkrtd| j|t|	 | j|||	fI d H  nq|d	dv rK| jd u s|| jkrtddd| j|| jt|d	pdd 	 W d   I d H  q.d| _|d}
|
d urt|
n| j| _tddd| j|| j| j| jt|d	p7dd | jdkrG|   n|   W d   I d H  n1 I d H s\w   Y  q.6 W d   I d H  d S 1 I d H suw   Y  d S )Ng{Gz?TdurableFrequeuer   rk  rK  tts_audio_chunkH5zws_bridge.py:_consume_tts_queue"tts_chunk_seq_mismatch_or_inactive)rg   msg_seq
active_seq
chunk_type	chunk_seqaudio_pcm_b64r   r   z0tts_audio_chunk_first call_id=%s seq=%s bytes=%s)tts_donetts_endtts_done_seq_mismatchr    )rg   r  r  payload_typefinal_chunk_seqH4tts_end_received)rg   rq  inbound_chunksr  last_chunk_seqr  ) rx   rd   rU  rN   rg   rw   declare_queueiteratorprocessbodyr   r   r   r   r   r   rz   ry   rO   r   rE   r   r   r   r!   r   putr-   r   r   _finalize_turn_no_audio_maybe_finish_playback)r   rn  queueq_itermessagedatar  rq  r  pcm16_8k_bytesr  r   r   r   r   6  s   
8

*0z#McubeCallSession._consume_tts_queuec                 C   s^   t d| j| j tddd| j| jd d| _d| _d| _d| _d| _d| _	d| _
d| _dS )u]   tts_end with no PCM chunks (TTS empty/failed upstream) — clear pending so STT is not stuck.z+tts_end_zero_audio call_id=%s active_seq=%sr  z$ws_bridge.py:_finalize_turn_no_audio'cleared_response_pending_after_zero_tts)rg   r  FN)r   r   rg   ry   rO   r{   r|   r}   rz   r   r   r   r"   r   r   r   r    s&   
z(McubeCallSession._finalize_turn_no_audior  c                 C   s   | dp| dp| dp| d}|rt|tsdS | }|s%dS | drHz|ddd d	dd
 }W |S  tyG   Y dS w |S )z
        Extract transfer target from tool/control payload.

        We accept several keys so different producers (ai_worker variants) stay compatible.
        transfer_tophone_numbertransfer_numbersip_uriNzsip::r   @r   )r   r   r-   r   rN  
startswithr$   r   )r   r  r  r   r   r   _extract_transfer_to  s(   
z%McubeCallSession._extract_transfer_tocancelled_seqr  c                C   s   |du r| j }|| _d| _d| _d| _d| _ d| _d| _d| _t| j	
 D ]\}}|j s4|j  q&| j	  z	 | j  q< tjyL   Y dS w )z
        Immediately stop any bot playback and cancel pending playedStream futures.
        This is used for MCube `terminate/transfer` and should be more aggressive
        than barge-in clear.
        NF)ry   rz   r{   r|   r}   r   r   r   r.   r~   itemsrb   r   r   r   r   
get_nowaitrd   
QueueEmpty)r   r  _r   r   r   r   rh    s,   



z%McubeCallSession._force_stop_playbackc                    s  t | j}| jj|ddI dH }| 4 I dH \}|2 zL3 dH W }|jdd4 I dH 1 zt|j	d}W n t
yJ   Y W d  I dH  qw | |I dH  W d  I dH  n1 I dH scw   Y  q6 W d  I dH  dS 1 I dH s{w   Y  dS )ze
        Consume per-call control queue and execute MCube call actions (terminate/transfer).
        Trx  NFrz  r   )rI   rg   rw   r  r  r  r   r   r  r   r   _handle_control_message)r   rn  r  r  r  r  r   r   r   r     s"   
(.z'McubeCallSession._consume_control_queuec              	      s   | d}| d}t|ttfrt| rt|nd }|d ur.| jd ur.|| jkr.d S |dkrM| jrE| jt	
d| jdI d H  | j|d d S |dkru| |}|rm| jrm| jt	
d| j|d	d
I d H  | j|d d S d S )NrK  rk  mcube_terminater   r   r  mcube_transfertransferT)r   r   
transferToshowOriginalCallerId)r   r   r   r-   isdigitry   rx   rv   r   r   r   rh  r  )r   r  msg_typerq  r  r  r   r   r   r    s8   

&

 



z(McubeCallSession._handle_control_messagec              
      s  | j }	 | j I d H \}}}| jd ur|| jkrq| jd u s%|| jkr&q| js6tddd| j||d q| j d| }| jsFt		 | _
d| _zdd l}t| j}||d|}W n	 tyd   Y nw t|}| jtd	tjtjt|d
|ddI d H  |dkrtddd| j||t|t|d |dkr|dks|| dkr| jtd| j|dI d H  zt|d tj }	t| j}
ttd|	d |
 I d H  W n ty   tdI d H  Y nw | j |d q)NTr~  zws_bridge.py:_playback_loopskip_playAudio_no_stream_id)rg   rq  r  _seg_r   rt   	playAudioascii)contentTyper   r  ra   )r   r   playAudio_sent_first_chunk)rg   utterance_seqr  	pcm_bytesr1  
checkpoint)r   r   ra   g        g\(\?g?played_chunk_seq)!rr   r   r   rz   ry   rx   rO   rg   r{   rP  r|   r$  r   rp   mulr   rF   rv   r   r   r   rD   r   r   base64	b64encoder   r!   rq   rd   rU  r   r  )r   rr   rq  r  r  ra   r$  gainmulawchunk_duration_spacer   r   r   r      s   




"zMcubeCallSession._playback_loopr  r  c             	   C   s   | j r| jd u r"| j r | jd u r tddd| j| j| j| jd d S |d ur(|n| j}|d u s9|| jk s9| j s;d S d| _	d | _
d| _d | _d | _d| _ d | _d | _d S )Nr  z#ws_bridge.py:_maybe_finish_playback#blocked_done_but_no_final_chunk_seq)rg   r  last_tts_chunk_seqr}   F)r   r   rO   rg   r   r   r}   r   emptyr{   r|   rz   ry   )r   r  last_playedr   r   r   r  |  s0   
z'McubeCallSession._maybe_finish_playbackc                    s   | j sd S | jsd S td| j| j | j| _d| _d | _d| _d | _t	| j
 D ]\}}|j s9|j  | j
|d  q+z| jtd| j dI d H  W n	 ty\   Y nw z	 | j  q_ tjyo   Y d S w )Nz barge-in clear call_id=%s seq=%sF
clearAudior   )rx   r{   r   r   rg   ry   rz   r|   r}   r.   r~   r  rb   r   r   r:  rv   r   r   r   r   r   r  rd   r  )r   ra   r   r   r   r   rQ    s6   

$
z McubeCallSession._barge_in_clearr	   N)!r*   r+   r,   r-   
websocketsWebSocketServerProtocolredis_asyncRedisr   r   r   r   dictr   r   r   r  r   r   r   rS  r   r   r  r   r  r   rh  r   r  r   r  rQ  r   r   r   r   rf   q   s:    
o

:x:H"JL
_
 &\rf   c                   s   t | ddpd}t | dd }|s|d urt |ddpd}td|t | dd |r2|ddd nd |r?|ddng }|rG|d nd}|sW| jdd	d
I d H  d S t|| ||d}| I d H  d S )Npathr    requestz4ws accepted path=%s remote=%s call_hint_from_path=%sremote_address/r|  i  zmissing call_id)codereason)rg   rh   rw   ri   )r   r   r   r   r$   r   rf   r   )rh   rw   ri   r  reqr%   rg   sessr   r   r   _ws_handler  s.   
r  c               	      s   t jt jd t I d H } t| I d H  tjtddt	dt
t t fdd}t }tjtjfD ]}z||| W q7 tyJ   Y q7w  fdd}| I d H  d S )	N)levelF)decode_responsesz"mcube ws bridge listening on %s:%sc                     s      s d d S d S )NT)r   r9  )r  )r   r   r   _signal_handler  s   zmain.<locals>._signal_handlerc                	      sf   t j fddttdddd4 I d H  I d H  W d   I d H  d S 1 I d H s,w   Y  d S )Nc                    s   t |  dS )N)rw   ri   )r  )rv   )rm  ri   r   r   r     s    z&main.<locals>.runner.<locals>.<lambda>i   r
   <   )max_sizeping_intervalping_timeout)r  serveWS_HOSTWS_PORTr   rm  ri   r   r   r   runner  s   .zmain.<locals>.runner)loggingbasicConfigINFOrK   rL   r  from_urlrS   r   r   r  r  rd   re   get_running_loopsignalSIGINTSIGTERMadd_signal_handlerr'   )
connectionr  loopsigr  r   r  r   main  s"   r  __main__r  )Wrd   r'  r$  r  r   r  r  r  r1   rP  dataclassesr   collectionsr   typingr   r   r3   r#   mcube_languager   r   r9   r  redis.asyncior  r  livekit.agents.stt.sttr:   livekit.pluginsr;   r<   r=   livekitr>   !livekit.agents.stt.stream_adapterr?   livekit.plugins.silero.vadr@   r  env_loadrA   mcube_defaultsrB   rC   mcube_codecrD   rE   rF   mqrG   rH   rI   rJ   rK   rL   rM   rN   mcube_debug_ndjsonrO   	getLoggerr   r   r   r  r  r  rS   r   r   r   r   r   r   r   r   r   r   r   r   r   r`   rf   r  r  r*   runr   r   r   r   <module>   s~    
&(
        S 
%