@viamrobotics/test-widgets 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/builtin.js CHANGED
@@ -1,49 +1,58 @@
1
- import { ArmClient, BaseClient, BoardClient, ButtonClient, CameraClient, DataManagerClient, DiscoveryClient, EncoderClient, GantryClient, GenericComponentClient, GenericServiceClient, GripperClient, InputControllerClient, MLModelClient, MotionClient, MotorClient, MovementSensorClient, NavigationClient, PoseTrackerClient, PowerSensorClient, SensorClient, ServoClient, SlamClient, SwitchClient, VideoClient, VisionClient, WorldStateStoreClient, } from '@viamrobotics/sdk';
2
1
  import { ArmWidget, BaseWidget, BoardWidget, ButtonWidget, CameraWidget, DiscoveryWidget, EncoderWidget, GantryWidget, GripperWidget, InputControllerWidget, MLModelServiceWidget, MotorWidget, MovementSensorWidget, NavigationServiceWidget, PowerSensorWidget, SensorWidget, ServoWidget, SlamWidget, SwitchWidget, VisionServiceWidget, } from './';
2
+ import { clientMap } from "./client-map.js";
3
3
  import { getResourceAPI } from "./resource.js";
4
+ export { clientForBuiltinResource } from "./client-map.js";
4
5
  // The types are nicer to work with as arrays (as opposed to a record of objects) because TS will infer the types.
5
6
  // try not to expose this map so that we can easily refactor it.
6
7
  const resourceMap =
7
8
  // api: [client, testView, showResourceInControlView]
8
9
  // list created via `cat rdkbuiltins/viam-server-stable.json | rg "api" | sort | uniq`
