@camera.ui/browser 0.0.124 → 0.0.125
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.js +240 -95
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Logger } from "@camera.ui/logger";
|
|
2
|
-
import { computed, inject, markRaw, onBeforeUnmount, reactive, readonly, ref, shallowRef, toValue, watch } from "vue";
|
|
2
|
+
import { computed, effectScope, inject, markRaw, onBeforeUnmount, reactive, readonly, ref, shallowRef, toValue, watch } from "vue";
|
|
3
3
|
import { tryOnScopeDispose, useTimeoutFn, whenever } from "@vueuse/core";
|
|
4
4
|
import { SensorType } from "@camera.ui/sdk";
|
|
5
5
|
//#region src/utils/createDebouncedCache.ts
|
|
@@ -302,6 +302,8 @@ function useRpcSubscription(subscribeFn, options = {}) {
|
|
|
302
302
|
}
|
|
303
303
|
} finally {
|
|
304
304
|
pending = false;
|
|
305
|
+
const current = ctx.rpc.value;
|
|
306
|
+
if (!disposed && current && current !== rpc) bind(current);
|
|
305
307
|
}
|
|
306
308
|
}
|
|
307
309
|
function unbind() {
|
|
@@ -337,13 +339,15 @@ function defaultRetryDelayMs(attempt) {
|
|
|
337
339
|
}
|
|
338
340
|
function defaultShouldRetry(err) {
|
|
339
341
|
if (!(err instanceof Error)) return false;
|
|
342
|
+
const code = err.code;
|
|
343
|
+
if (code === "TIMEOUT" || code === "CONNECTION_CLOSED") return true;
|
|
340
344
|
const msg = err.message.toLowerCase();
|
|
341
345
|
if (msg.includes("not connected")) return true;
|
|
342
346
|
if (msg.includes("connection closed")) return true;
|
|
343
347
|
if (msg.includes("no responders")) return true;
|
|
344
348
|
if (msg.includes("connection refused")) return true;
|
|
345
349
|
if (msg.includes("socket")) return true;
|
|
346
|
-
if (msg.includes("timeout")) return true;
|
|
350
|
+
if (msg.includes("timed out") || msg.includes("timeout")) return true;
|
|
347
351
|
return false;
|
|
348
352
|
}
|
|
349
353
|
async function waitForRpc(rpcRef, timeoutMs, signal) {
|
|
@@ -417,6 +421,13 @@ function setSnapshot(cameraId, data) {
|
|
|
417
421
|
function getSnapshot(cameraId) {
|
|
418
422
|
return snapshotCache.get(cameraId);
|
|
419
423
|
}
|
|
424
|
+
function subscribeSnapshot(cameraId, cb) {
|
|
425
|
+
if (!subscribers.has(cameraId)) subscribers.set(cameraId, /* @__PURE__ */ new Set());
|
|
426
|
+
subscribers.get(cameraId).add(cb);
|
|
427
|
+
return () => {
|
|
428
|
+
subscribers.get(cameraId)?.delete(cb);
|
|
429
|
+
};
|
|
430
|
+
}
|
|
420
431
|
function getSnapshotUrl(cameraId) {
|
|
421
432
|
const buffer = snapshotCache.get(cameraId);
|
|
422
433
|
if (!buffer) return void 0;
|
|
@@ -464,13 +475,17 @@ function useSnapshot(cameraIdOrName) {
|
|
|
464
475
|
}
|
|
465
476
|
_isLoading.value = true;
|
|
466
477
|
try {
|
|
467
|
-
const device = await deviceManager
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
478
|
+
const device = await acquireCameraDevice(deviceManager, id);
|
|
479
|
+
try {
|
|
480
|
+
if (device) {
|
|
481
|
+
const result = await device.fetchSnapshot();
|
|
482
|
+
if (result) {
|
|
483
|
+
setSnapshot(id, result);
|
|
484
|
+
snapshot.value = result;
|
|
485
|
+
}
|
|
473
486
|
}
|
|
487
|
+
} finally {
|
|
488
|
+
if (device) releaseCameraDevice(id);
|
|
474
489
|
}
|
|
475
490
|
} catch {} finally {
|
|
476
491
|
_isLoading.value = false;
|
|
@@ -487,13 +502,17 @@ function useSnapshot(cameraIdOrName) {
|
|
|
487
502
|
if (isCameraDisabled(cameraIdName)) return;
|
|
488
503
|
_isLoading.value = true;
|
|
489
504
|
try {
|
|
490
|
-
const device = await deviceManager
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
505
|
+
const device = await acquireCameraDevice(deviceManager, id);
|
|
506
|
+
try {
|
|
507
|
+
if (device) {
|
|
508
|
+
const result = await device.fetchSnapshot(void 0, true);
|
|
509
|
+
if (result) {
|
|
510
|
+
setSnapshot(id, result);
|
|
511
|
+
snapshot.value = result;
|
|
512
|
+
}
|
|
496
513
|
}
|
|
514
|
+
} finally {
|
|
515
|
+
if (device) releaseCameraDevice(id);
|
|
497
516
|
}
|
|
498
517
|
} catch {} finally {
|
|
499
518
|
_isLoading.value = false;
|
|
@@ -538,9 +557,12 @@ async function createReactiveCameraDevice(rpcOrContext, initialCamera) {
|
|
|
538
557
|
const camera = shallowRef(initialCamera);
|
|
539
558
|
const connected = ref(false);
|
|
540
559
|
const frameWorkerConnected = ref(false);
|
|
541
|
-
const snapshot =
|
|
560
|
+
const snapshot = shallowRef(getSnapshot(initialCamera._id));
|
|
542
561
|
const snapshotLoading = ref(false);
|
|
543
562
|
const sensorStates = ref({});
|
|
563
|
+
const unsubscribeSnapshot = subscribeSnapshot(initialCamera._id, () => {
|
|
564
|
+
snapshot.value = getSnapshot(initialCamera._id);
|
|
565
|
+
});
|
|
544
566
|
const name = computed(() => camera.value.name);
|
|
545
567
|
const room = computed(() => camera.value.room);
|
|
546
568
|
const nativeId = computed(() => camera.value.nativeId);
|
|
@@ -643,6 +665,7 @@ async function createReactiveCameraDevice(rpcOrContext, initialCamera) {
|
|
|
643
665
|
closeSubscription = void 0;
|
|
644
666
|
closeSensorSubscription?.();
|
|
645
667
|
closeSensorSubscription = void 0;
|
|
668
|
+
unsubscribeSnapshot();
|
|
646
669
|
}
|
|
647
670
|
async function fetchSnapshotFn(sourceId, forceNew) {
|
|
648
671
|
const useForSnapshotSource = sources.value.find((s) => s.useForSnapshot);
|
|
@@ -715,6 +738,27 @@ function clearCameraCache() {
|
|
|
715
738
|
cameraCache.clear();
|
|
716
739
|
pendingLoads$1.clear();
|
|
717
740
|
}
|
|
741
|
+
async function acquireCameraDevice(deviceManager, id) {
|
|
742
|
+
if (cameraCache.has(id)) return cameraCache.acquire(id, () => {
|
|
743
|
+
throw new Error("Should not create - already cached");
|
|
744
|
+
});
|
|
745
|
+
const pending = pendingLoads$1.get(id);
|
|
746
|
+
if (pending) {
|
|
747
|
+
const device = await pending;
|
|
748
|
+
return device ? cameraCache.acquire(id, () => device) : void 0;
|
|
749
|
+
}
|
|
750
|
+
const loadPromise = deviceManager.getCamera(id);
|
|
751
|
+
pendingLoads$1.set(id, loadPromise);
|
|
752
|
+
try {
|
|
753
|
+
const device = await loadPromise;
|
|
754
|
+
return device ? cameraCache.acquire(id, () => device) : void 0;
|
|
755
|
+
} finally {
|
|
756
|
+
pendingLoads$1.delete(id);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function releaseCameraDevice(id) {
|
|
760
|
+
cameraCache.release(id);
|
|
761
|
+
}
|
|
718
762
|
function reconnectAllCameraDevices() {
|
|
719
763
|
cameraCache.forEachValue((device) => {
|
|
720
764
|
device.reconnect().catch(() => {});
|
|
@@ -749,7 +793,7 @@ function useCameraById(cameraIdOrName) {
|
|
|
749
793
|
_isLoading.value = true;
|
|
750
794
|
try {
|
|
751
795
|
const device = await pending;
|
|
752
|
-
if (device && cameraCache.has(id)) {
|
|
796
|
+
if (device && cameraCache.has(id) && toValue(cameraIdOrName) === id) {
|
|
753
797
|
const cached = cameraCache.acquire(id, () => device);
|
|
754
798
|
currentCameraId = id;
|
|
755
799
|
camera.value = cached;
|
|
@@ -770,6 +814,10 @@ function useCameraById(cameraIdOrName) {
|
|
|
770
814
|
const device = await loadPromise;
|
|
771
815
|
if (device) {
|
|
772
816
|
cameraCache.acquire(id, () => device);
|
|
817
|
+
if (toValue(cameraIdOrName) !== id) {
|
|
818
|
+
cameraCache.release(id);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
773
821
|
currentCameraId = id;
|
|
774
822
|
camera.value = device;
|
|
775
823
|
}
|
|
@@ -862,7 +910,7 @@ function usePlugin(pluginName) {
|
|
|
862
910
|
if (pending) {
|
|
863
911
|
_isLoading.value = true;
|
|
864
912
|
try {
|
|
865
|
-
if (await pending) {
|
|
913
|
+
if (await pending && toValue(pluginName) === name) {
|
|
866
914
|
const cachedAfterPending = acquirePlugin(name);
|
|
867
915
|
if (cachedAfterPending) {
|
|
868
916
|
currentPluginName = name;
|
|
@@ -895,6 +943,10 @@ function usePlugin(pluginName) {
|
|
|
895
943
|
const result = await loadPromise;
|
|
896
944
|
if (result) {
|
|
897
945
|
pluginCache.acquire(name, () => result);
|
|
946
|
+
if (toValue(pluginName) !== name) {
|
|
947
|
+
pluginCache.release(name);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
898
950
|
currentPluginName = name;
|
|
899
951
|
plugin.value = result.proxy;
|
|
900
952
|
contract.value = result.contract;
|
|
@@ -1121,19 +1173,28 @@ function createSensorManager(rpcOrContext, cameraId, sensorSubjectNamespace, sen
|
|
|
1121
1173
|
for (const msg of queued) handleGlobalSensorEvent(msg);
|
|
1122
1174
|
initialized.value = true;
|
|
1123
1175
|
}
|
|
1176
|
+
function launchInit() {
|
|
1177
|
+
const p = doInit().finally(() => {
|
|
1178
|
+
if (initPromise === p) initPromise = void 0;
|
|
1179
|
+
});
|
|
1180
|
+
initPromise = p;
|
|
1181
|
+
return p;
|
|
1182
|
+
}
|
|
1124
1183
|
async function ensureInitialized() {
|
|
1125
1184
|
if (initialized.value) return;
|
|
1126
|
-
if (!initPromise)
|
|
1127
|
-
initPromise = void 0;
|
|
1128
|
-
});
|
|
1185
|
+
if (!initPromise) launchInit();
|
|
1129
1186
|
return initPromise;
|
|
1130
1187
|
}
|
|
1131
1188
|
async function reconnect() {
|
|
1132
1189
|
initialized.value = false;
|
|
1133
|
-
if (
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1190
|
+
if (initPromise) {
|
|
1191
|
+
const p = initPromise.catch(() => {}).then(() => doInit()).finally(() => {
|
|
1192
|
+
if (initPromise === p) initPromise = void 0;
|
|
1193
|
+
});
|
|
1194
|
+
initPromise = p;
|
|
1195
|
+
return p;
|
|
1196
|
+
}
|
|
1197
|
+
return launchInit();
|
|
1137
1198
|
}
|
|
1138
1199
|
function close() {
|
|
1139
1200
|
globalUnsubscribe?.();
|
|
@@ -1830,7 +1891,7 @@ function isContext(input) {
|
|
|
1830
1891
|
}
|
|
1831
1892
|
function makeContextFromTransport(input) {
|
|
1832
1893
|
const { natsTransport, target, wsTransport } = input;
|
|
1833
|
-
let hasBeenConnected =
|
|
1894
|
+
let hasBeenConnected = natsTransport.getClient() !== null;
|
|
1834
1895
|
const rpc = shallowRef(natsTransport.getClient() ?? void 0);
|
|
1835
1896
|
const isConnected = ref(natsTransport.getClient()?.isConnected ?? false);
|
|
1836
1897
|
const error = ref(void 0);
|
|
@@ -2263,6 +2324,7 @@ function createMSEHandler(options) {
|
|
|
2263
2324
|
const { videoElement, onReady, onFirstData, onError, signal } = options;
|
|
2264
2325
|
let mediaSource = null;
|
|
2265
2326
|
let sourceBuffer = null;
|
|
2327
|
+
let objectUrl = null;
|
|
2266
2328
|
let isReady = false;
|
|
2267
2329
|
let hasFirstData = false;
|
|
2268
2330
|
let pendingBuffer = new Uint8Array(STREAM_CONFIG.MSE.BUFFER_SIZE);
|
|
@@ -2287,7 +2349,8 @@ function createMSEHandler(options) {
|
|
|
2287
2349
|
videoElement.disableRemotePlayback = true;
|
|
2288
2350
|
videoElement.srcObject = mediaSource;
|
|
2289
2351
|
} else {
|
|
2290
|
-
|
|
2352
|
+
objectUrl = URL.createObjectURL(mediaSource);
|
|
2353
|
+
videoElement.src = objectUrl;
|
|
2291
2354
|
videoElement.srcObject = null;
|
|
2292
2355
|
}
|
|
2293
2356
|
return filterSupportedCodecs(supportedCodecs, MediaSourceConstructor.isTypeSupported.bind(MediaSourceConstructor));
|
|
@@ -2327,7 +2390,9 @@ function createMSEHandler(options) {
|
|
|
2327
2390
|
const data = pendingBuffer.slice(0, pendingLength);
|
|
2328
2391
|
sourceBuffer.appendBuffer(data);
|
|
2329
2392
|
pendingLength = 0;
|
|
2330
|
-
} catch {
|
|
2393
|
+
} catch (err) {
|
|
2394
|
+
if (!recoverFromQuota(err)) onError(err instanceof Error ? err : new Error(String(err)));
|
|
2395
|
+
}
|
|
2331
2396
|
if (!sourceBuffer.updating && sourceBuffer.buffered?.length) {
|
|
2332
2397
|
const end = sourceBuffer.buffered.end(sourceBuffer.buffered.length - 1);
|
|
2333
2398
|
const start = end - STREAM_CONFIG.MSE.BUFFER_WINDOW;
|
|
@@ -2359,11 +2424,38 @@ function createMSEHandler(options) {
|
|
|
2359
2424
|
}
|
|
2360
2425
|
if (sourceBuffer.updating || pendingLength > 0) {
|
|
2361
2426
|
const bytes = new Uint8Array(data);
|
|
2427
|
+
if (pendingLength + bytes.byteLength > pendingBuffer.byteLength) {
|
|
2428
|
+
pendingLength = 0;
|
|
2429
|
+
onError(/* @__PURE__ */ new Error("MSE pending buffer overflow"));
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2362
2432
|
pendingBuffer.set(bytes, pendingLength);
|
|
2363
2433
|
pendingLength += bytes.byteLength;
|
|
2364
2434
|
} else try {
|
|
2365
2435
|
sourceBuffer.appendBuffer(data);
|
|
2366
|
-
} catch {
|
|
2436
|
+
} catch (err) {
|
|
2437
|
+
if (recoverFromQuota(err)) {
|
|
2438
|
+
const bytes = new Uint8Array(data);
|
|
2439
|
+
if (bytes.byteLength <= pendingBuffer.byteLength) {
|
|
2440
|
+
pendingBuffer.set(bytes, 0);
|
|
2441
|
+
pendingLength = bytes.byteLength;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
function recoverFromQuota(err) {
|
|
2447
|
+
if (err?.name !== "QuotaExceededError") return false;
|
|
2448
|
+
if (!sourceBuffer || sourceBuffer.updating || !sourceBuffer.buffered?.length) return false;
|
|
2449
|
+
try {
|
|
2450
|
+
const start0 = sourceBuffer.buffered.start(0);
|
|
2451
|
+
const end = sourceBuffer.buffered.end(sourceBuffer.buffered.length - 1);
|
|
2452
|
+
const evictTo = Math.max(start0 + 1, end - STREAM_CONFIG.MSE.BUFFER_WINDOW);
|
|
2453
|
+
if (evictTo <= start0) return false;
|
|
2454
|
+
sourceBuffer.remove(start0, evictTo);
|
|
2455
|
+
return true;
|
|
2456
|
+
} catch {
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2367
2459
|
}
|
|
2368
2460
|
function close() {
|
|
2369
2461
|
if (sourceBuffer) {
|
|
@@ -2377,6 +2469,10 @@ function createMSEHandler(options) {
|
|
|
2377
2469
|
mediaSource.endOfStream();
|
|
2378
2470
|
} catch {}
|
|
2379
2471
|
mediaSource = null;
|
|
2472
|
+
if (objectUrl) {
|
|
2473
|
+
URL.revokeObjectURL(objectUrl);
|
|
2474
|
+
objectUrl = null;
|
|
2475
|
+
}
|
|
2380
2476
|
isReady = false;
|
|
2381
2477
|
hasFirstData = false;
|
|
2382
2478
|
pendingLength = 0;
|
|
@@ -2592,6 +2688,7 @@ var StreamConnection = class {
|
|
|
2592
2688
|
options;
|
|
2593
2689
|
connectionGeneration = 0;
|
|
2594
2690
|
abortController = new AbortController();
|
|
2691
|
+
scope = effectScope(true);
|
|
2595
2692
|
offTabVisible;
|
|
2596
2693
|
offTabPaused;
|
|
2597
2694
|
wasPausedByVisibility = false;
|
|
@@ -2618,6 +2715,8 @@ var StreamConnection = class {
|
|
|
2618
2715
|
stopWsConnectTimeout;
|
|
2619
2716
|
startConnectTimeout;
|
|
2620
2717
|
stopConnectTimeout;
|
|
2718
|
+
startMseConnectTimeout;
|
|
2719
|
+
stopMseConnectTimeout;
|
|
2621
2720
|
startReconnectTimeout;
|
|
2622
2721
|
stopReconnectTimeout;
|
|
2623
2722
|
constructor(options) {
|
|
@@ -2651,55 +2750,62 @@ var StreamConnection = class {
|
|
|
2651
2750
|
if (this.requestedMode.value === "auto") return this.activeMode.value;
|
|
2652
2751
|
return this.requestedMode.value;
|
|
2653
2752
|
});
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
this.
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
this.
|
|
2670
|
-
}
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
this.
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
}
|
|
2697
|
-
this.
|
|
2698
|
-
|
|
2753
|
+
this.scope.run(() => {
|
|
2754
|
+
const { onTabPaused, onTabVisible } = useTabVisibility();
|
|
2755
|
+
const wsConnectTimeout = useTimeoutFn(() => {
|
|
2756
|
+
if (this.wsHandle?.readyState === WebSocket.CONNECTING) {
|
|
2757
|
+
this.disconnectWebSocket();
|
|
2758
|
+
if (!this.abortController.signal.aborted && this.status.value !== "closed") this.restart();
|
|
2759
|
+
}
|
|
2760
|
+
}, STREAM_CONFIG.WEBRTC.WS_CONNECT_TIMEOUT, { immediate: false });
|
|
2761
|
+
const connectTimeout = useTimeoutFn(() => {
|
|
2762
|
+
if (this.webrtcHandler && !this.webrtcHandler.isConnected) if (this.requestedMode.value === "auto") {
|
|
2763
|
+
this.webrtcHandler.close();
|
|
2764
|
+
this.webrtcHandler = void 0;
|
|
2765
|
+
this.activeMode.value = "mse";
|
|
2766
|
+
if (this.mseHandler?.isReady) this.status.value = "connected";
|
|
2767
|
+
else this.startMSE();
|
|
2768
|
+
} else this.restart();
|
|
2769
|
+
}, STREAM_CONFIG.WEBRTC.CONNECT_TIMEOUT, { immediate: false });
|
|
2770
|
+
const mseConnectTimeout = useTimeoutFn(() => {
|
|
2771
|
+
if (this.abortController.signal.aborted || this.status.value === "connected" || this.status.value === "closed") return;
|
|
2772
|
+
if (this.mseHandler && !this.mseHandler.isReady) this.restart();
|
|
2773
|
+
}, STREAM_CONFIG.WEBRTC.CONNECT_TIMEOUT, { immediate: false });
|
|
2774
|
+
const reconnectTimeout = useTimeoutFn(() => {
|
|
2775
|
+
if (!this.abortController.signal.aborted) this.restart();
|
|
2776
|
+
}, STREAM_CONFIG.WEBRTC.RECONNECT_DELAY, { immediate: false });
|
|
2777
|
+
this.startWsConnectTimeout = wsConnectTimeout.start;
|
|
2778
|
+
this.stopWsConnectTimeout = wsConnectTimeout.stop;
|
|
2779
|
+
this.startConnectTimeout = connectTimeout.start;
|
|
2780
|
+
this.stopConnectTimeout = connectTimeout.stop;
|
|
2781
|
+
this.startMseConnectTimeout = mseConnectTimeout.start;
|
|
2782
|
+
this.stopMseConnectTimeout = mseConnectTimeout.stop;
|
|
2783
|
+
this.startReconnectTimeout = reconnectTimeout.start;
|
|
2784
|
+
this.stopReconnectTimeout = reconnectTimeout.stop;
|
|
2785
|
+
this.setupWatchers();
|
|
2786
|
+
this.offTabPaused = onTabPaused(() => {
|
|
2787
|
+
log.debug(`onTabPaused fired — status=${this.status.value}, isReady=${this.isReady.value}, target=${!!this.target.value}`);
|
|
2788
|
+
if (this.status.value === "idle" || this.status.value === "closed") {
|
|
2789
|
+
log.debug(`onTabPaused — already in ${this.status.value}, skipping stop()`);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
this.wasPausedByVisibility = true;
|
|
2793
|
+
this.stop();
|
|
2794
|
+
log.debug("onTabPaused — stop() done, wasPausedByVisibility=true");
|
|
2795
|
+
});
|
|
2796
|
+
this.offTabVisible = onTabVisible(() => {
|
|
2797
|
+
log.debug(`onTabVisible fired — wasPausedByVisibility=${this.wasPausedByVisibility}, status=${this.status.value}, isReady=${this.isReady.value}, target=${!!this.target.value}`);
|
|
2798
|
+
if (!this.wasPausedByVisibility) {
|
|
2799
|
+
log.debug("onTabVisible — not paused by visibility, no-op");
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
this.wasPausedByVisibility = false;
|
|
2803
|
+
this.startWhenReady();
|
|
2804
|
+
});
|
|
2805
|
+
if (autoStart) whenever(this.isReady, () => {
|
|
2806
|
+
if (this.status.value === "idle" || this.status.value === "closed") this.start();
|
|
2807
|
+
}, { immediate: true });
|
|
2699
2808
|
});
|
|
2700
|
-
if (autoStart) whenever(this.isReady, () => {
|
|
2701
|
-
if (this.status.value === "idle" || this.status.value === "closed") this.start();
|
|
2702
|
-
}, { immediate: true });
|
|
2703
2809
|
}
|
|
2704
2810
|
async start() {
|
|
2705
2811
|
log.debug(`start() called — status=${this.status.value}, isReady=${this.isReady.value}`);
|
|
@@ -2810,7 +2916,14 @@ var StreamConnection = class {
|
|
|
2810
2916
|
this.offTabVisible = void 0;
|
|
2811
2917
|
this.offTabPaused?.();
|
|
2812
2918
|
this.offTabPaused = void 0;
|
|
2919
|
+
const video = this.videoElement.value;
|
|
2920
|
+
if (video) {
|
|
2921
|
+
if (this.onVideoPauseBound) video.removeEventListener("pause", this.onVideoPauseBound);
|
|
2922
|
+
if (this.onVideoPlayBound) video.removeEventListener("play", this.onVideoPlayBound);
|
|
2923
|
+
if (this.onVideoResizeBound) video.removeEventListener("resize", this.onVideoResizeBound);
|
|
2924
|
+
}
|
|
2813
2925
|
this.stop();
|
|
2926
|
+
this.scope.stop();
|
|
2814
2927
|
}
|
|
2815
2928
|
async restart() {
|
|
2816
2929
|
this.status.value = "reconnecting";
|
|
@@ -3046,7 +3159,12 @@ var StreamConnection = class {
|
|
|
3046
3159
|
handleWsMessage(ev) {
|
|
3047
3160
|
if (this.abortController.signal.aborted) return;
|
|
3048
3161
|
if (typeof ev.data === "string") {
|
|
3049
|
-
|
|
3162
|
+
let msg;
|
|
3163
|
+
try {
|
|
3164
|
+
msg = JSON.parse(ev.data);
|
|
3165
|
+
} catch {
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3050
3168
|
this.handleMessage(msg);
|
|
3051
3169
|
} else if (this.mseHandler) {
|
|
3052
3170
|
this.mseHandler.appendBuffer(ev.data);
|
|
@@ -3106,12 +3224,15 @@ var StreamConnection = class {
|
|
|
3106
3224
|
if (this.requestedMode.value === "auto" && this.mseHandler) {
|
|
3107
3225
|
this.mseHandler.close();
|
|
3108
3226
|
this.mseHandler = void 0;
|
|
3227
|
+
this.stopMseConnectTimeout();
|
|
3109
3228
|
}
|
|
3110
3229
|
}
|
|
3111
3230
|
handleWebRTCDisconnected() {
|
|
3112
3231
|
if (this.status.value !== "closed" && !this.abortController.signal.aborted) {
|
|
3113
3232
|
this.status.value = "reconnecting";
|
|
3114
3233
|
if (this.requestedMode.value === "auto" && this.mseHandler?.isReady) {
|
|
3234
|
+
this.webrtcHandler?.close();
|
|
3235
|
+
this.webrtcHandler = void 0;
|
|
3115
3236
|
this.activeMode.value = "mse";
|
|
3116
3237
|
this.status.value = "connected";
|
|
3117
3238
|
} else this.restart();
|
|
@@ -3121,6 +3242,8 @@ var StreamConnection = class {
|
|
|
3121
3242
|
if (this.abortController.signal.aborted) return;
|
|
3122
3243
|
this.stopConnectTimeout();
|
|
3123
3244
|
if (this.requestedMode.value === "auto") {
|
|
3245
|
+
this.webrtcHandler?.close();
|
|
3246
|
+
this.webrtcHandler = void 0;
|
|
3124
3247
|
this.activeMode.value = "mse";
|
|
3125
3248
|
if (!this.mseHandler?.isReady) this.startMSE();
|
|
3126
3249
|
else this.status.value = "connected";
|
|
@@ -3130,6 +3253,7 @@ var StreamConnection = class {
|
|
|
3130
3253
|
}
|
|
3131
3254
|
}
|
|
3132
3255
|
startMSE() {
|
|
3256
|
+
if (this.mseHandler) return;
|
|
3133
3257
|
const video = this.videoElement.value;
|
|
3134
3258
|
if (this.abortController.signal.aborted || !video) return;
|
|
3135
3259
|
this.mseHandler = createMSEHandler({
|
|
@@ -3141,17 +3265,24 @@ var StreamConnection = class {
|
|
|
3141
3265
|
if (this.requestedMode.value !== "auto") {
|
|
3142
3266
|
this.error.value = err;
|
|
3143
3267
|
this.status.value = "error";
|
|
3268
|
+
} else if (this.activeMode.value === "mse" && this.status.value === "connected") {
|
|
3269
|
+
log.debug("MSE error while active in auto mode — restarting:", err);
|
|
3270
|
+
this.restart();
|
|
3144
3271
|
}
|
|
3145
3272
|
}
|
|
3146
3273
|
});
|
|
3147
3274
|
const codecs = this.mseHandler.setup();
|
|
3148
|
-
if (codecs)
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3275
|
+
if (codecs) {
|
|
3276
|
+
this.sendWsMessage({
|
|
3277
|
+
type: "mse",
|
|
3278
|
+
value: codecs
|
|
3279
|
+
});
|
|
3280
|
+
this.startMseConnectTimeout();
|
|
3281
|
+
}
|
|
3152
3282
|
}
|
|
3153
3283
|
handleMSEReady() {
|
|
3154
3284
|
if (this.abortController.signal.aborted) return;
|
|
3285
|
+
this.stopMseConnectTimeout();
|
|
3155
3286
|
if (this.requestedMode.value === "auto") {
|
|
3156
3287
|
if (!this.webrtcHandler?.isConnected) {
|
|
3157
3288
|
this.activeMode.value = "mse";
|
|
@@ -3221,14 +3352,9 @@ var StreamConnection = class {
|
|
|
3221
3352
|
this.abortController.abort();
|
|
3222
3353
|
this.stopConnectTimeout();
|
|
3223
3354
|
this.stopWsConnectTimeout();
|
|
3355
|
+
this.stopMseConnectTimeout();
|
|
3224
3356
|
this.stopReconnectTimeout();
|
|
3225
3357
|
this.stopMseMonitor();
|
|
3226
|
-
const videoEl = this.videoElement.value;
|
|
3227
|
-
if (videoEl) {
|
|
3228
|
-
if (this.onVideoPauseBound) videoEl.removeEventListener("pause", this.onVideoPauseBound);
|
|
3229
|
-
if (this.onVideoPlayBound) videoEl.removeEventListener("play", this.onVideoPlayBound);
|
|
3230
|
-
if (this.onVideoResizeBound) videoEl.removeEventListener("resize", this.onVideoResizeBound);
|
|
3231
|
-
}
|
|
3232
3358
|
this.lastMediaStream = null;
|
|
3233
3359
|
this.webrtcHandler?.close();
|
|
3234
3360
|
this.webrtcHandler = void 0;
|
|
@@ -3293,6 +3419,9 @@ var StreamManager = class {
|
|
|
3293
3419
|
has(cameraName) {
|
|
3294
3420
|
return this.streams.has(cameraName);
|
|
3295
3421
|
}
|
|
3422
|
+
getRefCount(cameraName) {
|
|
3423
|
+
return this.streams.get(cameraName)?.refCount ?? 0;
|
|
3424
|
+
}
|
|
3296
3425
|
get(cameraName) {
|
|
3297
3426
|
return this.streams.get(cameraName);
|
|
3298
3427
|
}
|
|
@@ -3367,7 +3496,7 @@ var StreamManager = class {
|
|
|
3367
3496
|
for (const timer of this.releaseTimers.values()) clearTimeout(timer);
|
|
3368
3497
|
this.releaseTimers.clear();
|
|
3369
3498
|
for (const [cameraName, entry] of this.streams) {
|
|
3370
|
-
entry.stream
|
|
3499
|
+
destroyStream(entry.stream);
|
|
3371
3500
|
this.streams.delete(cameraName);
|
|
3372
3501
|
}
|
|
3373
3502
|
}
|
|
@@ -3406,12 +3535,17 @@ var StreamManager = class {
|
|
|
3406
3535
|
const entry = this.streams.get(cameraName);
|
|
3407
3536
|
if (!entry) return;
|
|
3408
3537
|
if (entry.refCount <= 0) {
|
|
3409
|
-
entry.stream
|
|
3538
|
+
destroyStream(entry.stream);
|
|
3410
3539
|
this.streams.delete(cameraName);
|
|
3411
3540
|
}
|
|
3412
3541
|
this.releaseTimers.delete(cameraName);
|
|
3413
3542
|
}
|
|
3414
3543
|
};
|
|
3544
|
+
function destroyStream(stream) {
|
|
3545
|
+
const s = stream;
|
|
3546
|
+
if (typeof s.destroy === "function") s.destroy();
|
|
3547
|
+
else s.stop();
|
|
3548
|
+
}
|
|
3415
3549
|
var streamManager = new StreamManager();
|
|
3416
3550
|
//#endregion
|
|
3417
3551
|
//#region src/composables/useFullscreen.ts
|
|
@@ -3552,13 +3686,15 @@ function useCameraStream(options) {
|
|
|
3552
3686
|
const cam = cameraGetter.value;
|
|
3553
3687
|
return typeof cam === "string" ? cam : cam.name.value;
|
|
3554
3688
|
});
|
|
3555
|
-
const { camera: cameraDeviceFromLookup, isLoading:
|
|
3689
|
+
const { camera: cameraDeviceFromLookup, isLoading: lookupLoading } = useCameraById(computed(() => isCameraString.value ? cameraName.value : ""));
|
|
3690
|
+
const cameraDeviceLoading = computed(() => isCameraString.value && lookupLoading.value);
|
|
3556
3691
|
const resolvedCameraDevice = computed(() => {
|
|
3557
3692
|
if (isCameraString.value) return cameraDeviceFromLookup.value;
|
|
3558
3693
|
return cameraGetter.value;
|
|
3559
3694
|
});
|
|
3560
3695
|
let startDelayTimer;
|
|
3561
3696
|
let ownedConnection;
|
|
3697
|
+
let registeredCamName;
|
|
3562
3698
|
const containerElement = shallowRef();
|
|
3563
3699
|
const fullscreenElement = shallowRef();
|
|
3564
3700
|
const videoElement = shallowRef();
|
|
@@ -3601,7 +3737,13 @@ function useCameraStream(options) {
|
|
|
3601
3737
|
onStreamStart: () => {
|
|
3602
3738
|
if (!isCameraDisabled.value) currentStream.value?.start();
|
|
3603
3739
|
},
|
|
3604
|
-
onStreamStop: () =>
|
|
3740
|
+
onStreamStop: () => {
|
|
3741
|
+
if (isUsingCachedStream.value) {
|
|
3742
|
+
const camName = registeredCamName ?? cameraName.value;
|
|
3743
|
+
if (camName && streamManager.getRefCount(camName) > 1) return;
|
|
3744
|
+
}
|
|
3745
|
+
currentStream.value?.stop();
|
|
3746
|
+
},
|
|
3605
3747
|
isStreamPlaying: () => isPlaying.value
|
|
3606
3748
|
});
|
|
3607
3749
|
const { isFullscreen, toggle: toggleFullscreen } = useCuiFullscreen(fullscreenTarget);
|
|
@@ -3679,6 +3821,7 @@ function useCameraStream(options) {
|
|
|
3679
3821
|
const cached = streamManager.acquire(camName, containerElement);
|
|
3680
3822
|
if (cached) {
|
|
3681
3823
|
initialized.value = true;
|
|
3824
|
+
registeredCamName = camName;
|
|
3682
3825
|
isUsingCachedStream.value = true;
|
|
3683
3826
|
currentStream.value = cached.stream;
|
|
3684
3827
|
if (camDevice && cached.cameraDeviceRef) cached.cameraDeviceRef.value = camDevice;
|
|
@@ -3693,10 +3836,11 @@ function useCameraStream(options) {
|
|
|
3693
3836
|
videoElement.value = video;
|
|
3694
3837
|
cached.videoElementRef.value = video;
|
|
3695
3838
|
setupCachedStreamWatchers(cached.stream, video, camName);
|
|
3696
|
-
if (shouldAutoStart() && autoStartReady.value && !isCameraDisabled.value) if (cached.stream.activeMode.value !== "mse") attachCachedStream(cached, video, camName);
|
|
3839
|
+
if (shouldAutoStart() && autoStartReady.value && activityModeManager.mode.value === "always-on" && !isCameraDisabled.value) if (cached.stream.activeMode.value !== "mse") attachCachedStream(cached, video, camName);
|
|
3697
3840
|
else video.play().catch(() => {});
|
|
3698
3841
|
} else if (camDevice) {
|
|
3699
3842
|
initialized.value = true;
|
|
3843
|
+
registeredCamName = camName;
|
|
3700
3844
|
isUsingCachedStream.value = false;
|
|
3701
3845
|
currentStream.value = ownedStream;
|
|
3702
3846
|
const video = createVideoElement();
|
|
@@ -3846,11 +3990,12 @@ function useCameraStream(options) {
|
|
|
3846
3990
|
if (video) video.muted = true;
|
|
3847
3991
|
for (const stopFn of cleanupFns) stopFn();
|
|
3848
3992
|
cleanupFns.length = 0;
|
|
3849
|
-
if (isolated) ownedConnection?.
|
|
3993
|
+
if (isolated) ownedConnection?.destroy();
|
|
3850
3994
|
else {
|
|
3851
|
-
const camName =
|
|
3995
|
+
const camName = registeredCamName;
|
|
3852
3996
|
const video = videoElement.value;
|
|
3853
3997
|
if (camName && initialized.value) streamManager.release(camName, video, containerElement);
|
|
3998
|
+
if (!initialized.value || currentStream.value !== ownedStream) ownedConnection?.destroy();
|
|
3854
3999
|
}
|
|
3855
4000
|
activityModeManager.dispose();
|
|
3856
4001
|
}
|
|
@@ -3955,7 +4100,7 @@ function useCameraStream(options) {
|
|
|
3955
4100
|
autoStartReady
|
|
3956
4101
|
], () => {
|
|
3957
4102
|
if (!initialized.value) initialize();
|
|
3958
|
-
else if (autoStartReady.value && shouldAutoStart() && !isPlaying.value && status.value === "idle" && !isCameraDisabled.value) currentStream.value?.start();
|
|
4103
|
+
else if (autoStartReady.value && shouldAutoStart() && activityModeManager.mode.value === "always-on" && !isPlaying.value && status.value === "idle" && !isCameraDisabled.value) currentStream.value?.start();
|
|
3959
4104
|
}, { immediate: true });
|
|
3960
4105
|
watch(isCameraDisabled, (disabled, wasDisabled) => {
|
|
3961
4106
|
if (!initialized.value) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camera.ui/browser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.125",
|
|
4
4
|
"description": "camera.ui browser client",
|
|
5
5
|
"author": "seydx (https://github.com/cameraui/clients)",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@camera.ui/logger": ">=0.0.2",
|
|
32
|
-
"@camera.ui/rpc": ">=1.0.
|
|
32
|
+
"@camera.ui/rpc": ">=1.0.7",
|
|
33
33
|
"@camera.ui/sdk": ">=0.0.11",
|
|
34
34
|
"@camera.ui/transport": ">=0.0.1",
|
|
35
35
|
"@vueuse/core": ">=14.3.0",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@camera.ui/logger": "file:../../packages/logger",
|
|
40
|
-
"@camera.ui/rpc": "^1.0.
|
|
40
|
+
"@camera.ui/rpc": "^1.0.7",
|
|
41
41
|
"@camera.ui/sdk": "~0.0.11",
|
|
42
42
|
"@camera.ui/transport": "file:../../packages/transport",
|
|
43
43
|
"@eneris/push-receiver": "^4.3.1",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"typescript": "5.9.3",
|
|
61
61
|
"typescript-eslint": "^8.62.1",
|
|
62
62
|
"unplugin-dts": "^1.0.3",
|
|
63
|
-
"updates": "^17.18.
|
|
64
|
-
"vite": "^8.1.
|
|
63
|
+
"updates": "^17.18.2",
|
|
64
|
+
"vite": "^8.1.3",
|
|
65
65
|
"vitest": "^4.1.9",
|
|
66
66
|
"vue": "^3.5.39",
|
|
67
67
|
"vue-tsc": "^3.3.6"
|