@camera.ui/browser 0.0.190 → 0.0.192

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
@@ -883,7 +883,7 @@ export declare interface ReactiveContactSensor extends ReactiveSensor<ContactSen
883
883
  getProperty(property: string): unknown;
884
884
  }
885
885
 
886
- export declare interface ReactiveCoreManager extends Omit<CoreManager, 'connectToPlugin' | 'signRequest' | 'onEvent'> {
886
+ export declare interface ReactiveCoreManager extends Omit<CoreManager, 'connectToPlugin' | 'signRequest' | 'onEvent' | 'assistantAsk' | 'assistantAccess'> {
887
887
  }
888
888
 
889
889
  export declare interface ReactiveDeviceManager {
@@ -1078,6 +1078,7 @@ export declare interface ReactiveSmokeSensor extends ReactiveSensor<SmokeSensorP
1078
1078
 
1079
1079
  export declare interface ReactiveStorage {
1080
1080
  readonly proxy: Promisify<StorageRPC>;
1081
+ readonly client: RPCClient;
1081
1082
  readonly config: ShallowRef<SchemaConfig | undefined>;
1082
1083
  readonly isLoading: Ref<boolean>;
1083
1084
  readonly error: Ref<Error | undefined>;
package/dist/index.js CHANGED
@@ -430,9 +430,7 @@ function fetchSnapshotShared(deviceManager, id, forceFresh = false) {
430
430
  const device = await acquireCameraDevice(deviceManager, id);
431
431
  try {
432
432
  if (!device) return void 0;
433
- const result = await device.fetchSnapshot(void 0, forceFresh ? true : void 0);
434
- if (result) setSnapshot(id, result);
435
- return result ?? void 0;
433
+ return await device.fetchSnapshot(void 0, forceFresh ? true : void 0) ?? void 0;
436
434
  } finally {
437
435
  if (device) releaseCameraDevice(id);
438
436
  }
@@ -445,28 +443,32 @@ function hookReconnectRefresh(cameraUi, deviceManager) {
445
443
  if (reconnectHooked) return;
446
444
  reconnectHooked = true;
447
445
  cameraUi.on("reconnected", () => {
448
- let delay = 0;
449
- for (const id of subscribers.keys()) {
450
- if ((subscribers.get(id)?.size ?? 0) === 0) continue;
451
- const cameraId = id;
452
- setTimeout(() => {
453
- if ((subscribers.get(cameraId)?.size ?? 0) === 0) return;
454
- fetchSnapshotShared(deviceManager, cameraId).catch(() => void 0);
455
- }, delay);
456
- delay += 150;
457
- }
446
+ setTimeout(() => {
447
+ let delay = 0;
448
+ for (const id of subscribers.keys()) {
449
+ if ((subscribers.get(id)?.size ?? 0) === 0) continue;
450
+ const cameraId = id;
451
+ setTimeout(() => {
452
+ if ((subscribers.get(cameraId)?.size ?? 0) === 0) return;
453
+ fetchSnapshotShared(deviceManager, cameraId).catch(() => void 0);
454
+ }, delay);
455
+ delay += 150;
456
+ }
457
+ }, 0);
458
458
  });
459
459
  }
460
460
  function deferRevoke(url) {
461
461
  setTimeout(() => URL.revokeObjectURL(url), REVOKE_DELAY_MS);
462
462
  }
463
463
  function setSnapshot(cameraId, data, fetchedAt) {
464
- const oldUrl = urlCache.get(cameraId);
465
- if (oldUrl) {
466
- deferRevoke(oldUrl);
467
- urlCache.delete(cameraId);
464
+ if (snapshotCache.get(cameraId) !== data) {
465
+ const oldUrl = urlCache.get(cameraId);
466
+ if (oldUrl) {
467
+ deferRevoke(oldUrl);
468
+ urlCache.delete(cameraId);
469
+ }
470
+ snapshotCache.set(cameraId, data);
468
471
  }
469
- snapshotCache.set(cameraId, data);
470
472
  if (fetchedAt !== void 0) timestampCache.set(cameraId, fetchedAt);
471
473
  notify(cameraId);
472
474
  }
@@ -1808,7 +1810,7 @@ async function resolvePluginId(rpcRef, pluginName) {
1808
1810
  }
1809
1811
  //#endregion
1810
1812
  //#region src/composables/useStorage.ts
1811
- function createReactiveStorage(proxy) {
1813
+ function createReactiveStorage(proxy, client) {
1812
1814
  const config = shallowRef();
1813
1815
  const isLoading = ref(false);
1814
1816
  const error = ref();
@@ -1872,6 +1874,7 @@ function createReactiveStorage(proxy) {
1872
1874
  }
1873
1875
  return {
1874
1876
  proxy,
1877
+ client,
1875
1878
  config,
1876
1879
  isLoading,
1877
1880
  error,
@@ -1891,10 +1894,10 @@ function getCameraStorageKey(pluginId, cameraId) {
1891
1894
  function getSensorStorageKey(pluginId, sensorId) {
1892
1895
  return `plugin:${pluginId}:sensor:${sensorId}`;
1893
1896
  }
1894
- function acquireStorage(key, createProxy) {
1895
- return storageCache.acquire(key, () => {
1896
- return createReactiveStorage(createProxy());
1897
- });
1897
+ function acquireStorage(key, client, createProxy) {
1898
+ const cached = storageCache.get(key);
1899
+ if (cached && cached.client !== client) storageCache.forceRelease(key);
1900
+ return storageCache.acquire(key, () => createReactiveStorage(createProxy(), client));
1898
1901
  }
1899
1902
  function releaseStorage(key) {
1900
1903
  storageCache.release(key);
@@ -1918,10 +1921,23 @@ function cleanupStorage(state, isConnected, config) {
1918
1921
  state.cachedStorage = void 0;
1919
1922
  config.value = void 0;
1920
1923
  }
1924
+ function bindStorage(state, storageKey, client, config, createProxy) {
1925
+ if (state.currentStorageKey === storageKey && state.cachedStorage?.client === client) return;
1926
+ if (state.currentStorageKey && state.currentStorageKey !== storageKey) {
1927
+ releaseStorage(state.currentStorageKey);
1928
+ config.value = void 0;
1929
+ }
1930
+ state.currentStorageKey = storageKey;
1931
+ state.cachedStorage = acquireStorage(storageKey, client, createProxy);
1932
+ }
1921
1933
  var GET_CONFIG_RETRY_DELAYS = [
1922
1934
  2e3,
1923
1935
  5e3,
1924
1936
  1e4,
1937
+ 15e3,
1938
+ 15e3,
1939
+ 15e3,
1940
+ 15e3,
1925
1941
  15e3
1926
1942
  ];
1927
1943
  function sleep(ms) {
@@ -2017,7 +2033,8 @@ function createStorageOperations(state, config, isLoading, error) {
2017
2033
  };
2018
2034
  }
2019
2035
  function usePluginStorage(pluginName) {
2020
- const { rpc, isConnected: clientConnected } = useCameraUi();
2036
+ const cameraUi = useCameraUi();
2037
+ const { rpc, isConnected: clientConnected } = cameraUi;
2021
2038
  const config = shallowRef();
2022
2039
  const _isLoading = ref(false);
2023
2040
  const initialSetupDone = ref(false);
@@ -2026,16 +2043,14 @@ function usePluginStorage(pluginName) {
2026
2043
  const state = createStorageState();
2027
2044
  const operations = createStorageOperations(state, config, _isLoading, error);
2028
2045
  async function connect(name) {
2029
- if (!rpc.value || !clientConnected.value) return false;
2046
+ const client = rpc.value;
2047
+ if (!client || !clientConnected.value) return false;
2030
2048
  try {
2031
2049
  const pluginId = await resolvePluginId(rpc, name);
2032
2050
  if (!pluginId) throw new Error(`Plugin "${name}" not found`);
2033
- const storageKey = getPluginStorageKey(pluginId);
2034
- if (state.currentStorageKey && state.currentStorageKey !== storageKey) releaseStorage(state.currentStorageKey);
2035
- state.currentStorageKey = storageKey;
2036
- state.cachedStorage = acquireStorage(storageKey, () => {
2051
+ bindStorage(state, getPluginStorageKey(pluginId), client, config, () => {
2037
2052
  const namespaces = NamespaceManager.pluginNamespaces(pluginId);
2038
- return rpc.value.createProxy(namespaces.pluginStorageRpc);
2053
+ return client.createProxy(namespaces.pluginStorageRpc);
2039
2054
  });
2040
2055
  isConnected.value = true;
2041
2056
  return true;
@@ -2053,14 +2068,20 @@ function usePluginStorage(pluginName) {
2053
2068
  }
2054
2069
  return operations.getConfig();
2055
2070
  }
2056
- watch([clientConnected, () => toValue(pluginName)], async ([connected, name]) => {
2057
- if (connected && name) {
2071
+ async function sync() {
2072
+ const name = toValue(pluginName);
2073
+ if (clientConnected.value && name) {
2058
2074
  await connect(name);
2059
2075
  operations.getConfig();
2060
2076
  } else cleanupStorage(state, isConnected, config);
2061
2077
  initialSetupDone.value = true;
2062
- }, { immediate: true });
2063
- tryOnScopeDispose(() => cleanupStorage(state, isConnected, config));
2078
+ }
2079
+ watch([clientConnected, () => toValue(pluginName)], sync, { immediate: true });
2080
+ cameraUi.on("reconnected", sync);
2081
+ tryOnScopeDispose(() => {
2082
+ cameraUi.off("reconnected", sync);
2083
+ cleanupStorage(state, isConnected, config);
2084
+ });
2064
2085
  return {
2065
2086
  config,
2066
2087
  isLoading: computed(() => _isLoading.value || !initialSetupDone.value),
@@ -2073,7 +2094,8 @@ function usePluginStorage(pluginName) {
2073
2094
  };
2074
2095
  }
2075
2096
  function useCameraStorage(camera, pluginName) {
2076
- const { rpc, isConnected: clientConnected } = useCameraUi();
2097
+ const cameraUi = useCameraUi();
2098
+ const { rpc, isConnected: clientConnected } = cameraUi;
2077
2099
  const config = shallowRef();
2078
2100
  const _isLoading = ref(false);
2079
2101
  const initialSetupDone = ref(false);
@@ -2082,16 +2104,14 @@ function useCameraStorage(camera, pluginName) {
2082
2104
  const state = createStorageState();
2083
2105
  const operations = createStorageOperations(state, config, _isLoading, error);
2084
2106
  async function connect(cameraId, name) {
2085
- if (!rpc.value || !clientConnected.value) return false;
2107
+ const client = rpc.value;
2108
+ if (!client || !clientConnected.value) return false;
2086
2109
  try {
2087
2110
  const pluginId = await resolvePluginId(rpc, name);
2088
2111
  if (!pluginId) throw new Error(`Plugin "${name}" not found`);
2089
- const storageKey = getCameraStorageKey(pluginId, cameraId);
2090
- if (state.currentStorageKey && state.currentStorageKey !== storageKey) releaseStorage(state.currentStorageKey);
2091
- state.currentStorageKey = storageKey;
2092
- state.cachedStorage = acquireStorage(storageKey, () => {
2112
+ bindStorage(state, getCameraStorageKey(pluginId, cameraId), client, config, () => {
2093
2113
  const namespaces = NamespaceManager.pluginCameraNamespaces(pluginId, cameraId);
2094
- return rpc.value.createProxy(namespaces.cameraStorageRpc);
2114
+ return client.createProxy(namespaces.cameraStorageRpc);
2095
2115
  });
2096
2116
  isConnected.value = true;
2097
2117
  return true;
@@ -2110,18 +2130,25 @@ function useCameraStorage(camera, pluginName) {
2110
2130
  }
2111
2131
  return operations.getConfig();
2112
2132
  }
2113
- watch([
2114
- clientConnected,
2115
- () => extractCameraId(toValue(camera)),
2116
- () => toValue(pluginName)
2117
- ], async ([connected, cameraId, name]) => {
2118
- if (connected && cameraId && name) {
2133
+ async function sync() {
2134
+ const cameraId = extractCameraId(toValue(camera));
2135
+ const name = toValue(pluginName);
2136
+ if (clientConnected.value && cameraId && name) {
2119
2137
  await connect(cameraId, name);
2120
2138
  operations.getConfig();
2121
2139
  } else cleanupStorage(state, isConnected, config);
2122
2140
  initialSetupDone.value = true;
2123
- }, { immediate: true });
2124
- tryOnScopeDispose(() => cleanupStorage(state, isConnected, config));
2141
+ }
2142
+ watch([
2143
+ clientConnected,
2144
+ () => extractCameraId(toValue(camera)),
2145
+ () => toValue(pluginName)
2146
+ ], sync, { immediate: true });
2147
+ cameraUi.on("reconnected", sync);
2148
+ tryOnScopeDispose(() => {
2149
+ cameraUi.off("reconnected", sync);
2150
+ cleanupStorage(state, isConnected, config);
2151
+ });
2125
2152
  return {
2126
2153
  config,
2127
2154
  isLoading: computed(() => _isLoading.value || !initialSetupDone.value),
@@ -2134,7 +2161,8 @@ function useCameraStorage(camera, pluginName) {
2134
2161
  };
2135
2162
  }
2136
2163
  function useSensorStorage(sensorId, pluginId) {
2137
- const { rpc, isConnected: clientConnected } = useCameraUi();
2164
+ const cameraUi = useCameraUi();
2165
+ const { rpc, isConnected: clientConnected } = cameraUi;
2138
2166
  const config = shallowRef();
2139
2167
  const _isLoading = ref(false);
2140
2168
  const initialSetupDone = ref(false);
@@ -2143,14 +2171,12 @@ function useSensorStorage(sensorId, pluginId) {
2143
2171
  const state = createStorageState();
2144
2172
  const operations = createStorageOperations(state, config, _isLoading, error);
2145
2173
  function connect(senId, plugId) {
2146
- if (!rpc.value || !clientConnected.value) return false;
2174
+ const client = rpc.value;
2175
+ if (!client || !clientConnected.value) return false;
2147
2176
  try {
2148
- const storageKey = getSensorStorageKey(plugId, senId);
2149
- if (state.currentStorageKey && state.currentStorageKey !== storageKey) releaseStorage(state.currentStorageKey);
2150
- state.currentStorageKey = storageKey;
2151
- state.cachedStorage = acquireStorage(storageKey, () => {
2177
+ bindStorage(state, getSensorStorageKey(plugId, senId), client, config, () => {
2152
2178
  const namespaces = NamespaceManager.pluginSensorNamespaces(plugId, senId);
2153
- return rpc.value.createProxy(namespaces.sensorStorageRpc);
2179
+ return client.createProxy(namespaces.sensorStorageRpc);
2154
2180
  });
2155
2181
  isConnected.value = true;
2156
2182
  return true;
@@ -2169,18 +2195,25 @@ function useSensorStorage(sensorId, pluginId) {
2169
2195
  }
2170
2196
  return operations.getConfig();
2171
2197
  }
2172
- watch([
2173
- clientConnected,
2174
- () => toValue(sensorId),
2175
- () => toValue(pluginId)
2176
- ], ([connected, senId, plugId]) => {
2177
- if (connected && senId && plugId) {
2198
+ function sync() {
2199
+ const senId = toValue(sensorId);
2200
+ const plugId = toValue(pluginId);
2201
+ if (clientConnected.value && senId && plugId) {
2178
2202
  connect(senId, plugId);
2179
2203
  operations.getConfig();
2180
2204
  } else cleanupStorage(state, isConnected, config);
2181
2205
  initialSetupDone.value = true;
2182
- }, { immediate: true });
2183
- tryOnScopeDispose(() => cleanupStorage(state, isConnected, config));
2206
+ }
2207
+ watch([
2208
+ clientConnected,
2209
+ () => toValue(sensorId),
2210
+ () => toValue(pluginId)
2211
+ ], sync, { immediate: true });
2212
+ cameraUi.on("reconnected", sync);
2213
+ tryOnScopeDispose(() => {
2214
+ cameraUi.off("reconnected", sync);
2215
+ cleanupStorage(state, isConnected, config);
2216
+ });
2184
2217
  return {
2185
2218
  config,
2186
2219
  isLoading: computed(() => _isLoading.value || !initialSetupDone.value),
@@ -3018,6 +3051,7 @@ async function processWebRTCMessage(handler, msg) {
3018
3051
  //#endregion
3019
3052
  //#region src/streaming/streamConnection.ts
3020
3053
  var log = new Logger("StreamConnection");
3054
+ var MSE_AUDIO_CODEC = /mp4a|opus|flac|mp3/i;
3021
3055
  function isVideoInPictureInPicture(video) {
3022
3056
  if (!video) return false;
3023
3057
  if (typeof document !== "undefined" && "pictureInPictureElement" in document && document.pictureInPictureElement === video) return true;
@@ -3597,6 +3631,9 @@ var StreamConnection = class {
3597
3631
  this.markSourceUndecodable();
3598
3632
  this.restart();
3599
3633
  }
3634
+ noteDeliveredAudio(delivered) {
3635
+ if (delivered && !this.hasAudio.value) this.hasAudio.value = true;
3636
+ }
3600
3637
  async probeStream() {
3601
3638
  const cam = this.camera.value;
3602
3639
  const source = this.source.value;
@@ -3681,7 +3718,10 @@ var StreamConnection = class {
3681
3718
  else if (this.backchannelHandler) processWebRTCMessage(this.backchannelHandler, msg);
3682
3719
  break;
3683
3720
  case "mse":
3684
- if (this.mseHandler && typeof msg.value === "string") this.mseHandler.initializeBuffer(msg.value);
3721
+ if (this.mseHandler && typeof msg.value === "string") {
3722
+ this.noteDeliveredAudio(MSE_AUDIO_CODEC.test(msg.value));
3723
+ this.mseHandler.initializeBuffer(msg.value);
3724
+ }
3685
3725
  break;
3686
3726
  case "error": if (this.requestedMode.value !== "auto") {
3687
3727
  this.error.value = new Error(msg.value);
@@ -3717,6 +3757,7 @@ var StreamConnection = class {
3717
3757
  this.handleWebRTCFailed();
3718
3758
  return;
3719
3759
  }
3760
+ this.noteDeliveredAudio(stream.getAudioTracks().length > 0);
3720
3761
  this.activeMode.value = this.requestedMode.value === "auto" ? "webrtc" : this.requestedMode.value;
3721
3762
  this.lastMediaStream = stream;
3722
3763
  video.srcObject = stream;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camera.ui/browser",
3
- "version": "0.0.190",
3
+ "version": "0.0.192",
4
4
  "description": "camera.ui browser client",
5
5
  "author": "seydx (https://github.com/cameraui/clients)",
6
6
  "type": "module",
@@ -63,7 +63,7 @@
63
63
  "unplugin-dts": "^1.1.0",
64
64
  "updates": "^18.1.0",
65
65
  "vite": "^8.3.0",
66
- "vitest": "^5.0.0",
66
+ "vitest": "^5.0.1",
67
67
  "vue": "^3.5.42",
68
68
  "vue-tsc": "^3.3.11"
69
69
  },