gsaon commited on
Commit
c9d8ae0
·
verified ·
1 Parent(s): e02e89b

Load the capture worklet from a blob URL too; stop probing module imports the app no longer uses

Browse files
Files changed (3) hide show
  1. app.bundle.js +32 -3
  2. ctc-streaming.js +32 -3
  3. index.html +9 -24
app.bundle.js CHANGED
@@ -681,6 +681,37 @@ Object.assign(__M, { CtcEngine });
681
  const SAMPLE_RATE = 16000;
682
  const INT16_SCALE = 32768; // gate thresholds are in Int16 RMS units (as tuned)
683
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
  /**
685
  * Energy speech/silence gate with an adaptive noise floor.
686
  *
@@ -1007,9 +1038,7 @@ class StreamingCoordinator {
1007
  },
1008
  });
1009
  this.audioContext = new AudioContext({ sampleRate: SAMPLE_RATE });
1010
- // Resolve against this module, not the document: a bare './' would break
1011
- // for any page not sitting in the same directory as these scripts.
1012
- await this.audioContext.audioWorklet.addModule(new URL('./ctc-capture-worklet.js', document.baseURI));
1013
  const source = this.audioContext.createMediaStreamSource(this.mediaStream);
1014
  const node = new AudioWorkletNode(this.audioContext, 'ctc-capture');
1015
  node.port.onmessage = (e) => this.onSamples(e.data);
 
681
  const SAMPLE_RATE = 16000;
682
  const INT16_SCALE = 32768; // gate thresholds are in Int16 RMS units (as tuned)
683
 
684
+ /**
685
+ * URL to hand `audioWorklet.addModule()`.
686
+ *
687
+ * addModule fetches its argument as a *module*, which is the one request type a
688
+ * private Space's cross-site iframe fails to authenticate — measured there, the
689
+ * same file returns 200 to a plain fetch and intermittently fails as a module
690
+ * import. That would break dictation at the moment the user presses record, well
691
+ * after everything else had loaded cleanly. So fetch the source (plain fetches
692
+ * work) and hand addModule a blob: URL, which needs no network request at all.
693
+ * Falls back to the direct URL, which is what works everywhere else.
694
+ *
695
+ * Resolved against this module rather than the document so it survives being
696
+ * served from a subdirectory; build_bundle.py rewrites `document.baseURI` to
697
+ * `document.baseURI` for the bundled build, where the two are the same directory.
698
+ */
699
+ let cachedWorkletUrl = null;
700
+ async function captureWorkletUrl() {
701
+ if (cachedWorkletUrl) return cachedWorkletUrl;
702
+ const direct = new URL('./ctc-capture-worklet.js', document.baseURI).href;
703
+ try {
704
+ const r = await fetch(direct);
705
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
706
+ const source = await r.text();
707
+ cachedWorkletUrl = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
708
+ } catch (e) {
709
+ console.warn('could not preload the capture worklet; using its URL directly:', e);
710
+ cachedWorkletUrl = direct;
711
+ }
712
+ return cachedWorkletUrl;
713
+ }
714
+
715
  /**
716
  * Energy speech/silence gate with an adaptive noise floor.
717
  *
 
1038
  },
1039
  });
1040
  this.audioContext = new AudioContext({ sampleRate: SAMPLE_RATE });
1041
+ await this.audioContext.audioWorklet.addModule(await captureWorkletUrl());
 
 
1042
  const source = this.audioContext.createMediaStreamSource(this.mediaStream);
1043
  const node = new AudioWorkletNode(this.audioContext, 'ctc-capture');
1044
  node.port.onmessage = (e) => this.onSamples(e.data);
ctc-streaming.js CHANGED
@@ -23,6 +23,37 @@
23
  const SAMPLE_RATE = 16000;
24
  const INT16_SCALE = 32768; // gate thresholds are in Int16 RMS units (as tuned)
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  /**
27
  * Energy speech/silence gate with an adaptive noise floor.
28
  *
@@ -349,9 +380,7 @@ export class StreamingCoordinator {
349
  },
350
  });
351
  this.audioContext = new AudioContext({ sampleRate: SAMPLE_RATE });
352
- // Resolve against this module, not the document: a bare './' would break
353
- // for any page not sitting in the same directory as these scripts.
354
- await this.audioContext.audioWorklet.addModule(new URL('./ctc-capture-worklet.js', import.meta.url));
355
  const source = this.audioContext.createMediaStreamSource(this.mediaStream);
356
  const node = new AudioWorkletNode(this.audioContext, 'ctc-capture');
357
  node.port.onmessage = (e) => this.onSamples(e.data);
 
23
  const SAMPLE_RATE = 16000;
24
  const INT16_SCALE = 32768; // gate thresholds are in Int16 RMS units (as tuned)
25
 
26
+ /**
27
+ * URL to hand `audioWorklet.addModule()`.
28
+ *
29
+ * addModule fetches its argument as a *module*, which is the one request type a
30
+ * private Space's cross-site iframe fails to authenticate — measured there, the
31
+ * same file returns 200 to a plain fetch and intermittently fails as a module
32
+ * import. That would break dictation at the moment the user presses record, well
33
+ * after everything else had loaded cleanly. So fetch the source (plain fetches
34
+ * work) and hand addModule a blob: URL, which needs no network request at all.
35
+ * Falls back to the direct URL, which is what works everywhere else.
36
+ *
37
+ * Resolved against this module rather than the document so it survives being
38
+ * served from a subdirectory; build_bundle.py rewrites `import.meta.url` to
39
+ * `document.baseURI` for the bundled build, where the two are the same directory.
40
+ */
41
+ let cachedWorkletUrl = null;
42
+ async function captureWorkletUrl() {
43
+ if (cachedWorkletUrl) return cachedWorkletUrl;
44
+ const direct = new URL('./ctc-capture-worklet.js', import.meta.url).href;
45
+ try {
46
+ const r = await fetch(direct);
47
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
48
+ const source = await r.text();
49
+ cachedWorkletUrl = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
50
+ } catch (e) {
51
+ console.warn('could not preload the capture worklet; using its URL directly:', e);
52
+ cachedWorkletUrl = direct;
53
+ }
54
+ return cachedWorkletUrl;
55
+ }
56
+
57
  /**
58
  * Energy speech/silence gate with an adaptive noise floor.
59
  *
 
380
  },
381
  });
382
  this.audioContext = new AudioContext({ sampleRate: SAMPLE_RATE });
383
+ await this.audioContext.audioWorklet.addModule(await captureWorkletUrl());
 
 
384
  const source = this.audioContext.createMediaStreamSource(this.mediaStream);
385
  const node = new AudioWorkletNode(this.audioContext, 'ctc-capture');
386
  node.port.onmessage = (e) => this.onSamples(e.data);
index.html CHANGED
@@ -117,15 +117,15 @@
117
  *
118
  * When this demo is served from a *private* Space, huggingface.co embeds it
119
  * in a cross-site iframe, and browsers withhold the auth cookie from
120
- * CORS-mode requests in that context. ES module imports are fetched in CORS
121
- * mode, so `import` of the ctc-*.js files returns 401 while the surrounding
122
- * classic scripts load normally — app.js is fetched but can never execute,
123
- * and the page sits on "Loading..." with the reason visible only as a 401 in
124
- * devtools (and only if you know to switch to the iframe's console).
125
  *
126
- * These probes report which request types actually carry credentials here,
127
- * on the page, in the frame that matters. They stay quiet when everything
128
- * works and surface themselves when something doesn't.
129
  */
