Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 3 additions & 8 deletions moq-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,9 @@ yarn dev

3. Open the URL printed by Vite (e.g. `http://localhost:5173`).

4. Provide a **Fishjam ID** in one of two ways:
4. Provide the **Sandbox API URL** (the relay connection URL, including the Fishjam ID, is returned by the sandbox):

- Pass it as a query parameter: `http://localhost:5173?fishjamId=<your-id>`
- Leave it out — a **Fishjam ID** input field will appear in the UI so you can enter it at runtime.

5. Provide the **Sandbox API URL**:

- Pass it as a query parameter: `http://localhost:5173?fishjamId=<your-id>&sandboxApiUrl=https://fishjam.io/api/v1/connect/<your-key>/room-manager`
- Pass it as a query parameter: `http://localhost:5173?sandboxApiUrl=https://fishjam.io/api/v1/connect/<your-key>/room-manager`
- Or enter it in the UI at runtime.

6. Enter a stream name and click **Start Streaming** to publish, or **Connect to Stream** to watch.
5. Enter a stream name and click **Start Streaming** to publish, or **Connect to Stream** to watch.
33 changes: 3 additions & 30 deletions moq-demo/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { createSignal, Show } from "solid-js";
import Streamer from "./Streamer";
import Viewer from "./Viewer";
import {
fishjamId as configFishjamId,
SANDBOX_API_URL as configSandboxApiUrl,
} from "./config";
import { SANDBOX_API_URL as configSandboxApiUrl } from "./config";

