Skip to content

Commit fd09359

Browse files
Merge pull request #3 from viamrobotics/stream-client
Add createStreamClient hook
2 parents 9cb24bd + fab37b1 commit fd09359

File tree

6 files changed

+203
-4
lines changed

6 files changed

+203
-4
lines changed

.changeset/shiny-pens-begin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@viamrobotics/svelte-sdk': patch
3+
---
4+
5+
Add createStreamClient hook

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,52 @@ Is moving: {isMoving.current.data ?? false}
123123
<button onclick={() => moveStraight.current.mutate([100, 10])}> Move </button>
124124
```
125125

126+
### createStreamClient
127+
128+
A hook for more easily dealing with StreamClient.
129+
130+
```svelte
131+
<script lang="ts">
132+
import { createStreamClient } from '@viamrobotics/svelte-sdk';
133+
134+
interface Props {
135+
partID: string;
136+
name: string;
137+
}
138+
139+
let { partID, name }: Props = $props();
140+
141+
let element: HTMLVideoElement;
142+
143+
const client = createStreamClient(
144+
() => partID,
145+
() => name
146+
);
147+
148+
$effect(() => {
149+
element.srcObject = client.mediaStream;
150+
});
151+
152+
$effect(() => {
153+
const [firstResolution] = client.resolutions ?? [];
154+
155+
console.log(firstResolution);
156+
157+
if (firstResolution) {
158+
client.setResolution(firstResolution);
159+
}
160+
});
161+
</script>
162+
163+
<video
164+
muted
165+
autoplay
166+
controls={false}
167+
playsinline
168+
bind:this={element}
169+
></video>
170+
```
171+
126172
### useResourceNames
127173

128174
Wraps `client.resourceNames()` in a reactive query. Supports optional filtering by resource subtype.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { untrack } from 'svelte';
2+
import { streamApi, StreamClient } from '@viamrobotics/sdk';
3+
import { useRobotClient } from './robot-clients.svelte';
4+
import {
5+
createMutation,
6+
createQuery,
7+
queryOptions as createQueryOptions,
8+
} from '@tanstack/svelte-query';
9+
import { fromStore, toStore } from 'svelte/store';
10+
11+
export const createStreamClient = (
12+
partID: () => string,
13+
resourceName: () => string
14+
) => {
15+
let mediaStream = $state.raw<MediaStream | null>(null);
16+
17+
const client = useRobotClient(partID);
18+
const streamClient = $derived(
19+
client.current ? new StreamClient(client.current) : undefined
20+
);
21+
22+
const handleTrack = (event: unknown) => {
23+
const [stream] = (event as { streams: MediaStream[] }).streams;
24+
25+
if (!stream || stream.id !== resourceName()) {
26+
return;
27+
}
28+
29+
mediaStream = stream;
30+
};
31+
32+
$effect(() => {
33+
streamClient?.on('track', handleTrack);
34+
return () => streamClient?.off('track', handleTrack);
35+
});
36+
37+
$effect(() => {
38+
streamClient?.getStream(resourceName());
39+
return () => streamClient?.remove(resourceName());
40+
});
41+
42+
const queryOptions = $derived(
43+
createQueryOptions({
44+
queryKey: [
45+
'partID',
46+
partID(),
47+
'resource',
48+
resourceName(),
49+
'stream',
50+
'getOptions',
51+
],
52+
enabled: streamClient !== undefined,
53+
queryFn: async () => {
54+
return streamClient?.getOptions(resourceName());
55+
},
56+
})
57+
);
58+
const query = fromStore(createQuery(toStore(() => queryOptions)));
59+
const resolutions = $derived(query.current.data);
60+
61+
const mutationOptions = $derived({
62+
mutationKey: [
63+
'partID',
64+
partID(),
65+
'resource',
66+
resourceName(),
67+
'stream',
68+
'setOptions',
69+
],
70+
mutationFn: async (resolution?: streamApi.Resolution) => {
71+
if (resolution) {
72+
return streamClient?.setOptions(
73+
resourceName(),
74+
resolution.width,
75+
resolution.height
76+
);
77+
}
78+
79+
return streamClient?.resetOptions(resourceName());
80+
},
81+
});
82+
const mutation = fromStore(createMutation(toStore(() => mutationOptions)));
83+
84+
return {
85+
get current() {
86+
return streamClient;
87+
},
88+
get mediaStream() {
89+
return mediaStream;
90+
},
91+
get resolutions() {
92+
return resolutions;
93+
},
94+
setResolution(resolution?: streamApi.Resolution) {
95+
return untrack(() => mutation.current).mutate(resolution);
96+
},
97+
};
98+
};

src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export { createRobotMutation } from './hooks/create-robot-mutation.svelte';
1010
export { createResourceClient } from './hooks/create-resource-client.svelte';
1111
export { createResourceQuery } from './hooks/create-resource-query.svelte';
1212
export { createResourceMutation } from './hooks/create-resource-mutation.svelte';
13+
export { createStreamClient } from './hooks/create-stream-client.svelte';
1314

1415
export { useResourceNames } from './hooks/resource-names.svelte';

src/routes/components/parts.svelte

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useConnectionStatus, useResourceNames } from '$lib';
33
import { dialConfigs } from '../configs';
44
import Base from './base.svelte';
55
import Camera from './camera.svelte';
6+
import Stream from './stream.svelte';
67
import Version from './version.svelte';
78
89
const partIDs = Object.keys(dialConfigs);
@@ -13,6 +14,8 @@ const status = useConnectionStatus(() => partID);
1314
const resources = useResourceNames(() => partID);
1415
const cameras = useResourceNames(() => partID, 'camera');
1516
const bases = useResourceNames(() => partID, 'base');
17+
18+
let streaming = true;
1619
</script>
1720

1821
<section class="p-4">
@@ -53,9 +56,16 @@ const bases = useResourceNames(() => partID, 'base');
5356
{/each}
5457

5558
{#each cameras.current as { name } (name)}
56-
<Camera
57-
{name}
58-
{partID}
59-
/>
59+
{#if streaming}
60+
<Stream
61+
{name}
62+
{partID}
63+
/>
64+
{:else}
65+
<Camera
66+
{name}
67+
{partID}
68+
/>
69+
{/if}
6070
{/each}
6171
</section>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script lang="ts">
2+
import { createStreamClient } from '$lib';
3+
4+
interface Props {
5+
partID: string;
6+
name: string;
7+
}
8+
9+
let { partID, name }: Props = $props();
10+
11+
let element: HTMLVideoElement;
12+
13+
const client = createStreamClient(
14+
() => partID,
15+
() => name
16+
);
17+
18+
$effect(() => {
19+
element.srcObject = client.mediaStream;
20+
});
21+
22+
$effect(() => {
23+
const [firstResolution] = client.resolutions ?? [];
24+
25+
console.log(firstResolution);
26+
27+
if (firstResolution) {
28+
client.setResolution(firstResolution);
29+
}
30+
});
31+
</script>
32+
33+
<video
34+
muted
35+
autoplay
36+
controls={false}
37+
playsinline
38+
bind:this={element}
39+
></video>

0 commit comments

Comments
 (0)