4848API_VERSION = "v1"
4949PROTOCOL_VERSION = "realtimestt.remote.v1"
5050SERVER_NAME = "RealtimeSTT production server"
51- _PACKAGE_VERSION_FALLBACK = "1.1.1 "
51+ _PACKAGE_VERSION_FALLBACK = "1.1.2 "
5252
5353
5454def _package_version () -> str :
@@ -66,6 +66,7 @@ def _package_version() -> str:
6666PCM_FORMAT = "pcm_s16le"
6767PREVIEW_TAIL_SECONDS = 5.0
6868PREVIEW_EMPTY_RETRY_SILENCE_SECONDS = 0.5
69+ PREVIEW_EARLY_RMS_FLUSH_SILENCE_SECONDS = 0.025
6970LATE_FINAL_OPERATION = "late_full_turn_correction"
7071RESUME_ACK_TYPE = "resume_ack"
7172_LIVE_CANCEL = object ()
@@ -514,6 +515,18 @@ def capabilities_for(settings: ProductionServerSettings) -> Dict[str, Any]:
514515 # realtime is retained as a descriptive alias for live/partial.
515516 "realtime" : dict (live_contract ),
516517 },
518+ "preview" : {
519+ "inputCoverage" : "full_turn" ,
520+ "earlyRms" : {
521+ "supported" : True ,
522+ "requestMode" : "early_rms" ,
523+ "firstAttemptSilenceMs" : (
524+ PREVIEW_EARLY_RMS_FLUSH_SILENCE_SECONDS * 1000.0
525+ ),
526+ "maxAttempts" : 1 ,
527+ "emptyRetry" : False ,
528+ },
529+ },
517530 "resume" : {
518531 "command" : "resume" ,
519532 "ackType" : RESUME_ACK_TYPE ,
@@ -2913,6 +2926,7 @@ def _start_preview_worker(
29132926 candidate_base_text : str ,
29142927 resume_request_id : Optional [str ],
29152928 resume_epoch : int ,
2929+ preview_mode : str ,
29162930 preview_epoch : int ,
29172931 cancelled : threading .Event ,
29182932 ) -> None :
@@ -2936,6 +2950,7 @@ def _start_preview_worker(
29362950 candidate_base_text ,
29372951 resume_request_id ,
29382952 resume_epoch ,
2953+ preview_mode ,
29392954 preview_epoch ,
29402955 cancelled ,
29412956 )
@@ -3024,6 +3039,7 @@ def _run_preview_worker(
30243039 candidate_base_text : str ,
30253040 resume_request_id : Optional [str ],
30263041 resume_epoch : int ,
3042+ preview_mode : str ,
30273043 preview_epoch : int ,
30283044 cancelled : threading .Event ,
30293045 _release_thread : bool = True ,
@@ -3043,6 +3059,7 @@ def _run_preview_worker(
30433059 empty_retry_attempted = False
30443060 empty_retry_recovered = False
30453061 empty_retry_error = None
3062+ empty_retry_suppressed_reason = None
30463063 matched = False
30473064 used_fuzzy_match = False
30483065 anchor_length = 0
@@ -3238,16 +3255,37 @@ def run_asr_attempt(
32383255 cancelled ,
32393256 ):
32403257 return
3258+ first_attempt_audio = audio
3259+ first_attempt_silence_ms = 0.0
3260+ if preview_mode == "early_rms" :
3261+ first_attempt_silence_samples = int (
3262+ round (
3263+ PREVIEW_EARLY_RMS_FLUSH_SILENCE_SECONDS
3264+ * SERVER_SAMPLE_RATE
3265+ )
3266+ )
3267+ first_attempt_audio = np .concatenate (
3268+ (
3269+ audio ,
3270+ np .zeros (
3271+ first_attempt_silence_samples ,
3272+ dtype = np .float32 ,
3273+ ),
3274+ )
3275+ )
3276+ first_attempt_silence_ms = (
3277+ PREVIEW_EARLY_RMS_FLUSH_SILENCE_SECONDS * 1000.0
3278+ )
32413279 tail_text , first_attempt , first_error = run_asr_attempt (
3242- audio ,
3280+ first_attempt_audio ,
32433281 attempt_index = 1 ,
3244- added_silence_ms = 0.0 ,
3282+ added_silence_ms = first_attempt_silence_ms ,
32453283 )
32463284 asr_attempts .append (first_attempt )
32473285 if first_error :
32483286 raise RuntimeError (str (first_error ))
32493287
3250- if not tail_text :
3288+ if not tail_text and preview_mode != "early_rms" :
32513289 # The native transducer can occasionally return zero
32523290 # tokens when a snapshot ends directly on its final
32533291 # acoustic frame. Give every empty result exactly one
@@ -3300,6 +3338,13 @@ def run_asr_attempt(
33003338 else :
33013339 tail_text = retry_text
33023340 empty_retry_recovered = bool (tail_text )
3341+ elif not tail_text :
3342+ # An eager browser RMS probe is advisory and can be
3343+ # superseded within milliseconds. Do not spend a
3344+ # second native decode plus 500 ms suffix on an empty
3345+ # result; authoritative and legacy Preview requests
3346+ # retain the quality-preserving retry above.
3347+ empty_retry_suppressed_reason = "early_rms"
33033348
33043349 queue_values = [
33053350 float (attempt ["queueMs" ])
@@ -3403,6 +3448,7 @@ def run_asr_attempt(
34033448 "type" : "preview" ,
34043449 "turnId" : turn_id ,
34053450 "previewRequestId" : request_id ,
3451+ "previewMode" : preview_mode ,
34063452 "text" : cumulative_text ,
34073453 "cumulativeText" : cumulative_text ,
34083454 "liveText" : cumulative_live_text ,
@@ -3483,6 +3529,12 @@ def run_asr_attempt(
34833529 else 0.0
34843530 ),
34853531 "emptyRetryError" : empty_retry_error ,
3532+ "emptyRetrySuppressedReason" : empty_retry_suppressed_reason ,
3533+ "firstAttemptSilenceMs" : (
3534+ PREVIEW_EARLY_RMS_FLUSH_SILENCE_SECONDS * 1000.0
3535+ if preview_mode == "early_rms"
3536+ else 0.0
3537+ ),
34863538 "requestToPublishMs" : round (
34873539 max (0.0 , publish_ready_at - requested_at ) * 1000.0 ,
34883540 3 ,
@@ -3685,6 +3737,11 @@ async def preview(self, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
36853737 "invalid_preview_request" ,
36863738 "previewRequestId must be a non-empty string of at most 128 characters" ,
36873739 )
3740+ preview_mode = (
3741+ "early_rms"
3742+ if payload .get ("previewMode" ) == "early_rms"
3743+ else "authoritative"
3744+ )
36883745 audio_revision = turn .audio_revision
36893746 audio_frames = turn .audio_frames
36903747 turn .preview_requested = True
@@ -3737,6 +3794,7 @@ async def preview(self, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
37373794 candidate_base_text ,
37383795 resume_request_id ,
37393796 resume_epoch ,
3797+ preview_mode ,
37403798 preview_epoch ,
37413799 cancelled ,
37423800 )
@@ -3747,6 +3805,7 @@ async def preview(self, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
37473805 "sessionId" : self .session_id ,
37483806 "turnId" : turn_id ,
37493807 "previewRequestId" : request_id ,
3808+ "previewMode" : preview_mode ,
37503809 "audioPackets" : packet_count ,
37513810 "audioDurationSeconds" : round (audio_seconds , 6 ),
37523811 }
@@ -3825,6 +3884,7 @@ async def _finalize_preview_only(self) -> Optional[Dict[str, Any]]:
38253884 candidate_base_text ,
38263885 resume_request_id ,
38273886 resume_epoch ,
3887+ "authoritative" ,
38283888 preview_epoch ,
38293889 preview_cancelled ,
38303890 )
0 commit comments