export default function App() {
const [streamName, setStreamName] = createSignal("my-stream");
const [fishjamId, setFishjamId] = createSignal(configFishjamId);
const [sandboxApiUrl, setSandboxApiUrl] = createSignal(configSandboxApiUrl);

return (
Expand All @@ -33,21 +29,6 @@ export default function App() {
class="border-input bg-input/30 flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-xs outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
/>
</div>
<Show when={!configFishjamId}>
<div class="space-y-2">
<label class="text-sm font-medium leading-none" for="fishjam-id">
Fishjam ID
</label>
<input
id="fishjam-id"
type="text"
value={fishjamId()}
onInput={(e) => setFishjamId(e.currentTarget.value)}
placeholder="your-fishjam-id"
class="border-input bg-input/30 flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-xs outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
/>
</div>
</Show>
<Show when={!configSandboxApiUrl}>
<div class="space-y-2">
<label
Expand All @@ -70,16 +51,8 @@ export default function App() {
</div>
</div>
<div class="mx-auto max-w-7xl grid grid-cols-1 gap-8 lg:grid-cols-2">
<Streamer
streamName={streamName()}
fishjamId={fishjamId()}
sandboxApiUrl={sandboxApiUrl()}
/>
<Viewer
streamName={streamName()}
fishjamId={fishjamId()}
sandboxApiUrl={sandboxApiUrl()}
/>
<Streamer streamName={streamName()} sandboxApiUrl={sandboxApiUrl()} />
<Viewer streamName={streamName()} sandboxApiUrl={sandboxApiUrl()} />
</div>
</div>
);
Expand Down
9 changes: 3 additions & 6 deletions moq-demo/src/Streamer.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { createSignal, Show } from "solid-js";
import "@moq/publish/ui";
import "@moq/publish/element";
import { MOQ_BASE_URL } from "./config";

interface Props {
streamName: string;
fishjamId: string;
sandboxApiUrl: string;
}

Expand All @@ -23,9 +21,8 @@ export default function Streamer(props: Props) {
`${props.sandboxApiUrl}/moq/${encodeURIComponent(props.streamName)}/publisher`,
);
if (!res.ok) throw new Error(await res.text());
const { token } = (await res.json()) as { token: string };
const url = `${MOQ_BASE_URL}/${props.fishjamId}?jwt=${token}`;
publishEl.setAttribute("url", url);
const { connection_url } = (await res.json()) as { connection_url: string };
publishEl.setAttribute("url", connection_url);
publishEl.setAttribute("name", props.streamName);
setConnected(true);
} catch (e) {
Expand Down Expand Up @@ -89,7 +86,7 @@ export default function Streamer(props: Props) {
<button
type="button"
onClick={start}
disabled={!props.streamName || !props.fishjamId || loading()}
disabled={!props.streamName || !props.sandboxApiUrl || loading()}
class="inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 h-9 px-4 py-2 w-full"
>
{loading() ? "Connecting…" : "Start Streaming"}
Expand Down
9 changes: 3 additions & 6 deletions moq-demo/src/Viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { createSignal, Show } from "solid-js";
import "@moq/watch/ui";
import "@moq/watch/element";
import { MOQ_BASE_URL } from "./config";

interface Props {
streamName: string;
fishjamId: string;
sandboxApiUrl: string;
}

Expand All @@ -23,9 +21,8 @@ export default function Viewer(props: Props) {
`${props.sandboxApiUrl}/moq/${encodeURIComponent(props.streamName)}/subscriber`,
);
if (!res.ok) throw new Error(await res.text());
const { token } = (await res.json()) as { token: string };
const url = `${MOQ_BASE_URL}/${props.fishjamId}?jwt=${token}`;
watchEl.setAttribute("url", url);
const { connection_url } = (await res.json()) as { connection_url: string };
watchEl.setAttribute("url", connection_url);
watchEl.setAttribute("name", props.streamName);
setConnected(true);
} catch (e) {
Expand Down Expand Up @@ -85,7 +82,7 @@ export default function Viewer(props: Props) {
<button
type="button"
onClick={connect}
disabled={!props.streamName || !props.fishjamId || loading()}
disabled={!props.streamName || !props.sandboxApiUrl || loading()}
class="inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 h-9 px-4 py-2 w-full"
>
{loading() ? "Connecting…" : "Connect to Stream"}
Expand Down
8 changes: 0 additions & 8 deletions moq-demo/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
const params = new URLSearchParams(window.location.search);

export const fishjamId =
params.get("fishjamId") ?? import.meta.env.VITE_FISHJAM_ID ?? "";

export const SANDBOX_API_URL =
params.get("sandboxApiUrl") ?? import.meta.env.VITE_SANDBOX_API_URL ?? "";

export const MOQ_BASE_URL =
params.get("baseUrl") ??
import.meta.env.VITE_MOQ_BASE_URL ??
"https://moq.fishjam.work:443";
2 changes: 0 additions & 2 deletions moq-demo/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_FISHJAM_ID?: string;
readonly VITE_SANDBOX_API_URL?: string;
readonly VITE_MOQ_BASE_URL?: string;
readonly VITE_FISHJAM_API_BASE_URL?: string;
}

Expand Down
11 changes: 4 additions & 7 deletions translation-demo/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# Required: your Fishjam ID — the relay's root namespace and the connection path segment.
# MoQ tokens are scoped to paths under it (a stream `<name>` token authorizes `<fishjam-id>/<name>`).
VITE_FISHJAM_ID=ea94930e3ffd47c490f5144c43d57340

# Required: Fishjam sandbox API base URL. The app fetches a MoQ JWT from the sandbox and
# appends it to the relay connection URL as `?jwt=`. Get it at https://fishjam.io/app/sandbox.
VITE_SANDBOX_API_URL=https://fishjam.io/api/v1/connect/ea94930e3ffd47c490f5144c43d57340/room-manager
# Required: Fishjam sandbox API base URL. The app fetches a full MoQ relay connection URL from
# the sandbox (the relay host, the Fishjam ID root, and the JWT embedded as `?jwt=`) and connects
# to it directly. Get it at https://fishjam.io/app/sandbox.
VITE_SANDBOX_API_URL=https://fishjam.io/api/v1/connect/<your-key>/room-manager
6 changes: 3 additions & 3 deletions translation-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ Install dependencies:
yarn
```

Configure the MoQ relay (required):
Configure the sandbox API URL (required):

```bash
cp .env.example .env
# then set VITE_MOQ_URL and VITE_MOQ_FISHJAM_ID in .env to your MoQ relay URL
# then set VITE_SANDBOX_API_URL in .env to your Fishjam sandbox API URL
```

Start the development server:
Expand All @@ -25,4 +25,4 @@ yarn dev

## Environment Variables

- `VITE_MOQ_URL` (required) — URL of the MoQ relay to connect to. The app has no built-in default and will not connect until this is set.
- `VITE_SANDBOX_API_URL` (required) — Fishjam sandbox API URL used to fetch a MoQ relay connection URL. Get it at https://fishjam.io/app/sandbox.
2 changes: 1 addition & 1 deletion translation-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"@fishjam-cloud/react-client": "0.28.1",
"@fishjam-cloud/react-client": "0.28.2",
"@moq/publish": "0.2.15",
"@moq/signals": "0.1.9",
"@moq/watch": "0.2.17",
Expand Down
1 change: 0 additions & 1 deletion translation-demo/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export const FISHJAM_ID = import.meta.env.VITE_FISHJAM_ID;
export const SANDBOX_API_URL = import.meta.env.VITE_SANDBOX_API_URL;
7 changes: 1 addition & 6 deletions translation-demo/src/hooks/useMoqStreamViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { toast } from "sonner";

import { useMoqConnection } from "@/hooks/useMoqConnection";
import { useMoqTokens } from "@/hooks/useMoqTokens";
import { buildConnectionUrl } from "@/utils/translation";

// Watches a single published stream (and its translations). The connection goes to the Fishjam
// root; the subscriber token is scoped to this stream name, so the relay only announces that
Expand All @@ -27,11 +26,7 @@ export const useMoqStreamViewer = (

void (async () => {
try {
const url = await authorizeConnection(
buildConnectionUrl(),
streamName,
"subscriber",
);
const url = await authorizeConnection(streamName, "subscriber");
if (!cancelled) {
connect(url);
}
Expand Down
24 changes: 11 additions & 13 deletions translation-demo/src/hooks/useMoqTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,32 @@ import { SANDBOX_API_URL } from "@/config";
const tokenStreamName = (streamName: string) => streamName;

/**
* Wraps `useSandbox` to authorize a MoQ relay connection URL. Returns a function that, given
* a connection URL and the stream name, fetches a Fishjam sandbox JWT and appends it as
* `?jwt=<token>`.
* Wraps `useSandbox` to fetch a MoQ relay connection URL for a stream. The sandbox returns a
* full relay URL (including the Fishjam ID) with the JWT embedded as `?jwt=<token>`, scoped to
* the given role.
*/
export const useMoqTokens = () => {
const { getSandboxMoqPublisherToken, getSandboxMoqSubscriberToken } =
const { getSandboxMoqPublisherAccess, getSandboxMoqSubscriberAccess } =
useSandbox({
sandboxApiUrl: SANDBOX_API_URL ?? "",
});

const authorizeConnection = useCallback(
async (url: URL, streamName: string, role: "publisher" | "subscriber") => {
async (streamName: string, role: "publisher" | "subscriber") => {
if (!SANDBOX_API_URL) {
throw new Error(
"VITE_SANDBOX_API_URL is not set — required to fetch a MoQ token (see .env.example).",
"VITE_SANDBOX_API_URL is not set — required to fetch a MoQ connection URL (see .env.example).",
);
}

const token =
const { connectionUrl } =
role === "publisher"
? await getSandboxMoqPublisherToken(tokenStreamName(streamName))
: await getSandboxMoqSubscriberToken(tokenStreamName(streamName));
? await getSandboxMoqPublisherAccess(tokenStreamName(streamName))
: await getSandboxMoqSubscriberAccess(tokenStreamName(streamName));

const authorized = new URL(url);
authorized.searchParams.set("jwt", token);
return authorized;
return new URL(connectionUrl);
},
[getSandboxMoqPublisherToken, getSandboxMoqSubscriberToken],
[getSandboxMoqPublisherAccess, getSandboxMoqSubscriberAccess],
);

return { authorizeConnection };
Expand Down
7 changes: 1 addition & 6 deletions translation-demo/src/hooks/usePublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import type { MoqConnectionStatus } from "@/utils/types";
import { useSignalValue } from "@/hooks/useSignalValue";
import { useMoqTokens } from "@/hooks/useMoqTokens";
import { buildConnectionUrl } from "@/utils/translation";

// A friendly, human-shareable stream name (e.g. "brave-otter") used as the broadcast's
// last path segment. Word-only (no digits) so it stays easy to read off a screen or say aloud.
Expand Down Expand Up @@ -93,11 +92,7 @@ export const usePublisher = () => {

let connectionUrl: URL;
try {
connectionUrl = await authorizeConnection(
buildConnectionUrl(),
name,
"publisher",
);
connectionUrl = await authorizeConnection(name, "publisher");
} catch (error) {
if (!cancelled) {
stopPublishing();
Expand Down
26 changes: 0 additions & 26 deletions translation-demo/src/utils/translation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type * as Watch from "@moq/watch";

import { FISHJAM_ID } from "@/config";
import {
compareGoogleLanguageCodes,
formatGoogleLanguage,
Expand Down Expand Up @@ -119,28 +118,3 @@ export const compareTranslationOptions = (

export const getTranslationTargetId = (path: string, trackName: string) =>
`${path}#${trackName}`;

// The Fishjam MoQ relay. All connections go here.
const RELAY_URL = "https://relay.fishjam.io";

const getFishjamId = () => {
if (!FISHJAM_ID) {
throw new Error(
"VITE_FISHJAM_ID is not set — provide your Fishjam ID (see .env.example).",
);
}
return FISHJAM_ID;
};

// Every connection goes to `<relay>/<fishjam-id>`: the Fishjam ID is the root namespace, and
// streams are published and discovered relative to it. Both publisher and viewer connect here;
// what they may do is decided by the MoQ token, not the URL path. A token for stream `<name>`
// authorises the path `<fishjam-id>/<name>` — the Fishjam ID is prepended server-side, so it is
// never part of the stream name we request a token for. The publisher then announces the stream
// as the single-segment broadcast `<name>`, with its translation tracks alongside at
// `<name>/<provider>/translation/<lang>`.
export const buildConnectionUrl = () => {
const url = new URL(RELAY_URL);
url.pathname = `/${getFishjamId()}`;
return url;
};
1 change: 0 additions & 1 deletion translation-demo/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_FISHJAM_ID?: string;
readonly VITE_SANDBOX_API_URL?: string;
}

Expand Down
11 changes: 2 additions & 9 deletions translation-demo/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import react from '@vitejs/plugin-react-swc';
import path from 'path';
import { defineConfig, loadEnv } from 'vite';

export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
if (!env.VITE_MOQ_URL) {
throw new Error(
'VITE_MOQ_URL is not set — provide the MoQ relay URL in a .env file (see .env.example).',
);
}
import { defineConfig } from 'vite';

export default defineConfig(() => {
return {
plugins: [react()],
server: {
Expand Down
Loading