Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions js/hang/src/publish/audio/captions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type * as Catalog from "../../catalog";
import { u8 } from "../../catalog";
import type { Audio } from ".";
import type { Request, Result } from "./captions-worker";
import CaptureWorklet from "./capture-worklet?worker&url";

export type CaptionsProps = {
enabled?: boolean;
Expand Down Expand Up @@ -93,7 +92,14 @@ export class Captions {

// The workload needs to be loaded asynchronously, unfortunately, but it should be instant.
effect.spawn(async () => {
await ctx.audioWorklet.addModule(CaptureWorklet);
// Hacky workaround to support Webpack and Vite:
// https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214

const { register } = navigator.serviceWorker;
// @ts-ignore hack to make webpack believe that it is registering a worker
navigator.serviceWorker.register = (url: URL) => ctx.audioWorklet.addModule(url);
await navigator.serviceWorker.register(new URL("./capture-worklet", import.meta.url));
navigator.serviceWorker.register = register;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Same global override concerns: add guard + try/finally and prefer @ts-expect-error

Mirror the safer pattern here to prevent leaking the patched register and to handle no-SW environments gracefully.

Apply this diff:

-      // Hacky workaround to support Webpack and Vite:
-      // https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214
-
-      const { register } = navigator.serviceWorker;
-      // @ts-ignore hack to make webpack believe that it is registering a worker
-      navigator.serviceWorker.register = (url: URL) => ctx.audioWorklet.addModule(url);
-      await navigator.serviceWorker.register(new URL("./capture-worklet", import.meta.url));
-      navigator.serviceWorker.register = register;
+      // Hacky workaround to support Webpack and Vite:
+      // https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214
+      const sw: any = (navigator as any).serviceWorker;
+      const workletUrl = new URL("./capture-worklet", import.meta.url);
+
+      if (!sw || typeof sw.register !== "function") {
+        await ctx.audioWorklet.addModule(workletUrl);
+      } else {
+        const originalRegister = sw.register.bind(sw);
+        // @ts-expect-error: Intentionally masquerading as ServiceWorker.register for bundlers.
+        sw.register = (url: URL) => ctx.audioWorklet.addModule(url);
+        try {
+          await sw.register(workletUrl);
+        } finally {
+          sw.register = originalRegister;
+        }
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Hacky workaround to support Webpack and Vite:
// https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214
const { register } = navigator.serviceWorker;
// @ts-ignore hack to make webpack believe that it is registering a worker
navigator.serviceWorker.register = (url: URL) => ctx.audioWorklet.addModule(url);
await navigator.serviceWorker.register(new URL("./capture-worklet", import.meta.url));
navigator.serviceWorker.register = register;
// Hacky workaround to support Webpack and Vite:
// https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214
const sw: any = (navigator as any).serviceWorker;
const workletUrl = new URL("./capture-worklet", import.meta.url);
if (!sw || typeof sw.register !== "function") {
await ctx.audioWorklet.addModule(workletUrl);
} else {
const originalRegister = sw.register.bind(sw);
// @ts-expect-error: Intentionally masquerading as ServiceWorker.register for bundlers.
sw.register = (url: URL) => ctx.audioWorklet.addModule(url);
try {
await sw.register(workletUrl);
} finally {
sw.register = originalRegister;
}
}
🤖 Prompt for AI Agents
In js/hang/src/publish/audio/captions.ts around lines 95 to 103, the code
overrides navigator.serviceWorker.register without a guard or try/finally and
uses @ts-ignore; change it to first check that navigator.serviceWorker and
navigator.serviceWorker.register exist, save the original register, replace it
with the worklet-adding function inside a try block, await registering the
worklet URL, and restore the original register in a finally block; also replace
the // @ts-ignore with // @ts-expect-error to indicate the intentional type
bypass and ensure the code no-ops gracefully when service workers are
unavailable.

// Create the worklet.
const worklet = new AudioWorkletNode(ctx, "capture", {
Expand Down
17 changes: 11 additions & 6 deletions js/hang/src/publish/audio/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import * as Moq from "@kixelated/moq";
import { Effect, type Getter, Signal } from "@kixelated/signals";
import type * as Catalog from "../../catalog";
import { u8, u53 } from "../../catalog/integers";
import { u53, u8 } from "../../catalog/integers";
import * as Container from "../../container";
import { Captions, type CaptionsProps } from "./captions";
import type * as Capture from "./capture";
import { Speaking, type SpeakingProps } from "./speaking";

export * from "./captions";

const GAIN_MIN = 0.001;
const FADE_TIME = 0.2;

// Unfortunately, we need to use a Vite-exclusive import for now.
import CaptureWorklet from "./capture-worklet?worker&url";
import { Speaking, type SpeakingProps } from "./speaking";

export type AudioConstraints = Omit<
MediaTrackConstraints,
"aspectRatio" | "backgroundBlur" | "displaySurface" | "facingMode" | "frameRate" | "height" | "width"
Expand Down Expand Up @@ -132,7 +129,15 @@ export class Audio {

// Async because we need to wait for the worklet to be registered.
effect.spawn(async () => {
await context.audioWorklet.addModule(CaptureWorklet);
// Hacky workaround to support Webpack and Vite:
// https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214

const { register } = navigator.serviceWorker;
// @ts-ignore hack to make webpack believe that it is registering a worker
navigator.serviceWorker.register = (url: URL) => context.audioWorklet.addModule(url);
await navigator.serviceWorker.register(new URL("./capture-worklet", import.meta.url));
navigator.serviceWorker.register = register;

const worklet = new AudioWorkletNode(context, "capture", {
numberOfInputs: 1,
numberOfOutputs: 0,
Expand Down
5 changes: 1 addition & 4 deletions js/hang/src/publish/video/detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import * as Comlink from "comlink";
import * as Catalog from "../../catalog";
import type { Video } from ".";
import type { DetectionWorker } from "./detection-worker";
// Vite-specific import for worker
import WorkerUrl from "./detection-worker?worker&url";

export type DetectionProps = {
enabled?: boolean;
Expand Down Expand Up @@ -52,8 +50,7 @@ export class Detection {
track: { name: this.#track.name, priority: Catalog.u8(this.#track.priority) },
});

// Initialize worker
const worker = new Worker(WorkerUrl, { type: "module" });
const worker = new Worker(new URL("./detection-worker", import.meta.url), { type: "module" });
effect.cleanup(() => worker.terminate());

const api = Comlink.wrap<DetectionWorker>(worker);
Expand Down
2 changes: 1 addition & 1 deletion js/hang/src/support/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export default class HangSupport extends HTMLElement {
if (mode !== "publish") {
addRow("Rendering", "Audio", binary(support.audio.render));
addRow("", "Video", binary(support.video.render));
addRow("Decoding", "Audio", binary(support.audio.decoding?.opus));
addRow("Decoding", "Opus", binary(support.audio.decoding?.opus));
addRow("", "AAC", binary(support.audio.decoding?.aac));
addRow("", "AV1", hardware(support.video.decoding?.av1));
addRow("", "H.265", hardware(support.video.decoding?.h265));
Expand Down
13 changes: 9 additions & 4 deletions js/hang/src/watch/audio/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ export type AudioProps = {
speaking?: SpeakingProps;
};

// Unfortunately, we need to use a Vite-exclusive import for now.
import RenderWorklet from "./render-worklet?worker&url";

// Downloads audio from a track and emits it to an AudioContext.
// The user is responsible for hooking up audio to speakers, an analyzer, etc.
export class Audio {
Expand Down Expand Up @@ -94,7 +91,15 @@ export class Audio {

effect.spawn(async () => {
// Register the AudioWorklet processor
await context.audioWorklet.addModule(RenderWorklet);

// Hacky workaround to support Webpack and Vite:
// https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214

const { register } = navigator.serviceWorker;
// @ts-ignore hack to make webpack believe that it is registering a worker
navigator.serviceWorker.register = (url: URL) => context.audioWorklet.addModule(url);
await navigator.serviceWorker.register(new URL("./render-worklet", import.meta.url));
navigator.serviceWorker.register = register;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard and always-restore when monkey-patching navigator.serviceWorker.register

Overriding a global API without a guard and without a try/finally can leave the environment in a broken state if addModule throws. It will also throw on platforms where navigator.serviceWorker is undefined (HTTP, some Safari contexts).

Apply this diff to add a guard and guarantee restoration:

-      // Hacky workaround to support Webpack and Vite:
-      // https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214
-
-      const { register } = navigator.serviceWorker;
-      // @ts-ignore hack to make webpack believe that it is registering a worker
-      navigator.serviceWorker.register = (url: URL) => context.audioWorklet.addModule(url);
-      await navigator.serviceWorker.register(new URL("./render-worklet", import.meta.url));
-      navigator.serviceWorker.register = register;
+      // Hacky workaround to support Webpack and Vite:
+      // https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214
+      const sw: any = (navigator as any).serviceWorker;
+      const workletUrl = new URL("./render-worklet", import.meta.url);
+
+      if (!sw || typeof sw.register !== "function") {
+        // Fallback: no Service Worker support—load directly.
+        await context.audioWorklet.addModule(workletUrl);
+      } else {
+        const originalRegister = sw.register.bind(sw);
+        // @ts-expect-error: Intentionally masquerading as ServiceWorker.register for bundlers.
+        sw.register = (url: URL) => context.audioWorklet.addModule(url);
+        try {
+          await sw.register(workletUrl);
+        } finally {
+          sw.register = originalRegister;
+        }
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Hacky workaround to support Webpack and Vite:
// https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214
const { register } = navigator.serviceWorker;
// @ts-ignore hack to make webpack believe that it is registering a worker
navigator.serviceWorker.register = (url: URL) => context.audioWorklet.addModule(url);
await navigator.serviceWorker.register(new URL("./render-worklet", import.meta.url));
navigator.serviceWorker.register = register;
// Hacky workaround to support Webpack and Vite:
// https://github.com/webpack/webpack/issues/11543#issuecomment-2045809214
const sw: any = (navigator as any).serviceWorker;
const workletUrl = new URL("./render-worklet", import.meta.url);
if (!sw || typeof sw.register !== "function") {
// Fallback: no Service Worker support—load directly.
await context.audioWorklet.addModule(workletUrl);
} else {
const originalRegister = sw.register.bind(sw);
// @ts-expect-error: Intentionally masquerading as ServiceWorker.register for bundlers.
sw.register = (url: URL) => context.audioWorklet.addModule(url);
try {
await sw.register(workletUrl);
} finally {
sw.register = originalRegister;
}
}
🤖 Prompt for AI Agents
In js/hang/src/watch/audio/index.ts around lines 95 to 103, the code
monkey-patches navigator.serviceWorker.register without checking
navigator.serviceWorker exists and without a try/finally to guarantee
restoration; change it to first guard that navigator.serviceWorker and
navigator.serviceWorker.register are defined, save the original register, then
perform the temporary assignment and await addModule inside a try block, and
always restore the original register in a finally block so the global is never
left patched even if addModule throws or navigator.serviceWorker is absent.

// Create the worklet node
const worklet = new AudioWorkletNode(context, "render");
Expand Down
5 changes: 3 additions & 2 deletions js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
"concurrently": "^9.1.2",
"cpy-cli": "^5.0.0",
"knip": "^5.60.2",
"prettier": "^3.6.2",
"rimraf": "^6.0.1",
"typescript": "^5.8.3",
"tsx": "^4.20.1"
"tsx": "^4.20.1",
"typescript": "^5.8.3"
},
"packageManager": "[email protected]"
}
10 changes: 10 additions & 0 deletions js/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading