@camera.ui/browser 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/index.d.ts CHANGED
@@ -1601,6 +1601,7 @@ export declare interface UseSnapshotReturn {
1601
1601
  snapshotSrc: ComputedRef<string | undefined>;
1602
1602
  snapshotTimestamp: ComputedRef<number | undefined>;
1603
1603
  isLoading: ComputedRef<boolean>;
1604
+ isRefreshing: ComputedRef<boolean>;
1604
1605
  refresh: () => Promise<void>;
1605
1606
  }
1606
1607
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Logger } from "@camera.ui/logger";
2
- import { computed, effectScope, inject, markRaw, onBeforeUnmount, readonly, ref, shallowReactive, shallowRef, toValue, watch } from "vue";
2
+ import { computed, effectScope, inject, markRaw, onBeforeUnmount, reactive, readonly, ref, shallowReactive, shallowRef, toValue, watch } from "vue";
3
3
  import { tryOnScopeDispose, useTimeoutFn, whenever } from "@vueuse/core";
4
4
  import { SensorType } from "@camera.ui/sdk";
5
5
  import { isNoRespondersError } from "@camera.ui/rpc";
@@ -666,6 +666,7 @@ function useTabVisibility() {
666
666
  //#region src/composables/useSnapshot.ts
667
667
  var REVOKE_DELAY_MS = 5e3;
668
668
  var SERVER_CLOCK_STEP_MS = 6e4;
669
+ var REFRESH_LINGER_MS = 500;
669
670
  var snapshotCache = /* @__PURE__ */ new Map();
670
671
  var timestampCache = /* @__PURE__ */ new Map();
671
672
  var takenAtCache = /* @__PURE__ */ new Map();
@@ -673,6 +674,10 @@ var stampCache = /* @__PURE__ */ new Map();
673
674
  var urlCache = /* @__PURE__ */ new Map();
674
675
  var subscribers = /* @__PURE__ */ new Map();
675
676
  var pendingLoads$2 = /* @__PURE__ */ new Map();
677
+ var refreshingCameras = reactive(/* @__PURE__ */ new Set());
678
+ var refreshLingers = /* @__PURE__ */ new Map();
679
+ var forcedRefreshes = /* @__PURE__ */ new Map();
680
+ var serverClockOffset = 0;
676
681
  function notify(cameraId) {
677
682
  subscribers.get(cameraId)?.forEach((cb) => cb());
678
683
  }
@@ -687,7 +692,9 @@ function fetchSnapshotShared(deviceManager, id, forceFresh = false) {
687
692
  } finally {
688
693
  if (device) releaseCameraDevice(device);
689
694
  }
690
- })().finally(() => pendingLoads$2.delete(id));
695
+ })().finally(() => {
696
+ if (pendingLoads$2.get(id) === promise) pendingLoads$2.delete(id);
697
+ });
691
698
  pendingLoads$2.set(id, promise);
692
699
  return promise;
693
700
  }
