@camera.ui/browser 0.0.173 → 0.0.174

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/index.d.ts CHANGED
@@ -290,8 +290,8 @@ export declare interface CoreManagerInterface {
290
290
  getFFmpegPath(): Promise<string>;
291
291
  getServerAddresses(): Promise<string[]>;
292
292
  getCloudServerId(): Promise<string>;
293
- getPlugin(pluginName: string): Promise<PluginInfo | undefined>;
294
- getPluginsByInterface(interfaceName: PluginInterface): Promise<PluginInfo[]>;
293
+ getPlugin(pluginName: string): Promise<HostPluginInfo | undefined>;
294
+ getPluginsByInterface(interfaceName: PluginInterface): Promise<HostPluginInfo[]>;
295
295
  }
296
296
 
297
297
  export declare interface CoreManagerNamespaces {
@@ -381,6 +381,10 @@ export declare function getSnapshotUrl(cameraId: string): string | undefined;
381
381
 
382
382
  declare type HiddenCallback = () => void;
383
383
 
384
+ declare interface HostPluginInfo extends PluginInfo {
385
+ running: boolean;
386
+ }
387
+
384
388
  /* Excluded from this release type: HumidityInfoProperties */
385
389
 
386
390
  /* Excluded from this release type: HumidityProperty */
package/dist/index.js CHANGED
@@ -459,7 +459,8 @@ function clearSnapshotCache() {
459
459
  timestampCache.clear();
460
460
  }
461
461
  function useSnapshot(cameraIdOrName) {
462
- const { isConnected } = useCameraUi();
462
+ const cameraUi = useCameraUi();
463
+ const { isConnected } = cameraUi;
463
464
  const deviceManager = useDeviceManager();
464
465
  const snapshot = shallowRef();
465
466
  const _isLoading = ref(false);
@@ -510,9 +511,9 @@ function useSnapshot(cameraIdOrName) {
510
511
  heldDeviceId = void 0;
511
512
  }
512
513
  }
513
- async function loadSnapshot(id) {
514
+ async function loadSnapshot(id, skipCache = false) {
514
515
  if (!isConnected.value || !id) return;
515
- const cached = snapshotCache.get(id);
516
+ const cached = skipCache ? void 0 : snapshotCache.get(id);
516
517
  if (cached) {
517
518
  snapshot.value = cached;
518
519
  initialLoadDone.value = true;
@@ -585,7 +586,15 @@ function useSnapshot(cameraIdOrName) {
585
586
  snapshot.value = void 0;
586
587
  }
587
588
  }, { immediate: true });
589
+ const onReconnected = () => {
590
+ const idOrName = toValue(cameraIdOrName);
591
+ if (isCameraDisabled(idOrName)) return;
592
+ const id = typeof idOrName === "string" ? idOrName : idOrName._id;
593
+ if (id) loadSnapshot(id, true);
594
+ };
595
+ cameraUi.on("reconnected", onReconnected);
588
596
  tryOnScopeDispose(() => {
597
+ cameraUi.off("reconnected", onReconnected);
589
598
  unsubscribe?.();
590
599
  releaseHeldDevice();
591
600
  });
@@ -942,9 +951,16 @@ function useCameraById(cameraIdOrName) {
942
951
  }
943
952
  //#endregion
944
953
  //#region src/composables/usePlugin.ts
954
+ var RETRY_BASE_MS = 2e3;
955
+ var RETRY_MAX_MS = 3e4;
945
956
  var pluginCache = createDebouncedCache({ releaseDelay: 1e3 });
946
957
  var pendingLoads = /* @__PURE__ */ new Map();
947
958
  var instances = /* @__PURE__ */ new Set();
959
+ var reloaders = /* @__PURE__ */ new Map();
960
+ var retryTimers = /* @__PURE__ */ new Map();
961
+ var retryAttempts = /* @__PURE__ */ new Map();
962
+ var statusClient;
963
+ var statusUnsubscribe;
948
964
  function clearPluginCache() {
949
965
  pluginCache.clear();
950
966
  pendingLoads.clear();
@@ -952,6 +968,44 @@ function clearPluginCache() {
952
968
  reset();
953
969
  } catch {}
954
970
  }
971
+ function notifyReloaders(name) {
972
+ for (const reload of [...reloaders.get(name) ?? []]) reload();
973
+ }
974
+ function stopRetry(name) {
975
+ const timer = retryTimers.get(name);
976
+ if (timer) clearTimeout(timer);
977
+ retryTimers.delete(name);
978
+ retryAttempts.delete(name);
979
+ }
980
+ function scheduleRetry(name) {
981
+ if (retryTimers.has(name)) return;
982
+ const attempt = retryAttempts.get(name) ?? 0;
983
+ retryAttempts.set(name, attempt + 1);
984
+ const timer = setTimeout(() => {
985
+ retryTimers.delete(name);
986
+ notifyReloaders(name);
987
+ }, Math.min(RETRY_BASE_MS * 2 ** attempt, RETRY_MAX_MS));
988
+ retryTimers.set(name, timer);
989
+ }
990
+ function handleStatusMessage(message) {
991
+ if (message?.type !== "pluginStatusChanged") return;
992
+ const { pluginName, running } = message.data;
993
+ if (!running) pluginCache.forceRelease(pluginName);
994
+ stopRetry(pluginName);
995
+ notifyReloaders(pluginName);
996
+ }
997
+ async function ensureStatusSubscription(rpc) {
998
+ const client = rpc.value;
999
+ if (!client || client === statusClient) return;
1000
+ statusUnsubscribe?.();
1001
+ statusUnsubscribe = void 0;
1002
+ statusClient = client;
1003
+ try {
1004
+ statusUnsubscribe = await client.subscribe(NamespaceManager.coreManagerNamespaces().coreManagerSubject, handleStatusMessage);
1005
+ } catch {
1006
+ statusClient = void 0;
1007
+ }
1008
+ }
955
1009
  function usePlugin(pluginName) {
956
1010
  const { rpc, isConnected } = useCameraUi();
957
1011
  const plugin = shallowRef();
@@ -960,6 +1014,7 @@ function usePlugin(pluginName) {
960
1014
  const initialLoadDone = ref(false);
961
1015
  const error = ref();
962
1016
  let currentPluginName;
1017
+ let watchedName;
963
1018
  function acquirePlugin(name) {
964
1019
  if (pluginCache.has(name)) return pluginCache.acquire(name, () => {
965
1020
  throw new Error("Should not create - already cached");
@@ -968,48 +1023,95 @@ function usePlugin(pluginName) {
968
1023
  function releasePlugin(name) {
969
1024
  pluginCache.release(name);
970
1025
  }
971
- async function loadPlugin(name) {
1026
+ function watchPlugin(name) {
1027
+ if (watchedName === name) return;
1028
+ if (watchedName) unwatchPlugin(watchedName);
1029
+ const set = reloaders.get(name) ?? /* @__PURE__ */ new Set();
1030
+ set.add(reload);
1031
+ reloaders.set(name, set);
1032
+ watchedName = name;
1033
+ }
1034
+ function unwatchPlugin(name) {
1035
+ const set = reloaders.get(name);
1036
+ if (set) {
1037
+ set.delete(reload);
1038
+ if (set.size === 0) {
1039
+ reloaders.delete(name);
1040
+ stopRetry(name);
1041
+ }
1042
+ }
1043
+ if (watchedName === name) watchedName = void 0;
1044
+ }
1045
+ function reload() {
1046
+ const name = toValue(pluginName);
1047
+ if (name) loadPlugin(name, true);
1048
+ }
1049
+ function markUnavailable(name) {
1050
+ if (currentPluginName) {
1051
+ releasePlugin(currentPluginName);
1052
+ currentPluginName = void 0;
1053
+ }
1054
+ plugin.value = void 0;
1055
+ contract.value = void 0;
1056
+ scheduleRetry(name);
1057
+ }
1058
+ function adopt(name, cached) {
1059
+ currentPluginName = name;
1060
+ plugin.value = cached.proxy;
1061
+ contract.value = cached.contract;
1062
+ error.value = void 0;
1063
+ stopRetry(name);
1064
+ }
1065
+ async function loadPlugin(name, silent = false) {
972
1066
  if (!isConnected.value || !name) return;
973
1067
  if (currentPluginName && currentPluginName !== name) {
1068
+ unwatchPlugin(currentPluginName);
974
1069
  releasePlugin(currentPluginName);
975
1070
  plugin.value = void 0;
976
1071
  contract.value = void 0;
977
1072
  currentPluginName = void 0;
978
1073
  }
1074
+ watchPlugin(name);
1075
+ ensureStatusSubscription(rpc);
1076
+ if (currentPluginName === name && !pluginCache.has(name)) {
1077
+ currentPluginName = void 0;
1078
+ plugin.value = void 0;
1079
+ contract.value = void 0;
1080
+ } else if (currentPluginName === name && plugin.value) return;
979
1081
  const cached = acquirePlugin(name);
980
1082
  if (cached) {
981
- currentPluginName = name;
982
- plugin.value = cached.proxy;
983
- contract.value = cached.contract;
1083
+ adopt(name, cached);
984
1084
  initialLoadDone.value = true;
985
1085
  return;
986
1086
  }
987
1087
  const pending = pendingLoads.get(name);
988
1088
  if (pending) {
989
- _isLoading.value = true;
1089
+ if (!silent) _isLoading.value = true;
990
1090
  try {
991
- if (await pending && toValue(pluginName) === name) {
1091
+ const result = await pending;
1092
+ if (toValue(pluginName) !== name) return;
1093
+ if (result) {
992
1094
  const cachedAfterPending = acquirePlugin(name);
993
- if (cachedAfterPending) {
994
- currentPluginName = name;
995
- plugin.value = cachedAfterPending.proxy;
996
- contract.value = cachedAfterPending.contract;
997
- }
998
- }
1095
+ if (cachedAfterPending) adopt(name, cachedAfterPending);
1096
+ } else markUnavailable(name);
999
1097
  } catch (err) {
1000
- error.value = err instanceof Error ? err : new Error(String(err));
1098
+ if (!silent) error.value = err instanceof Error ? err : new Error(String(err));
1099
+ markUnavailable(name);
1001
1100
  } finally {
1002
- _isLoading.value = false;
1101
+ if (!silent) _isLoading.value = false;
1003
1102
  initialLoadDone.value = true;
1004
1103
  }
1005
1104
  return;
1006
1105
  }
1007
- _isLoading.value = true;
1008
- error.value = void 0;
1106
+ if (!silent) {
1107
+ _isLoading.value = true;
1108
+ error.value = void 0;
1109
+ }
1009
1110
  const loadPromise = rpcCall(rpc, async (client) => {
1010
1111
  const coreNamespaces = NamespaceManager.coreManagerNamespaces();
1011
1112
  const pluginInfo = await client.createProxy(coreNamespaces.coreManagerRpc).getPlugin(name);
1012
1113
  if (!pluginInfo) throw new Error(`Plugin "${name}" not found`);
1114
+ if (pluginInfo.running === false) return;
1013
1115
  const pluginNamespaces = NamespaceManager.pluginNamespaces(pluginInfo.id);
1014
1116
  return {
1015
1117
  proxy: client.createProxy(pluginNamespaces.pluginChildRpc),
@@ -1025,17 +1127,14 @@ function usePlugin(pluginName) {
1025
1127
  pluginCache.release(name);
1026
1128
  return;
1027
1129
  }
1028
- currentPluginName = name;
1029
- plugin.value = result.proxy;
1030
- contract.value = result.contract;
1031
- }
1130
+ adopt(name, result);
1131
+ } else if (toValue(pluginName) === name) markUnavailable(name);
1032
1132
  } catch (err) {
1033
- error.value = err instanceof Error ? err : new Error(String(err));
1034
- plugin.value = void 0;
1035
- contract.value = void 0;
1133
+ if (!silent) error.value = err instanceof Error ? err : new Error(String(err));
1134
+ markUnavailable(name);
1036
1135
  } finally {
1037
1136
  pendingLoads.delete(name);
1038
- _isLoading.value = false;
1137
+ if (!silent) _isLoading.value = false;
1039
1138
  initialLoadDone.value = true;
1040
1139
  }
1041
1140
  }
@@ -1072,6 +1171,7 @@ function usePlugin(pluginName) {
1072
1171
  }
1073
1172
  if (connected && name && currentClient) await loadPlugin(name);
1074
1173
  else if ((!connected || !currentClient) && currentPluginName) {
1174
+ unwatchPlugin(currentPluginName);
1075
1175
  releasePlugin(currentPluginName);
1076
1176
  plugin.value = void 0;
1077
1177
  contract.value = void 0;
@@ -1080,7 +1180,9 @@ function usePlugin(pluginName) {
1080
1180
  }, { immediate: true });
1081
1181
  tryOnScopeDispose(() => {
1082
1182
  instances.delete(resetInstance);
1183
+ unwatchPlugin(toValue(pluginName));
1083
1184
  if (currentPluginName) {
1185
+ unwatchPlugin(currentPluginName);
1084
1186
  releasePlugin(currentPluginName);
1085
1187
  plugin.value = void 0;
1086
1188
  contract.value = void 0;
@@ -2989,17 +3091,18 @@ var StreamConnection = class {
2989
3091
  }
2990
3092
  }, STREAM_CONFIG.WEBRTC.WS_CONNECT_TIMEOUT, { immediate: false });
2991
3093
  const connectTimeout = useTimeoutFn(() => {
2992
- if (this.webrtcHandler && !this.webrtcHandler.isConnected) if (this.requestedMode.value === "auto") {
2993
- this.webrtcHandler.close();
2994
- this.webrtcHandler = void 0;
2995
- this.activeMode.value = "mse";
2996
- if (this.mseHandler?.isReady) this.status.value = "connected";
2997
- else this.startMSE();
2998
- } else {
2999
- this.markSourceUndecodable();
3000
- this.restart();
3001
- }
3002
- else if (this.status.value === "connected" && !this.isPlaying.value && !this.abortController.signal.aborted && this.sourceLikelyH265()) this.handleNoFramesWhileConnected();
3094
+ if (this.webrtcHandler && !this.webrtcHandler.isConnected) {
3095
+ if (this.requestedMode.value === "auto") {
3096
+ this.webrtcHandler.close();
3097
+ this.webrtcHandler = void 0;
3098
+ this.activeMode.value = "mse";
3099
+ if (this.mseHandler?.isReady) this.status.value = "connected";
3100
+ else this.startMSE();
3101
+ } else {
3102
+ this.markSourceUndecodable();
3103
+ this.restart();
3104
+ }
3105
+ } else if (this.status.value === "connected" && !this.isPlaying.value && !this.abortController.signal.aborted && this.sourceLikelyH265()) this.handleNoFramesWhileConnected();
3003
3106
  }, STREAM_CONFIG.WEBRTC.CONNECT_TIMEOUT, { immediate: false });
3004
3107
  const mseConnectTimeout = useTimeoutFn(() => {
3005
3108
  if (this.abortController.signal.aborted || this.status.value === "connected" || this.status.value === "closed") return;
@@ -3937,8 +4040,10 @@ function useCuiFullscreen(target, options = {}) {
3937
4040
  if (!state) return;
3938
4041
  fsRegistry.delete(el);
3939
4042
  el.removeAttribute("data-cui-fullscreen");
3940
- if (state.parent) if (state.next && state.next.parentNode === state.parent) state.parent.insertBefore(el, state.next);
3941
- else state.parent.appendChild(el);
4043
+ if (state.parent) {
4044
+ if (state.next && state.next.parentNode === state.parent) state.parent.insertBefore(el, state.next);
4045
+ else state.parent.appendChild(el);
4046
+ }
3942
4047
  state.wrapper.remove();
3943
4048
  document.body.style.overflow = state.bodyOverflow;
3944
4049
  document.documentElement.style.overflow = state.htmlOverflow;
@@ -4177,8 +4282,10 @@ function useCameraStream(options) {
4177
4282
  videoElement.value = video;
4178
4283
  cached.videoElementRef.value = video;
4179
4284
  setupCachedStreamWatchers(cached.stream, video, camName);
4180
- if (shouldAutoStart() && autoStartReady.value && activityModeManager.mode.value === "always-on" && !isCameraDisabled.value) if (cached.stream.activeMode.value !== "mse") attachCachedStream(cached, video, camName);
4181
- else video.play().catch(() => {});
4285
+ if (shouldAutoStart() && autoStartReady.value && activityModeManager.mode.value === "always-on" && !isCameraDisabled.value) {
4286
+ if (cached.stream.activeMode.value !== "mse") attachCachedStream(cached, video, camName);
4287
+ else video.play().catch(() => {});
4288
+ }
4182
4289
  } else if (camDevice) {
4183
4290
  initialized.value = true;
4184
4291
  registeredCamName = camName;
@@ -4343,14 +4450,18 @@ function useCameraStream(options) {
4343
4450
  function applyCanvasStyles(canvas) {
4344
4451
  const style = toValue(options.canvasStyle);
4345
4452
  const cls = toValue(options.canvasClass);
4346
- if (style) if (typeof style === "string") canvas.style.cssText += ";" + style;
4347
- else if (Array.isArray(style)) {
4348
- for (const s of style) if (typeof s === "string") canvas.style.cssText += ";" + s;
4349
- else if (s) Object.assign(canvas.style, s);
4350
- } else Object.assign(canvas.style, style);
4351
- if (cls) if (typeof cls === "string") canvas.className = cls;
4352
- else if (Array.isArray(cls)) canvas.className = cls.filter(Boolean).join(" ");
4353
- else for (const [name, active] of Object.entries(cls)) canvas.classList.toggle(name, !!active);
4453
+ if (style) {
4454
+ if (typeof style === "string") canvas.style.cssText += ";" + style;
4455
+ else if (Array.isArray(style)) {
4456
+ for (const s of style) if (typeof s === "string") canvas.style.cssText += ";" + s;
4457
+ else if (s) Object.assign(canvas.style, s);
4458
+ } else Object.assign(canvas.style, style);
4459
+ }
4460
+ if (cls) {
4461
+ if (typeof cls === "string") canvas.className = cls;
4462
+ else if (Array.isArray(cls)) canvas.className = cls.filter(Boolean).join(" ");
4463
+ else for (const [name, active] of Object.entries(cls)) canvas.classList.toggle(name, !!active);
4464
+ }
4354
4465
  }
4355
4466
  if (!isolated && typeof IntersectionObserver !== "undefined") {
4356
4467
  let visibilityObserver;
@@ -4401,14 +4512,18 @@ function useCameraStream(options) {
4401
4512
  () => toValue(options.videoClass)
4402
4513
  ], ([video, style, cls]) => {
4403
4514
  if (!video) return;
4404
- if (style) if (typeof style === "string") video.style.cssText += ";" + style;
4405
- else if (Array.isArray(style)) {
4406
- for (const s of style) if (typeof s === "string") video.style.cssText += ";" + s;
4407
- else if (s) Object.assign(video.style, s);
4408
- } else Object.assign(video.style, style);
4409
- if (cls) if (typeof cls === "string") video.className = cls;
4410
- else if (Array.isArray(cls)) video.className = cls.filter(Boolean).join(" ");
4411
- else for (const [name, active] of Object.entries(cls)) video.classList.toggle(name, active);
4515
+ if (style) {
4516
+ if (typeof style === "string") video.style.cssText += ";" + style;
4517
+ else if (Array.isArray(style)) {
4518
+ for (const s of style) if (typeof s === "string") video.style.cssText += ";" + s;
4519
+ else if (s) Object.assign(video.style, s);
4520
+ } else Object.assign(video.style, style);
4521
+ }
4522
+ if (cls) {
4523
+ if (typeof cls === "string") video.className = cls;
4524
+ else if (Array.isArray(cls)) video.className = cls.filter(Boolean).join(" ");
4525
+ else for (const [name, active] of Object.entries(cls)) video.classList.toggle(name, active);
4526
+ }
4412
4527
  }, { immediate: true });
4413
4528
  cleanupFns.push(stopVideoStyleWatch);
4414
4529
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camera.ui/browser",
3
- "version": "0.0.173",
3
+ "version": "0.0.174",
4
4
  "description": "camera.ui browser client",
5
5
  "author": "seydx (https://github.com/cameraui/clients)",
6
6
  "type": "module",
@@ -39,17 +39,17 @@
39
39
  "devDependencies": {
40
40
  "@camera.ui/logger": "file:../../packages/logger",
41
41
  "@camera.ui/rpc": "^1.0.11",
42
- "@camera.ui/sdk": "~1.2.30",
42
+ "@camera.ui/sdk": "~1.2.31",
43
43
  "@camera.ui/transport": "file:../../packages/transport",
44
44
  "@eneris/push-receiver": "^4.3.1",
45
- "@microsoft/api-extractor": "^7.58.12",
45
+ "@microsoft/api-extractor": "^7.59.0",
46
46
  "@stylistic/eslint-plugin": "^5.10.0",
47
47
  "@types/webrtc": "^0.0.47",
48
48
  "@typescript-eslint/parser": "^8.67.0",
49
49
  "@vitejs/plugin-vue": "^6.0.8",
50
50
  "@vue/eslint-config-prettier": "^10.2.0",
51
51
  "@vue/eslint-config-typescript": "^14.9.0",
52
- "@vue/language-core": "^3.3.10",
52
+ "@vue/language-core": "^3.3.11",
53
53
  "@vue/tsconfig": "^0.9.1",
54
54
  "@vueuse/core": "^14.4.0",
55
55
  "@webgpu/types": "^0.1.72",
@@ -61,11 +61,11 @@
61
61
  "typescript": "5.9.3",
62
62
  "typescript-eslint": "^8.67.0",
63
63
  "unplugin-dts": "^1.0.3",
64
- "updates": "^18.0.3",
65
- "vite": "^8.2.1",
64
+ "updates": "^18.0.5",
65
+ "vite": "^8.2.2",
66
66
  "vitest": "^4.1.11",
67
67
  "vue": "^3.5.41",
68
- "vue-tsc": "^3.3.10"
68
+ "vue-tsc": "^3.3.11"
69
69
  },
70
70
  "overrides": {
71
71
  "@vue/language-core": "$@vue/language-core"