speaker-verification
audio
nemotron
tyger
experimental
File size: 5,840 Bytes
b21cfae
 
0efd0f1
b21cfae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0efd0f1
b21cfae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
"""Reproducible browser clock checks; optional actual API/voice and camera QA.

uv run --no-project --with playwright --with numpy==1.26.4 --python /path/to/python browser_checks.py [--live] [--camera]
The live fixture is synthetic text; microphone input is generated silence.
"""
import argparse
import asyncio
import json
from pathlib import Path
import subprocess
import tempfile
import numpy as np
from playwright.async_api import async_playwright


async def run(args):
    logs=Path(__file__).parent/'logs';logs.mkdir(exist_ok=True)
    with tempfile.TemporaryDirectory(prefix='robby-browser-') as temporary:
        silence=Path(temporary)/'synthetic-silence.wav'
        subprocess.run(['ffmpeg','-nostdin','-v','error','-f','lavfi','-i','anullsrc=r=48000:cl=mono',
            '-t','30',str(silence)],check=True)
        async with async_playwright() as playwright:
            browser=await playwright.chromium.launch(executable_path=args.chrome,headless=True,
                args=['--no-sandbox','--use-fake-device-for-media-stream','--use-fake-ui-for-media-stream',
                    '--use-file-for-fake-audio-capture='+str(silence),'--autoplay-policy=no-user-gesture-required'])
            try:
                page=await browser.new_page(viewport={'width':1440,'height':1000})
                errors=[];page.on('pageerror',lambda e:errors.append(str(e)))
                await page.goto(args.url);await page.wait_for_function('window.robo')
                clock=await page.evaluate('''async()=>{
                  const {PlaybackClock}=await import('/static/clock.js');let before=[],after=[];
                  for(const output of [.005,.02,.055,.15,.25])for(const input of [.005,.04,.12])for(const network of [0,.1,.5,.9]){
                    const context={currentTime:10.1,getOutputTimestamp:()=>({contextTime:10.1-output,performanceTime:10000})};
                    const c=new PlaybackClock(context);c.inputLatency=input;c.add(1,8,240000,0);
                    const actual=Math.floor((10-output-input-8)*24000),tag=c.tag(10,10000)[1];
                    after.push(Math.abs(tag-actual)/24);before.push(Math.abs(output+input+network-.15)*1000);
                  }
                  const ctx={currentTime:9,baseLatency:0,outputLatency:0};const c=new PlaybackClock(ctx);c.inputLatency=0;
                  c.add(1,9.005,240,0);const parts=c.pieces(9,320,16000,10000);
                  if(JSON.stringify(parts.map(p=>[p.first,p.end,p.id]))!==JSON.stringify([[0,80,0],[80,240,1],[240,320,0]]))throw Error('Playback-edge split failed');
                  const d=new PlaybackClock({currentTime:9,baseLatency:.1,outputLatency:0});d.inputLatency=.02;d.add(2,8,48000,0);d.stop(9);
                  const kept=d.tag(9.05,10000)[0]===2,cut=d.tag(9.5,10000)[1]===-1;if(!kept||!cut)throw Error('Cancel history failed');
                  return {before,after,playback_edge_split:true,cancel_keeps_history:kept,cancel_removes_future:cut};
                }''')
                result={'protocol':'clock-v3 explicit latency grid','scenarios':len(clock['before']),
                    'before_p50_ms':float(np.median(clock['before'])),'before_p95_ms':float(np.percentile(clock['before'],95)),
                    'after_p50_ms':float(np.median(clock['after'])),'after_max_ms':max(clock['after']),
                    **{k:v for k,v in clock.items() if k not in ('before','after')},
                    'hardware_accuracy':'Ideal-clock simulation, not physical audio-device measurement'}
                await page.screenshot(path=str(logs/'robo-desktop.png'))
                await page.set_viewport_size({'width':390,'height':844});await page.screenshot(path=str(logs/'robo-mobile.png'))
                result['mobile_no_overflow']=await page.evaluate('document.documentElement.scrollWidth<=innerWidth')
                if args.camera:
                    await page.click('.settings summary');await page.click('#camera')
                    await page.wait_for_function('''()=>{let t=document.querySelector('#camera-status').textContent;return t.startsWith('Tracking locally')||t.startsWith('No face visible')||t.includes('unavailable')}''',timeout=60000)
                    await page.wait_for_timeout(1000)
                    result['camera_status']=await page.locator('#camera-status').text_content()
                    result['camera_video_running']=await page.evaluate('document.querySelector("#video").readyState>=2')
                    await page.click('#camera')
                if args.live:
                    await page.click('#call');await page.wait_for_function('window.robo.ws?.readyState===1')
                    await page.fill('#message','Say hello in just four words.');await page.click('button[type=submit]')
                    await page.wait_for_function('window.robo.metrics.tagged_blocks>0',timeout=90000)
                    await page.wait_for_function('document.querySelector("#phase-text").textContent==="Listening to you"',timeout=30000)
                    result['live']=await page.evaluate('({...window.robo.metrics,clock:window.robo.clock.report()})')
                    await page.evaluate('window.robo.stop()')
                result['page_errors']=errors
                assert not errors and result['mobile_no_overflow'] and result['after_max_ms']<.05,result
                (logs/'browser-checks.json').write_text(json.dumps(result,indent=2))
                print(json.dumps(result,indent=2))
            finally:await browser.close()


if __name__=='__main__':
    parser=argparse.ArgumentParser();parser.add_argument('--url',default='http://127.0.0.1:8793')
    parser.add_argument('--chrome',default='/usr/bin/google-chrome');parser.add_argument('--live',action='store_true')
    parser.add_argument('--camera',action='store_true');asyncio.run(run(parser.parse_args()))