@camera.ui/browser 0.1.5 → 0.1.6

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,9 @@ 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 serverClockOffset = 0;
676
680
  function notify(cameraId) {
677
681
  subscribers.get(cameraId)?.forEach((cb) => cb());
678
682
  }
@@ -719,6 +723,25 @@ function isOutdated(settings, fetchedAt) {
719
723
  function deferRevoke(url) {
720
724
  setTimeout(() => URL.revokeObjectURL(url), REVOKE_DELAY_MS);
721
725
  }
726
+ function startSnapshotRefresh(cameraId) {
727
+ clearTimeout(refreshLingers.get(cameraId));
728
+ refreshLingers.delete(cameraId);
729
+ refreshingCameras.add(cameraId);
730
+ }
731
+ function endSnapshotRefresh(cameraId) {
732
+ clearTimeout(refreshLingers.get(cameraId));
733
+ const linger = setTimeout(() => {
734
+ refreshLingers.delete(cameraId);
735
+ refreshingCameras.delete(cameraId);
736
+ }, REFRESH_LINGER_MS);
737
+ refreshLingers.set(cameraId, linger);
738
+ }
739
+ function rememberServerClock(browserTime, serverTime) {
740
+ serverClockOffset = browserTime - serverTime;
741
+ }
742
+ function toBrowserTime(serverTime) {
743
+ return serverTime === void 0 ? void 0 : serverTime + serverClockOffset;
744
+ }
722
745
  function isOlderThanHeld(cameraId, takenAt, stamp) {
723
746
  const held = stampCache.get(cameraId);
724
747
  if (stamp && held) {
@@ -739,6 +762,8 @@ function setSnapshot(cameraId, data, fetchedAt, takenAt, stamp) {
739
762
  urlCache.delete(cameraId);
740
763
  }
741
764
  snapshotCache.set(cameraId, data);
765
+ startSnapshotRefresh(cameraId);
766
+ endSnapshotRefresh(cameraId);
742
767
  }
743
768
  if (fetchedAt !== void 0) timestampCache.set(cameraId, fetchedAt);
744
769
  notify(cameraId);
@@ -769,17 +794,21 @@ function getSnapshotUrl(cameraId) {
769
794
  }
770
795
  function clearSnapshotCache() {
771
796
  for (const url of urlCache.values()) URL.revokeObjectURL(url);
797
+ for (const linger of refreshLingers.values()) clearTimeout(linger);
772
798
  urlCache.clear();
773
799
  snapshotCache.clear();
774
800
  timestampCache.clear();
775
801
  takenAtCache.clear();
776
802
  stampCache.clear();
803
+ refreshLingers.clear();
804
+ refreshingCameras.clear();
805
+ serverClockOffset = 0;
777
806
  }
778
807
  function useSnapshot(cameraIdOrName) {
779
808
  const cameraUi = useCameraUi();
780
809
  const { isConnected } = cameraUi;
781
810
  const deviceManager = useDeviceManager();
782
- const { onTabVisible } = useTabVisibility();
811
+ const { isVisible, onTabVisible } = useTabVisibility();
783
812
  const snapshot = shallowRef();
784
813
  const _isLoading = ref(false);
785
814
  const initialLoadDone = ref(false);
@@ -799,6 +828,7 @@ function useSnapshot(cameraIdOrName) {
799
828
  if (!subscribers.has(cameraId)) subscribers.set(cameraId, /* @__PURE__ */ new Set());
800
829
  const updateFn = () => {
801
830
  snapshot.value = snapshotCache.get(cameraId);
831
+ armViewRefresh(cameraId);
802
832
  };
803
833
  subscribers.get(cameraId).add(updateFn);
804
834
  return () => {
@@ -809,6 +839,7 @@ function useSnapshot(cameraIdOrName) {
809
839
  let heldDeviceId;
810
840
  let heldDevice;
811
841
  let pendingHoldId;
842
+ let viewRefreshTimer;
812
843
  const holdRetry = createRetryTimer();
813
844
  let disposed = false;
814
845
  function currentId() {
@@ -829,6 +860,7 @@ function useSnapshot(cameraIdOrName) {
829
860
  heldDeviceId = id;
830
861
  heldDevice = device;
831
862
  cancelHoldRetry();
863
+ armViewRefresh(id);
832
864
  } catch {
833
865
  if (currentId() === id) scheduleHoldRetry(id);
834
866
  } finally {
@@ -849,12 +881,32 @@ function useSnapshot(cameraIdOrName) {
849
881
  holdRetry.reset();
850
882
  }
851
883
  function releaseHeldDevice() {
884
+ clearTimeout(viewRefreshTimer);
852
885
  if (heldDeviceId) {
853
886
  if (heldDevice) releaseCameraDevice(heldDevice);
854
887
  heldDeviceId = void 0;
855
888
  heldDevice = void 0;
856
889
  }
857
890
  }
891
+ function refreshIfOutdated(id) {
892
+ if (!isConnected.value || !heldDevice || heldDeviceId !== id) return;
893
+ if (!isOutdated(heldDevice.camera.value.snapshotSettings, timestampCache.get(id))) return;
894
+ fetchSnapshotShared(deviceManager, id).catch(() => void 0);
895
+ }
896
+ function armViewRefresh(id) {
897
+ clearTimeout(viewRefreshTimer);
898
+ const settings = heldDevice?.camera.value.snapshotSettings;
899
+ if (heldDeviceId !== id || settings?.mode !== "onView") return;
900
+ const maxAgeMs = settings.maxAge * 1e3;
901
+ const leftMs = (timestampCache.get(id) ?? 0) + maxAgeMs - Date.now();
902
+ viewRefreshTimer = setTimeout(() => {
903
+ refreshOnView(id);
904
+ }, leftMs > 0 ? leftMs : maxAgeMs);
905
+ }
906
+ async function refreshOnView(id) {
907
+ if (isVisible.value && isConnected.value) await fetchSnapshotShared(deviceManager, id).catch(() => void 0);
908
+ armViewRefresh(id);
909
+ }
858
910
  function showCached(id) {
859
911
  const cached = snapshotCache.get(id);
860
912
  if (!cached) return false;
@@ -864,7 +916,10 @@ function useSnapshot(cameraIdOrName) {
864
916
  }
865
917
  async function loadSnapshot(id, skipCache = false) {
866
918
  if (!isConnected.value || !id) return;
867
- if (!skipCache && showCached(id)) return;
919
+ if (!skipCache && showCached(id)) {
920
+ refreshIfOutdated(id);
921
+ return;
922
+ }
868
923
  _isLoading.value = true;
869
924
  try {
870
925
  const result = await fetchSnapshotShared(deviceManager, id);
@@ -915,12 +970,7 @@ function useSnapshot(cameraIdOrName) {
915
970
  snapshot.value = void 0;
916
971
  }
917
972
  }, { 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
- });
973
+ onTabVisible(() => refreshIfOutdated(currentId()));
924
974
  tryOnScopeDispose(() => {
925
975
  disposed = true;
926
976
  cancelHoldRetry();
@@ -932,6 +982,7 @@ function useSnapshot(cameraIdOrName) {
932
982
  snapshotSrc,
933
983
  snapshotTimestamp,
934
984
  isLoading: computed(() => _isLoading.value || !initialLoadDone.value),
985
+ isRefreshing: computed(() => refreshingCameras.has(currentId())),
935
986
  refresh
936
987
  };
937
988
  }
@@ -999,6 +1050,7 @@ async function createReactiveCameraDevice(rpcOrContext, initialCamera) {
999
1050
  const hasPtz = computed(() => existingSensorTypes.value.has(SensorType.PTZ));
1000
1051
  async function fetchSnapshot(sourceId, forceNew) {
1001
1052
  snapshotLoading.value = true;
1053
+ startSnapshotRefresh(initialCamera._id);
1002
1054
  const requestedAt = Date.now();
1003
1055
  try {
1004
1056
  const result = await requestSnapshot(sourceId, forceNew);
@@ -1008,12 +1060,14 @@ async function createReactiveCameraDevice(rpcOrContext, initialCamera) {
1008
1060
  sourceId,
1009
1061
  fetchedAt: result.stamp
1010
1062
  };
1063
+ if (stamp && result.fetchedAt !== void 0) rememberServerClock(result.fetchedAt, stamp.fetchedAt);
1011
1064
  setSnapshot(initialCamera._id, result.data, result.fetchedAt, result.fetchedAt ?? requestedAt, stamp);
1012
1065
  return result.data;
1013
1066
  }
1014
1067
  return;
1015
1068
  } finally {
1016
1069
  snapshotLoading.value = false;
1070
+ endSnapshotRefresh(initialCamera._id);
1017
1071
  }
1018
1072
  }
1019
1073
  async function requestSnapshot(sourceId, forceNew) {
@@ -1139,7 +1193,7 @@ async function createReactiveCameraDevice(rpcOrContext, initialCamera) {
1139
1193
  break;
1140
1194
  case "snapshot:updated": {
1141
1195
  const { sourceId, snapshot: data, fetchedAt } = event.data;
1142
- setSnapshot(initialCamera._id, data, fetchedAt, void 0, sourceId && fetchedAt !== void 0 ? {
1196
+ setSnapshot(initialCamera._id, data, toBrowserTime(fetchedAt), void 0, sourceId && fetchedAt !== void 0 ? {
1143
1197
  sourceId,
1144
1198
  fetchedAt
1145
1199
  } : 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.6",
4
4
  "description": "camera.ui browser client",
5
5
  "author": "seydx (https://github.com/cameraui/clients)",
6
6
  "type": "module",