9
10
  {
10
- 'rdk:component:arm': [ArmClient, ArmWidget, true],
11
- 'rdk:component:base': [BaseClient, BaseWidget, true],
12
- 'rdk:component:board': [BoardClient, BoardWidget, true],
13
- 'rdk:component:button': [ButtonClient, ButtonWidget, true],
14
- 'rdk:component:camera': [CameraClient, CameraWidget, true],
15
- 'rdk:component:encoder': [EncoderClient, EncoderWidget, true],
16
- 'rdk:component:gantry': [GantryClient, GantryWidget, true],
17
- 'rdk:component:generic': [GenericComponentClient, undefined, true],
18
- 'rdk:component:gripper': [GripperClient, GripperWidget, true],
19
- 'rdk:component:input_controller': [InputControllerClient, InputControllerWidget, true],
20
- 'rdk:component:motor': [MotorClient, MotorWidget, true],
21
- 'rdk:component:movement_sensor': [MovementSensorClient, MovementSensorWidget, true],
22
- 'rdk:component:pose_tracker': [PoseTrackerClient, undefined, true],
23
- 'rdk:component:power_sensor': [PowerSensorClient, PowerSensorWidget, true],
24
- 'rdk:component:sensor': [SensorClient, SensorWidget, true],
25
- 'rdk:component:servo': [ServoClient, ServoWidget, true],
26
- 'rdk:component:switch': [SwitchClient, SwitchWidget, true],
11
+ 'rdk:component:arm': [clientMap['rdk:component:arm'], ArmWidget, true],
12
+ 'rdk:component:base': [clientMap['rdk:component:base'], BaseWidget, true],
13
+ 'rdk:component:board': [clientMap['rdk:component:board'], BoardWidget, true],
14
+ 'rdk:component:button': [clientMap['rdk:component:button'], ButtonWidget, true],
15
+ 'rdk:component:camera': [clientMap['rdk:component:camera'], CameraWidget, true],
16
+ 'rdk:component:encoder': [clientMap['rdk:component:encoder'], EncoderWidget, true],
17
+ 'rdk:component:gantry': [clientMap['rdk:component:gantry'], GantryWidget, true],
18
+ 'rdk:component:generic': [clientMap['rdk:component:generic'], undefined, true],
19
+ 'rdk:component:gripper': [clientMap['rdk:component:gripper'], GripperWidget, true],
20
+ 'rdk:component:input_controller': [
21
+ clientMap['rdk:component:input_controller'],
22
+ InputControllerWidget,
23
+ true,
24
+ ],
25
+ 'rdk:component:motor': [clientMap['rdk:component:motor'], MotorWidget, true],
26
+ 'rdk:component:movement_sensor': [
27
+ clientMap['rdk:component:movement_sensor'],
28
+ MovementSensorWidget,
29
+ true,
30
+ ],
31
+ 'rdk:component:pose_tracker': [clientMap['rdk:component:pose_tracker'], undefined, true],
32
+ 'rdk:component:power_sensor': [
33
+ clientMap['rdk:component:power_sensor'],
34
+ PowerSensorWidget,
35
+ true,
36
+ ],
37
+ 'rdk:component:sensor': [clientMap['rdk:component:sensor'], SensorWidget, true],
38
+ 'rdk:component:servo': [clientMap['rdk:component:servo'], ServoWidget, true],
39
+ 'rdk:component:switch': [clientMap['rdk:component:switch'], SwitchWidget, true],
27
40
  'rdk:service:base_remote_control': [undefined, undefined, true],
28
41
  // dont show -- confusing to users
29
- 'rdk:service:data_manager': [DataManagerClient, undefined, false],
30
- 'rdk:service:discovery': [DiscoveryClient, DiscoveryWidget, true],
31
- 'rdk:service:generic': [GenericServiceClient, undefined, true],
32
- 'rdk:service:mlmodel': [MLModelClient, MLModelServiceWidget, true],
42
+ 'rdk:service:data_manager': [clientMap['rdk:service:data_manager'], undefined, false],
43
+ 'rdk:service:discovery': [clientMap['rdk:service:discovery'], DiscoveryWidget, true],
44
+ 'rdk:service:generic': [clientMap['rdk:service:generic'], undefined, true],
45
+ 'rdk:service:mlmodel': [clientMap['rdk:service:mlmodel'], MLModelServiceWidget, true],
33
46
  // dont show -- confusing to users
34
- 'rdk:service:motion': [MotionClient, undefined, false],
35
- 'rdk:service:navigation': [NavigationClient, NavigationServiceWidget, true],
47
+ 'rdk:service:motion': [clientMap['rdk:service:motion'], undefined, false],
48
+ 'rdk:service:navigation': [clientMap['rdk:service:navigation'], NavigationServiceWidget, true],
36
49
  // dont show -- confusing to users
37
50
  'rdk:service:sensors': [undefined, undefined, false],
38
51
  'rdk:service:shell': [undefined, undefined, false],
39
- 'rdk:service:slam': [SlamClient, SlamWidget, true],
40
- 'rdk:service:vision': [VisionClient, VisionServiceWidget, true],
41
- 'rdk:service:world_state_store': [WorldStateStoreClient, undefined, true],
42
- 'rdk:service:video': [VideoClient, undefined, true],
43
- };
44
- export const clientForBuiltinResource = (resource) => {
45
- const resAPI = getResourceAPI(resource);
46
- return resAPI in resourceMap ? resourceMap[resAPI][0] : undefined;
52
+ 'rdk:service:slam': [clientMap['rdk:service:slam'], SlamWidget, true],
53
+ 'rdk:service:vision': [clientMap['rdk:service:vision'], VisionServiceWidget, true],
54
+ 'rdk:service:world_state_store': [clientMap['rdk:service:world_state_store'], undefined, true],
55
+ 'rdk:service:video': [clientMap['rdk:service:video'], undefined, true],
47
56
  };