@@ -696,6 +703,7 @@ function hookReconnectRefresh(cameraUi, deviceManager) {
696
703
  if (hookedContexts.has(cameraUi)) return;
697
704
  hookedContexts.add(cameraUi);
698
705
  cameraUi.on("reconnected", () => {
706
+ pendingLoads$2.clear();
699
707
  setTimeout(() => {
700
708
  let delay = 0;
701
709
  for (const id of subscribers.keys()) {
@@ -719,6 +727,32 @@ function isOutdated(settings, fetchedAt) {
719
727
  function deferRevoke(url) {
720
728
  setTimeout(() => URL.revokeObjectURL(url), REVOKE_DELAY_MS);
721
729
  }
730
+ function startSnapshotRefresh(cameraId, forced = false) {
731
+ clearTimeout(refreshLingers.get(cameraId));
732
+ refreshLingers.delete(cameraId);
733
+ if (forced) forcedRefreshes.set(cameraId, (forcedRefreshes.get(cameraId) ?? 0) + 1);
734
+ refreshingCameras.add(cameraId);
735
+ }
736
+ function endSnapshotRefresh(cameraId, forced = false) {
737
+ const running = (forcedRefreshes.get(cameraId) ?? 0) - (forced ? 1 : 0);
738
+ if (running > 0) {
739
+ forcedRefreshes.set(cameraId, running);
740
+ return;
741
+ }
742
+ forcedRefreshes.delete(cameraId);
743
+ clearTimeout(refreshLingers.get(cameraId));
744
+ const linger = setTimeout(() => {
745
+ refreshLingers.delete(cameraId);
746
+ refreshingCameras.delete(cameraId);
747
+ }, REFRESH_LINGER_MS);
748
+ refreshLingers.set(cameraId, linger);
749
+ }
750
+ function rememberServerClock(browserTime, serverTime) {
751
+ serverClockOffset = browserTime - serverTime;
752
+ }
753
+ function toBrowserTime(serverTime) {
754
+ return serverTime === void 0 ? void 0 : serverTime + serverClockOffset;
755
+ }
722
756
  function isOlderThanHeld(cameraId, takenAt, stamp) {
723
757
  const held = stampCache.get(cameraId);
724
758
  if (stamp && held) {
@@ -739,6 +773,8 @@ function setSnapshot(cameraId, data, fetchedAt, takenAt, stamp) {
739
773
  urlCache.delete(cameraId);
740
774
  }
741
775
  snapshotCache.set(cameraId, data);
776
+ startSnapshotRefresh(cameraId);
777
+ endSnapshotRefresh(cameraId);
742
778
  }
743
779
  if (fetchedAt !== void 0) timestampCache.set(cameraId, fetchedAt);
744
780
  notify(cameraId);
@@ -769,17 +805,22 @@ function getSnapshotUrl(cameraId) {
769
805
  }
770
806
  function clearSnapshotCache() {
771
807
  for (const url of urlCache.values()) URL.revokeObjectURL(url);
808
+ for (const linger of refreshLingers.values()) clearTimeout(linger);
772
809
  urlCache.clear();
773
810
  snapshotCache.clear();
774
811
  timestampCache.clear();
775
812
  takenAtCache.clear();
776
813
  stampCache.clear();
814
+ refreshLingers.clear();
815
+ forcedRefreshes.clear();
816
+ refreshingCameras.clear();
817
+ serverClockOffset = 0;
777
818
  }
778
819
  function useSnapshot(cameraIdOrName) {
779
820
  const cameraUi = useCameraUi();
780
821
  const { isConnected } = cameraUi;
781
822
  const deviceManager = useDeviceManager();
782
- const { onTabVisible } = useTabVisibility();
823
+ const { isVisible, onTabVisible } = useTabVisibility();
783
824
  const snapshot = shallowRef();
784
825
  const _isLoading = ref(false);
785
826
  const initialLoadDone = ref(false);
@@ -799,6 +840,7 @@ function useSnapshot(cameraIdOrName) {
799
840
  if (!subscribers.has(cameraId)) subscribers.set(cameraId, /* @__PURE__ */ new Set());
800
841
  const updateFn = () => {
801
842
  snapshot.value = snapshotCache.get(cameraId);
843
+ armViewRefresh(cameraId);
802
844
  };
803
845
  subscribers.get(cameraId).add(updateFn);
804
846
  return () => {
@@ -809,6 +851,7 @@ function useSnapshot(cameraIdOrName) {
809
851
  let heldDeviceId;
810
852
  let heldDevice;
811
853
  let pendingHoldId;
854
+ let viewRefreshTimer;
812
855
  const holdRetry = createRetryTimer();
813
856
  let disposed = false;
814
857
  function currentId() {
@@ -829,6 +872,7 @@ function useSnapshot(cameraIdOrName) {
829
872
  heldDeviceId = id;
830
873
  heldDevice = device;
831
874
  cancelHoldRetry();
875
+ armViewRefresh(id);
832
876
  } catch {
833
877
  if (currentId() === id) scheduleHoldRetry(id);
834
878
  } finally {
@@ -849,12 +893,32 @@ function useSnapshot(cameraIdOrName) {
849
893
  holdRetry.reset();
850
894
  }
851
895
  function releaseHeldDevice() {
896
+ clearTimeout(viewRefreshTimer);
852
897
  if (heldDeviceId) {
853
898
  if (heldDevice) releaseCameraDevice(heldDevice);
854
899
  heldDeviceId = void 0;
855
900
  heldDevice = void 0;
856
901
  }
857
902
  }
903
+ function refreshIfOutdated(id) {
904
+ if (!isConnected.value || !heldDevice || heldDeviceId !== id) return;
905
+ if (!isOutdated(heldDevice.camera.value.snapshotSettings, timestampCache.get(id))) return;
906
+ fetchSnapshotShared(deviceManager, id).catch(() => void 0);
907
+ }
908
+ function armViewRefresh(id) {
909
+ clearTimeout(viewRefreshTimer);
910
+ const settings = heldDevice?.camera.value.snapshotSettings;
911
+ if (heldDeviceId !== id || settings?.mode !== "onView") return;
912
+ const maxAgeMs = settings.maxAge * 1e3;
913
+ const leftMs = (timestampCache.get(id) ?? 0) + maxAgeMs - Date.now();
914
+ viewRefreshTimer = setTimeout(() => {
915
+ refreshOnView(id);
916
+ }, leftMs > 0 ? leftMs : maxAgeMs);
917
+ }
918
+ async function refreshOnView(id) {
919
+ if (isVisible.value && isConnected.value) await fetchSnapshotShared(deviceManager, id).catch(() => void 0);
920
+ armViewRefresh(id);
921
+ }
858
922
  function showCached(id) {
859
923
  const cached = snapshotCache.get(id);
860
924
  if (!cached) return false;
@@ -864,7 +928,10 @@ function useSnapshot(cameraIdOrName) {
864
928
  }
865
929
  async function loadSnapshot(id, skipCache = false) {
866
930
  if (!isConnected.value || !id) return;
867
- if (!skipCache && showCached(id)) return;
931
+ if (!skipCache && showCached(id)) {
932
+ refreshIfOutdated(id);
933
+ return;
934
+ }
868
935
  _isLoading.value = true;
869
936
  try {
870
937
  const result = await fetchSnapshotShared(deviceManager, id);
@@ -915,12 +982,7 @@ function useSnapshot(cameraIdOrName) {
915
982
  snapshot.value = void 0;
916
983
  }
917
984
  }, { immediate: true });
918
- onTabVisible(() => {
919
- const id = currentId();
920
- if (!isConnected.value || !heldDevice || heldDeviceId !== id) return;
921
- if (!isOutdated(heldDevice.camera.value.snapshotSettings, timestampCache.get(id))) return;
922
- fetchSnapshotShared(deviceManager, id).catch(() => void 0);
923
- });
985
+ onTabVisible(() => refreshIfOutdated(currentId()));
924
986
  tryOnScopeDispose(() => {
925
987
  disposed = true;
926
988
  cancelHoldRetry();
@@ -932,14 +994,22 @@ function useSnapshot(cameraIdOrName) {
932
994
  snapshotSrc,
933
995
  snapshotTimestamp,
934
996
  isLoading: computed(() => _isLoading.value || !initialLoadDone.value),
997
+ isRefreshing: computed(() => refreshingCameras.has(currentId())),
935
998
  refresh
936
999
  };
937
1000
  }
938
1001
  //#endregion
939
1002
  //#region src/composables/useCamera.ts
940
1003
  var NOT_MODIFIED = "not-modified";
1004
+ var CLOCK_SAMPLE_MAX_MS = 5e3;
941
1005
  var snapshotWithMetaSupport = /* @__PURE__ */ new WeakMap();
942
1006
  var sensorEventFanouts = /* @__PURE__ */ new WeakMap();
1007
+ function replyTime(result, requestedAt) {
1008
+ if (result.stamp === void 0 || result.fetchedAt === void 0) return result.fetchedAt;
1009
+ if (Date.now() - requestedAt > CLOCK_SAMPLE_MAX_MS) return toBrowserTime(result.stamp);
1010
+ rememberServerClock(result.fetchedAt, result.stamp);
1011
+ return result.fetchedAt;
1012
+ }
943
1013
  async function createReactiveCameraDevice(rpcOrContext, initialCamera) {
944
1014
  const cameraNamespaces = NamespaceManager.cameraNamespaces(initialCamera._id);
945
1015
  const ctx = "rpc" in rpcOrContext && "value" in rpcOrContext.rpc ? rpcOrContext : void 0;
@@ -999,6 +1069,7 @@ async function createReactiveCameraDevice(rpcOrContext, initialCamera) {
999
1069
  const hasPtz = computed(() => existingSensorTypes.value.has(SensorType.PTZ));
1000
1070
  async function fetchSnapshot(sourceId, forceNew) {
1001
1071
  snapshotLoading.value = true;
1072
+ startSnapshotRefresh(initialCamera._id, forceNew);
1002
1073
  const requestedAt = Date.now();
1003
1074
  try {
1004
1075
  const result = await requestSnapshot(sourceId, forceNew);
@@ -1008,12 +1079,14 @@ async function createReactiveCameraDevice(rpcOrContext, initialCamera) {
1008
1079
  sourceId,
1009
1080
  fetchedAt: result.stamp
1010
1081
  };
1011
- setSnapshot(initialCamera._id, result.data, result.fetchedAt, result.fetchedAt ?? requestedAt, stamp);
1082
+ const fetchedAt = replyTime(result, requestedAt);
1083
+ setSnapshot(initialCamera._id, result.data, fetchedAt, fetchedAt ?? requestedAt, stamp);
1012
1084
  return result.data;
1013
1085
  }
1014
1086
  return;
1015
1087
  } finally {
1016
1088
  snapshotLoading.value = false;
1089
+ endSnapshotRefresh(initialCamera._id, forceNew);
1017
1090
  }
1018
1091
  }
1019
1092
  async function requestSnapshot(sourceId, forceNew) {
@@ -1139,7 +1212,7 @@ async function createReactiveCameraDevice(rpcOrContext, initialCamera) {
1139
1212
  break;
1140
1213
  case "snapshot:updated": {
1141
1214
  const { sourceId, snapshot: data, fetchedAt } = event.data;
1142
- setSnapshot(initialCamera._id, data, fetchedAt, void 0, sourceId && fetchedAt !== void 0 ? {
1215
+ setSnapshot(initialCamera._id, data, toBrowserTime(fetchedAt), void 0, sourceId && fetchedAt !== void 0 ? {
1143
1216
  sourceId,
1144
1217
  fetchedAt
1145
1218
  } : void 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camera.ui/browser",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "camera.ui browser client",
5
5
  "author": "seydx (https://github.com/cameraui/clients)",
6
6
  "type": "module",