130
  // Capture failures from the app's own scripts. Registered synchronously,
131
  // before the <script> tags below, and in the capture phase so it also sees
@@ -172,28 +172,13 @@
172
  const results = [
173
  await probe('plain fetch (small asset)', () => status('./onnx-ctc/frontend_config.json')),
174
  await probe('plain fetch (worklet file)', () => status('./ctc-capture-worklet.js')),
175
- await probe('ES module import', async () => {
176
- const m = await import('./ctc-tokenizer.js');
177
- return m.CtcDecoder ? 'imported' : 'imported but empty';
178
- }),
179
  await probe('classic script (onnxruntime)', async () => {
180
  if (typeof ort === 'undefined') throw new Error('global `ort` is undefined');
181
  return `ort ${ort.env?.versions?.common ?? 'loaded'}`;
182
  }),
183
  await probe('LFS range (model weights)', () => ranged('./onnx-ctc/ctc_conformer_q4f16.onnx.data')),
184
  await probe('LFS range (punctuator)', () => ranged('./punct_cap_seg_en.onnx')),
185
- // The exact two files app.js imports. The first console log from the
186
- // Space showed 401s on precisely these while everything else loaded,
187
- // so they are probed by name rather than by proxy.
188
- await probe('import ctc-engine.js', async () => {
189
- const m = await import('./ctc-engine.js');
190
- return m.CtcEngine ? 'imported' : 'imported but empty';
191
- }),
192
- await probe('import ctc-streaming.js', async () => {
193
- const m = await import('./ctc-streaming.js');
194
- return m.StreamingCoordinator ? 'imported' : 'imported but empty';
195
- }),
196
- await probe('fetch app.js', () => status('./app.js')),
197
  await probe('WebGPU adapter', async () => {
198
  if (!navigator.gpu) throw new Error('navigator.gpu is undefined');
199
  const a = await navigator.gpu.requestAdapter();
 
117
  *
118
  * When this demo is served from a *private* Space, huggingface.co embeds it
119
  * in a cross-site iframe, and browsers withhold the auth cookie from
120
+ * CORS-mode requests there — ES module fetches. That is why the app ships as
121
+ * one bundled classic script (build_bundle.py) and why the ORT wasm glue and
122
+ * the capture worklet are loaded from blob: URLs. ES module imports are
123
+ * deliberately NOT probed here: nothing loads them any more, and they fail
124
+ * intermittently in this context, so probing them only cries wolf.
125
  *
126
+ * What remains worth checking is that every asset the app really fetches is
127
+ * reachable, and that the app becomes usable. Silent on a healthy load;
128
+ * shows itself, with the status line, when something breaks.
129
  */
130
  // Capture failures from the app's own scripts. Registered synchronously,
131
  // before the <script> tags below, and in the capture phase so it also sees
 
172
  const results = [
173
  await probe('plain fetch (small asset)', () => status('./onnx-ctc/frontend_config.json')),
174
  await probe('plain fetch (worklet file)', () => status('./ctc-capture-worklet.js')),
 
 
 
 
175
  await probe('classic script (onnxruntime)', async () => {
176
  if (typeof ort === 'undefined') throw new Error('global `ort` is undefined');
177
  return `ort ${ort.env?.versions?.common ?? 'loaded'}`;
178
  }),
179
  await probe('LFS range (model weights)', () => ranged('./onnx-ctc/ctc_conformer_q4f16.onnx.data')),
180
  await probe('LFS range (punctuator)', () => ranged('./punct_cap_seg_en.onnx')),
181
+ await probe('fetch app bundle', () => status('./app.bundle.js')),
 
 
 
 
 
 
 
 
 
 
 
182
  await probe('WebGPU adapter', async () => {
183
  if (!navigator.gpu) throw new Error('navigator.gpu is undefined');
184
  const a = await navigator.gpu.requestAdapter();