48
57
  export const viewForBuiltinResource = (resource) => {
49
58
  const resAPI = getResourceAPI(resource);
@@ -0,0 +1,31 @@
1
+ import { ArmClient, BaseClient, BoardClient, ButtonClient, CameraClient, DataManagerClient, DiscoveryClient, EncoderClient, GantryClient, GenericComponentClient, GenericServiceClient, GripperClient, InputControllerClient, MLModelClient, MotionClient, MotorClient, MovementSensorClient, NavigationClient, PoseTrackerClient, PowerSensorClient, type ResourceName, SensorClient, ServoClient, SlamClient, SwitchClient, VideoClient, VisionClient, WorldStateStoreClient } from '@viamrobotics/sdk';
2
+ export declare const clientMap: {
3
+ readonly 'rdk:component:arm': typeof ArmClient;
4
+ readonly 'rdk:component:base': typeof BaseClient;
5
+ readonly 'rdk:component:board': typeof BoardClient;
6
+ readonly 'rdk:component:button': typeof ButtonClient;
7
+ readonly 'rdk:component:camera': typeof CameraClient;
8
+ readonly 'rdk:component:encoder': typeof EncoderClient;
9
+ readonly 'rdk:component:gantry': typeof GantryClient;
10
+ readonly 'rdk:component:generic': typeof GenericComponentClient;
11
+ readonly 'rdk:component:gripper': typeof GripperClient;
12
+ readonly 'rdk:component:input_controller': typeof InputControllerClient;
13
+ readonly 'rdk:component:motor': typeof MotorClient;
14
+ readonly 'rdk:component:movement_sensor': typeof MovementSensorClient;
15
+ readonly 'rdk:component:pose_tracker': typeof PoseTrackerClient;
16
+ readonly 'rdk:component:power_sensor': typeof PowerSensorClient;
17
+ readonly 'rdk:component:sensor': typeof SensorClient;
18
+ readonly 'rdk:component:servo': typeof ServoClient;
19
+ readonly 'rdk:component:switch': typeof SwitchClient;
20
+ readonly 'rdk:service:data_manager': typeof DataManagerClient;
21
+ readonly 'rdk:service:discovery': typeof DiscoveryClient;
22
+ readonly 'rdk:service:generic': typeof GenericServiceClient;
23
+ readonly 'rdk:service:mlmodel': typeof MLModelClient;
24
+ readonly 'rdk:service:motion': typeof MotionClient;
25
+ readonly 'rdk:service:navigation': typeof NavigationClient;
26
+ readonly 'rdk:service:slam': typeof SlamClient;
27
+ readonly 'rdk:service:vision': typeof VisionClient;
28
+ readonly 'rdk:service:world_state_store': typeof WorldStateStoreClient;
29
+ readonly 'rdk:service:video': typeof VideoClient;
30
+ };
31
+ export declare const clientForBuiltinResource: (resource: ResourceName) => typeof ArmClient | typeof BaseClient | typeof GantryClient | typeof GripperClient | typeof MotorClient | typeof ServoClient | typeof BoardClient | typeof ButtonClient | typeof CameraClient | typeof DiscoveryClient | typeof EncoderClient | typeof GenericComponentClient | typeof InputControllerClient | typeof MovementSensorClient | typeof PoseTrackerClient | typeof PowerSensorClient | typeof SensorClient | typeof SwitchClient | typeof DataManagerClient | typeof GenericServiceClient | typeof MLModelClient | typeof MotionClient | typeof NavigationClient | typeof SlamClient | typeof VisionClient | typeof WorldStateStoreClient | typeof VideoClient | undefined;
@@ -0,0 +1,35 @@
1
+ import { ArmClient, BaseClient, BoardClient, ButtonClient, CameraClient, DataManagerClient, DiscoveryClient, EncoderClient, GantryClient, GenericComponentClient, GenericServiceClient, GripperClient, InputControllerClient, MLModelClient, MotionClient, MotorClient, MovementSensorClient, NavigationClient, PoseTrackerClient, PowerSensorClient, SensorClient, ServoClient, SlamClient, SwitchClient, VideoClient, VisionClient, WorldStateStoreClient, } from '@viamrobotics/sdk';
2
+ import { getResourceAPI } from "./resource.js";
3
+ export const clientMap = {
4
+ 'rdk:component:arm': ArmClient,
5
+ 'rdk:component:base': BaseClient,
6
+ 'rdk:component:board': BoardClient,
7
+ 'rdk:component:button': ButtonClient,
8
+ 'rdk:component:camera': CameraClient,
9
+ 'rdk:component:encoder': EncoderClient,
10
+ 'rdk:component:gantry': GantryClient,
11
+ 'rdk:component:generic': GenericComponentClient,
12
+ 'rdk:component:gripper': GripperClient,
13
+ 'rdk:component:input_controller': InputControllerClient,
14
+ 'rdk:component:motor': MotorClient,
15
+ 'rdk:component:movement_sensor': MovementSensorClient,
16
+ 'rdk:component:pose_tracker': PoseTrackerClient,
17
+ 'rdk:component:power_sensor': PowerSensorClient,
18
+ 'rdk:component:sensor': SensorClient,
19
+ 'rdk:component:servo': ServoClient,
20
+ 'rdk:component:switch': SwitchClient,
21
+ 'rdk:service:data_manager': DataManagerClient,
22
+ 'rdk:service:discovery': DiscoveryClient,
23
+ 'rdk:service:generic': GenericServiceClient,
24
+ 'rdk:service:mlmodel': MLModelClient,
25
+ 'rdk:service:motion': MotionClient,
26
+ 'rdk:service:navigation': NavigationClient,
27
+ 'rdk:service:slam': SlamClient,
28
+ 'rdk:service:vision': VisionClient,
29
+ 'rdk:service:world_state_store': WorldStateStoreClient,
30
+ 'rdk:service:video': VideoClient,
31
+ };
32
+ export const clientForBuiltinResource = (resource) => {
33
+ const resAPI = getResourceAPI(resource);
34
+ return resAPI in clientMap ? clientMap[resAPI] : undefined;
35
+ };
@@ -42,7 +42,8 @@
42
42
  </tr>
43
43
  </thead>
44
44
  <tbody>
45
- {#each displayPositions as pos, index (pos)}
45
+ <!-- joint ids are just the index in the list of values, we need to use pos and index -->
46
+ {#each displayPositions as pos, index ([pos, index])}
46
47
  <tr>
47
48
  <th> {index} </th>
48
49
  <th> {formatNumeric(pos)} </th>
@@ -29,7 +29,6 @@
29
29
  () => resourceName,
30
30
  'camera'
31
31
  )
32
- const isShowingPip = $state(false)
33
32
  let isShowingPointcloud = $state(false)
34
33
 
35
34
  const { addImageToDataset } = useAddImageToDataset()
@@ -46,7 +45,6 @@
46
45
  const imageQuery = createResourceQuery(client, 'getImages', () => ({
47
46
  enabled: refetchInterval.current !== RefetchIntervals.LIVE,
48
47
  refetchInterval: refetchInterval.current,
49
- refetchIntervalInBackground: isShowingPip,
50
48
  }))
51
49
 
52
50
  const pointcloudQuery = createResourceQuery(client, 'getPointCloud', () => ({
@@ -70,11 +68,6 @@
70
68
  // to be requested separately. Don't show a "toggle pip" button in Firefox.
71
69
  const isFirefox = navigator.userAgent.toLowerCase().includes('firefox')
72
70
 
73
- let mediaStream = $state<MediaStream | null>(null)
74
- const setMediaStream = (next: MediaStream | null) => {
75
- mediaStream = next
76
- }
77
-
78
71
  let mousePostionTooltip = $state<'On' | 'Off'>('Off')
79
72
  const setMousePostionTooltip = (event: CustomEvent<string>) => {
80
73
  mousePostionTooltip = event.detail as 'On' | 'Off'
@@ -103,7 +96,6 @@
103
96
  error={imageQuery.error}
104
97
  isLoading={imageQuery.isLoading}
105
98
  refetch={imageQuery.refetch}
106
- {setMediaStream}
107
99
  showMousePositionTooltip={mousePostionTooltip === 'On'}
108
100
  />
109
101
  </div>
@@ -146,9 +138,8 @@
146
138
  {/if}
147
139
  {#if !isFirefox}
148
140
  <PictureInPictureButton
149
- {mediaStream}
150
141
  {resourceName}
151
- {isShowingPip}
142
+ rate={refetchInterval.current}
152
143
  />
153
144
  {/if}
154
145
  <Label>
@@ -25,7 +25,6 @@
25
25
  isLoading: boolean
26
26
  videoClass?: string
27
27
  showMousePositionTooltip?: boolean
28
- setMediaStream?: ((mediaStream: MediaStream | null) => void) | undefined
29
28
  refetch: () => Promise<unknown>
30
29
  }
31
30
 
@@ -39,7 +38,6 @@
39
38
  isLoading,
40
39
  videoClass = '',
41
40
  showMousePositionTooltip = false,
42
- setMediaStream: setPipMediaStream,
43
41
  refetch,
44
42
  }: Props = $props()
45
43
 
@@ -72,7 +70,6 @@
72
70
  }
73
71
  videoElement.srcObject = mediaStream
74
72
  }
75
- setPipMediaStream?.(mediaStream)
76
73
  }
77
74
 
78
75
  // For live streams, set the media stream when there is a new video track.
@@ -185,13 +182,12 @@
185
182
  })
186
183
 
187
184
  $effect(() => {
188
- return () => {
185
+ return () =>
189
186
  untrack(() => {
190
187
  disableStream().then(() => {
191
188
  setMediaStream(null)
192
189
  })
193
190
  })
194
- }
195
191
  })
196
192
 
197
193
  $effect(() => {
@@ -10,7 +10,6 @@ interface Props {
10
10
  isLoading: boolean;
11
11
  videoClass?: string;
12
12
  showMousePositionTooltip?: boolean;
13
- setMediaStream?: ((mediaStream: MediaStream | null) => void) | undefined;
14
13
  refetch: () => Promise<unknown>;
15
14
  }
16
15
  declare const LiveOrPollingVideo: import("svelte").Component<Props, {}, "">;
@@ -1,80 +1,35 @@
1
1
  <script lang="ts">
2
2
  import { Button } from '@viamrobotics/prime-core'
3
- import { onMount } from 'svelte'
4
3
 
5
4
  import ErrorDisplay from '../../error.svelte'
6
5
 
6
+ import { usePip } from '../../../pip/context.svelte'
7
+
7
8
  interface Props {
8
9
  resourceName: string
9
- mediaStream: MediaStream | null
10
- isShowingPip: boolean
11
- }
12
-
13
- let { resourceName, mediaStream, isShowingPip = $bindable(false) }: Props = $props()
14
-
15
- let videoElement = $state.raw<HTMLVideoElement>()
16
- let lastErr = $state.raw<Error>()
17
-
18
- const onEnter = () => {
19
- isShowingPip = true
20
- }
21
-
22
- const onLeave = () => {
23
- isShowingPip = false
10
+ rate: number | 'live' | false
24
11
  }
25
12
 
26
- onMount(() => {
27
- videoElement?.addEventListener('enterpictureinpicture', onEnter)
28
- videoElement?.addEventListener('leavepictureinpicture', onLeave)
13
+ const { resourceName, rate }: Props = $props()
29
14
 
30
- return () => {
31
- videoElement?.removeEventListener('enterpictureinpicture', onEnter)
32
- videoElement?.removeEventListener('leavepictureinpicture', onLeave)
33
- if (isShowingPip) {
34
- document.exitPictureInPicture()
35
- }
36
- }
37
- })
15
+ const pip = usePip()
38
16
 
39
- $effect.pre(() => {
40
- if (videoElement) {
41
- videoElement.srcObject = mediaStream
17
+ $effect(() => {
18
+ if (resourceName === pip.resourceName) {
19
+ pip.setRate(rate)
42
20
  }
43
21
  })
44
-
45
- const togglePictureInPicture = async () => {
46
- lastErr = undefined
47
-
48
- try {
49
- await (isShowingPip
50
- ? document.exitPictureInPicture()
51
- : videoElement?.requestPictureInPicture())
52
- } catch (error) {
53
- lastErr = error as Error
54
- }
55
- }
56
22
  </script>
57
23
 
58
24
  <Button
59
25
  icon="picture-in-picture-top-right"
60
- onclick={togglePictureInPicture}
26
+ onclick={() => pip.toggle(resourceName)}
27
+ progress={pip.readyState === 'loading' ? 'indeterminate' : undefined}
61
28
  >
62
29
  Toggle picture-in-picture
63
30
  </Button>
64
31
 
65
- <!-- Display a fixed video element on the page but essentially hide it.
66
- There must always be a video element visible for PiP. -->
67
- <video
68
- class="fixed right-0 bottom-0 h-px w-px opacity-[0.01]"
69
- bind:this={videoElement}
70
- muted
71
- autoplay
72
- controls={false}
73
- playsinline
74
- aria-label={`${resourceName} picture-in-picture stream`}
75
- ></video>
76
-
77
32
  <ErrorDisplay
78
33
  class="max-w-50"
79
- lastError={lastErr}
34
+ lastError={pip.error}
80
35
  />
@@ -1,8 +1,7 @@
1
1
  interface Props {
2
2
  resourceName: string;
3
- mediaStream: MediaStream | null;
4
- isShowingPip: boolean;
3
+ rate: number | 'live' | false;
5
4
  }
6
- declare const PictureInPictureButton: import("svelte").Component<Props, {}, "isShowingPip">;
5
+ declare const PictureInPictureButton: import("svelte").Component<Props, {}, "">;
7
6
  type PictureInPictureButton = ReturnType<typeof PictureInPictureButton>;
8
7
  export default PictureInPictureButton;
@@ -5,7 +5,7 @@
5
5
  import { createResourceClient, createResourceMutation } from '@viamrobotics/svelte-sdk'
6
6
  import { PersistedState } from 'runed'
7
7
 
8
- import { clientForBuiltinResource } from '../../../builtin'
8
+ import { clientForBuiltinResource } from '../../../client-map'
9
9
  import { getResourceAPI, getResourceKey } from '../../../resource'
10
10
 
11
11
  import ErrorDisplay from '../../error.svelte'
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { createAddImageToDatasetContext, type ImageData, useAddImageToDataset, } from './add-image-to-dataset';
2
2
  export * from './components';
3
+ export { providePip, usePip } from './pip/context.svelte';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { createAddImageToDatasetContext, useAddImageToDataset, } from './add-image-to-dataset';
2
2
  export * from './components';
3
+ export { providePip, usePip } from './pip/context.svelte';
@@ -0,0 +1,13 @@
1
+ interface PipContext {
2
+ /** Called by PictureInPictureButton to start or stop PiP. */
3
+ toggle(resourceName: string): Promise<void>;
4
+ setRate(rate: 'live' | number | false): void;
5
+ error: Error | undefined;
6
+ /** Whether the PictureInPicture is active */
7
+ readyState: 'inactive' | 'loading' | 'active';
8
+ /** The current resource streamed */
9
+ resourceName: string | undefined;
10
+ }
11
+ export declare function providePip(partID: () => string): PipContext;
12
+ export declare function usePip(): PipContext;
13
+ export {};
@@ -0,0 +1,125 @@
1
+ import { CameraClient } from '@viamrobotics/sdk';
2
+ import { createResourceClient, createResourceQuery, createStreamClient, } from '@viamrobotics/svelte-sdk';
3
+ import { getContext, setContext } from 'svelte';
4
+ const PIP_KEY = Symbol('pip-context');
5
+ export function providePip(partID) {
6
+ const video = document.createElement('video');
7
+ video.muted = true;
8
+ video.autoplay = true;
9
+ video.controls = false;
10
+ video.playsInline = true;
11
+ video.ariaHidden = 'true';
12
+ video.style = 'position:fixed; right: 0; bottom: 0; height: 1px; width: 1px; opacity: 0.01;';
13
+ const canvas = document.createElement('canvas');
14
+ const canvasCtx = canvas.getContext('2d');
15
+ const canvasStream = canvas.captureStream();
16
+ const img = document.createElement('img');
17
+ let error = $state();
18
+ let readyState = $state('inactive');
19
+ let rate = $state('live');
20
+ let activeName = $state();
21
+ const cameraClient = $derived(activeName ? createResourceClient(CameraClient, partID, () => activeName ?? '') : undefined);
22
+ const streamClient = $derived(activeName ? createStreamClient(partID, () => activeName ?? '') : undefined);
23
+ const imageQuery = $derived(cameraClient?.current
24
+ ? createResourceQuery(cameraClient, 'getImages', () => ({
25
+ enabled: rate !== 'live',
26
+ refetchInterval: typeof rate === 'number' ? rate : false,
27
+ refetchIntervalInBackground: activeName === cameraClient?.current?.name,
28
+ }))
29
+ : undefined);
30
+ const mediaStream = $derived((rate === 'live' ? streamClient?.mediaStream : canvasStream) ?? null);
31
+ $effect(() => {
32
+ document.body.append(video);
33
+ return () => {
34
+ video.remove();
35
+ };
36
+ });
37
+ $effect(() => {
38
+ video.srcObject = mediaStream;
39
+ return () => {
40
+ video.srcObject = null;
41
+ };
42
+ });
43
+ $effect(() => {
44
+ const currentRate = rate;
45
+ if (currentRate === 'live')
46
+ return;
47
+ const image = imageQuery?.data?.images[0];
48
+ if (!image)
49
+ return;
50
+ if (img.src)
51
+ URL.revokeObjectURL(img.src);
52
+ img.src = URL.createObjectURL(new Blob([new Uint8Array(image.image)], {
53
+ type: image.mimeType || 'image/jpeg',
54
+ }));
55
+ const drawImage = () => {
56
+ canvas.width = img.naturalWidth;
57
+ canvas.height = img.naturalHeight;
58
+ canvasCtx?.drawImage(img, 0, 0);
59
+ };
60
+ img.addEventListener('load', drawImage);
61
+ return () => {
62
+ if (img.src)
63
+ URL.revokeObjectURL(img.src);
64
+ img.removeEventListener('load', drawImage);
65
+ };
66
+ });
67
+ const toggle = async () => {
68
+ if (activeName === undefined &&
69
+ readyState === 'active' &&
70
+ document.pictureInPictureElement === video) {
71
+ try {
72
+ await document.exitPictureInPicture();
73
+ readyState = 'inactive';
74
+ error = undefined;
75
+ }
76
+ catch (nextError) {
77
+ error = nextError;
78
+ }
79
+ return;
80
+ }
81
+ readyState = 'loading';
82
+ // Wait for the new stream's metadata to load before requesting PiP,
83
+ // otherwise the browser throws InvalidStateError.
84
+ if (video.readyState < 1 /* HAVE_METADATA */) {
85
+ await new Promise((resolve) => {
86
+ video.addEventListener('loadedmetadata', () => resolve(), { once: true });
87
+ });
88
+ }
89
+ try {
90
+ await video.requestPictureInPicture();
91
+ readyState = 'active';
92
+ error = undefined;
93
+ }
94
+ catch (nextError) {
95
+ error = nextError;
96
+ }
97
+ };
98
+ const context = {
99
+ async toggle(resourceName) {
100
+ activeName = activeName === resourceName ? undefined : resourceName;
101
+ await toggle();
102
+ },
103
+ setRate(newRate) {
104
+ rate = newRate;
105
+ },
106
+ get error() {
107
+ return error;
108
+ },
109
+ get readyState() {
110
+ return readyState;
111
+ },
112
+ get resourceName() {
113
+ return activeName;
114
+ },
115
+ };
116
+ setContext(PIP_KEY, context);
117
+ return context;
118
+ }
119
+ export function usePip() {
120
+ const ctx = getContext(PIP_KEY);
121
+ if (!ctx) {
122
+ throw new Error('usePipContext must be called after providePip()');
123
+ }
124
+ return ctx;
125
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/test-widgets",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "files": [