@camera.ui/browser 0.0.145 → 0.0.147
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 +7 -1
- package/dist/index.js +175 -42
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -163,6 +163,8 @@ export declare interface CameraStream {
|
|
|
163
163
|
readonly isPlaying: Ref<boolean>;
|
|
164
164
|
readonly activeMode: Ref<'webrtc' | 'webrtc/tcp' | 'mse'>;
|
|
165
165
|
readonly activeResolution: Ref<StreamingRole>;
|
|
166
|
+
readonly degradedFrom: Ref<StreamingRole | null>;
|
|
167
|
+
readonly unsupported: Ref<boolean>;
|
|
166
168
|
readonly hasAudio: Ref<boolean>;
|
|
167
169
|
readonly hasBackchannel: Ref<boolean>;
|
|
168
170
|
readonly error: Ref<Error | undefined>;
|
|
@@ -837,6 +839,8 @@ export declare interface ReactiveStream {
|
|
|
837
839
|
readonly requestedMode: Ref<VideoStreamingMode>;
|
|
838
840
|
readonly activeResolution: Ref<StreamingRole>;
|
|
839
841
|
readonly requestedResolution: Ref<StreamingRole>;
|
|
842
|
+
readonly degradedFrom: Ref<StreamingRole | null>;
|
|
843
|
+
readonly degradeReason: Ref<StreamDegradeReason | null>;
|
|
840
844
|
readonly source: Ref<CameraSource | undefined>;
|
|
841
845
|
readonly hasVideo: Ref<boolean>;
|
|
842
846
|
readonly hasAudio: Ref<boolean>;
|
|
@@ -1023,6 +1027,8 @@ export declare const STREAM_CONFIG: {
|
|
|
1023
1027
|
};
|
|
1024
1028
|
};
|
|
1025
1029
|
|
|
1030
|
+
declare type StreamDegradeReason = 'codec';
|
|
1031
|
+
|
|
1026
1032
|
export declare interface StreamOptions {
|
|
1027
1033
|
camera: MaybeRefOrGetter<ReactiveCameraDevice | undefined>;
|
|
1028
1034
|
videoElement: MaybeRefOrGetter<HTMLVideoElement | undefined | null>;
|
|
@@ -1034,7 +1040,7 @@ export declare interface StreamOptions {
|
|
|
1034
1040
|
webrtcConnectTimeout?: number;
|
|
1035
1041
|
}
|
|
1036
1042
|
|
|
1037
|
-
export declare type StreamStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'error' | 'closed';
|
|
1043
|
+
export declare type StreamStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'error' | 'unsupported' | 'closed';
|
|
1038
1044
|
|
|
1039
1045
|
/* Excluded from this release type: SwitchControlProperties */
|
|
1040
1046
|
|
package/dist/index.js
CHANGED
|
@@ -2322,6 +2322,7 @@ var STREAM_CONFIG = {
|
|
|
2322
2322
|
};
|
|
2323
2323
|
//#endregion
|
|
2324
2324
|
//#region src/streaming/utils.ts
|
|
2325
|
+
var H265_TEST_CODEC = "hvc1.1.6.L153.B0";
|
|
2325
2326
|
function safariVersion() {
|
|
2326
2327
|
return navigator.userAgent.match(/version\/(\d+)/i);
|
|
2327
2328
|
}
|
|
@@ -2354,14 +2355,27 @@ function isWebRTCCompatibleVideo(codec) {
|
|
|
2354
2355
|
if (codec === "H265") return isH265Supported();
|
|
2355
2356
|
return false;
|
|
2356
2357
|
}
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2358
|
+
var mseH265;
|
|
2359
|
+
var webrtcH265;
|
|
2360
|
+
function canPlayH265(transport) {
|
|
2361
|
+
if (transport === "auto") return canPlayH265("webrtc") || canPlayH265("mse");
|
|
2362
|
+
if (transport === "mse") {
|
|
2363
|
+
if (mseH265 === void 0) mseH265 = !!getMediaSourceConstructor()?.isTypeSupported(`video/mp4; codecs="${H265_TEST_CODEC}"`);
|
|
2364
|
+
return mseH265;
|
|
2365
|
+
}
|
|
2366
|
+
if (webrtcH265 === void 0) try {
|
|
2367
|
+
webrtcH265 = !!(RTCRtpReceiver?.getCapabilities("video")?.codecs)?.some((c) => c.mimeType.toLowerCase().includes("h265") || c.mimeType.toLowerCase().includes("hevc"));
|
|
2362
2368
|
} catch {
|
|
2363
|
-
|
|
2369
|
+
webrtcH265 = false;
|
|
2364
2370
|
}
|
|
2371
|
+
return webrtcH265;
|
|
2372
|
+
}
|
|
2373
|
+
function reportH265DecodeFailure(transport) {
|
|
2374
|
+
if (transport === "mse") mseH265 = false;
|
|
2375
|
+
else webrtcH265 = false;
|
|
2376
|
+
}
|
|
2377
|
+
function isH265Supported() {
|
|
2378
|
+
return canPlayH265("webrtc");
|
|
2365
2379
|
}
|
|
2366
2380
|
function checkWebRTCCompatibility(probe) {
|
|
2367
2381
|
const audioStreams = probe.audio.filter((s) => s.direction === "sendonly");
|
|
@@ -2755,6 +2769,8 @@ var StreamConnection = class {
|
|
|
2755
2769
|
requestedMode;
|
|
2756
2770
|
activeResolution;
|
|
2757
2771
|
requestedResolution;
|
|
2772
|
+
degradedFrom;
|
|
2773
|
+
degradeReason;
|
|
2758
2774
|
source;
|
|
2759
2775
|
hasVideo;
|
|
2760
2776
|
hasAudio;
|
|
@@ -2766,14 +2782,20 @@ var StreamConnection = class {
|
|
|
2766
2782
|
paused;
|
|
2767
2783
|
nativeWidth;
|
|
2768
2784
|
nativeHeight;
|
|
2785
|
+
camera;
|
|
2786
|
+
videoElement;
|
|
2787
|
+
containerElement;
|
|
2788
|
+
target;
|
|
2789
|
+
isReady;
|
|
2790
|
+
effectiveMode;
|
|
2791
|
+
triedSourceIds = /* @__PURE__ */ new Set();
|
|
2769
2792
|
options;
|
|
2793
|
+
wsTransport;
|
|
2794
|
+
scope = effectScope(true);
|
|
2770
2795
|
connectionGeneration = 0;
|
|
2771
2796
|
abortController = new AbortController();
|
|
2772
|
-
|
|
2773
|
-
offTabVisible;
|
|
2774
|
-
offTabPaused;
|
|
2797
|
+
probedSourceId;
|
|
2775
2798
|
wasPausedByVisibility = false;
|
|
2776
|
-
wsTransport;
|
|
2777
2799
|
wsHandle;
|
|
2778
2800
|
webrtcHandler;
|
|
2779
2801
|
mseHandler;
|
|
@@ -2783,15 +2805,11 @@ var StreamConnection = class {
|
|
|
2783
2805
|
lastMediaStream = null;
|
|
2784
2806
|
stopWatchers = [];
|
|
2785
2807
|
mseMonitorInterval;
|
|
2808
|
+
offTabVisible;
|
|
2809
|
+
offTabPaused;
|
|
2786
2810
|
onVideoPauseBound;
|
|
2787
2811
|
onVideoPlayBound;
|
|
2788
2812
|
onVideoResizeBound;
|
|
2789
|
-
camera;
|
|
2790
|
-
videoElement;
|
|
2791
|
-
containerElement;
|
|
2792
|
-
target;
|
|
2793
|
-
isReady;
|
|
2794
|
-
effectiveMode;
|
|
2795
2813
|
startWsConnectTimeout;
|
|
2796
2814
|
stopWsConnectTimeout;
|
|
2797
2815
|
startConnectTimeout;
|
|
@@ -2810,6 +2828,8 @@ var StreamConnection = class {
|
|
|
2810
2828
|
this.status = ref("idle");
|
|
2811
2829
|
this.activeMode = ref("webrtc");
|
|
2812
2830
|
this.activeResolution = ref("low-resolution");
|
|
2831
|
+
this.degradedFrom = ref(null);
|
|
2832
|
+
this.degradeReason = ref(null);
|
|
2813
2833
|
this.source = shallowRef();
|
|
2814
2834
|
this.hasVideo = ref(false);
|
|
2815
2835
|
this.hasAudio = ref(false);
|
|
@@ -2846,11 +2866,18 @@ var StreamConnection = class {
|
|
|
2846
2866
|
this.activeMode.value = "mse";
|
|
2847
2867
|
if (this.mseHandler?.isReady) this.status.value = "connected";
|
|
2848
2868
|
else this.startMSE();
|
|
2849
|
-
} else
|
|
2869
|
+
} else {
|
|
2870
|
+
this.markSourceUndecodable();
|
|
2871
|
+
this.restart();
|
|
2872
|
+
}
|
|
2873
|
+
else if (this.status.value === "connected" && !this.isPlaying.value && !this.abortController.signal.aborted && this.sourceLikelyH265()) this.handleNoFramesWhileConnected();
|
|
2850
2874
|
}, STREAM_CONFIG.WEBRTC.CONNECT_TIMEOUT, { immediate: false });
|
|
2851
2875
|
const mseConnectTimeout = useTimeoutFn(() => {
|
|
2852
2876
|
if (this.abortController.signal.aborted || this.status.value === "connected" || this.status.value === "closed") return;
|
|
2853
|
-
if (this.mseHandler && !this.mseHandler.isReady)
|
|
2877
|
+
if (this.mseHandler && !this.mseHandler.isReady) {
|
|
2878
|
+
this.markSourceUndecodable();
|
|
2879
|
+
this.restart();
|
|
2880
|
+
}
|
|
2854
2881
|
}, STREAM_CONFIG.WEBRTC.CONNECT_TIMEOUT, { immediate: false });
|
|
2855
2882
|
const reconnectTimeout = useTimeoutFn(() => {
|
|
2856
2883
|
if (!this.abortController.signal.aborted) this.restart();
|
|
@@ -2901,22 +2928,43 @@ var StreamConnection = class {
|
|
|
2901
2928
|
this.cleanup();
|
|
2902
2929
|
this.abortController = new AbortController();
|
|
2903
2930
|
const gen = ++this.connectionGeneration;
|
|
2931
|
+
this.triedSourceIds.clear();
|
|
2904
2932
|
this.status.value = "connecting";
|
|
2905
2933
|
this.error.value = void 0;
|
|
2906
2934
|
this.isPlaying.value = false;
|
|
2907
2935
|
try {
|
|
2908
|
-
|
|
2936
|
+
const init = this.initializeSource();
|
|
2937
|
+
if (init === "unsupported") {
|
|
2938
|
+
log.debug("start() — no source with a playable codec");
|
|
2939
|
+
this.setUnsupported();
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
if (!init) {
|
|
2909
2943
|
log.debug("start() — no streaming source available");
|
|
2910
2944
|
this.error.value = /* @__PURE__ */ new Error("No streaming source available");
|
|
2911
2945
|
this.status.value = "error";
|
|
2912
2946
|
return;
|
|
2913
2947
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2948
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2949
|
+
const probedSourceId = this.source.value?._id;
|
|
2950
|
+
const probe = await this.probeStream();
|
|
2951
|
+
if (gen !== this.connectionGeneration) {
|
|
2952
|
+
log.debug("start() — generation stale after probe, aborting");
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
if (probe && probedSourceId && this.probeSaysUnplayable(probe)) this.triedSourceIds.add(probedSourceId);
|
|
2956
|
+
const reinit = this.initializeSource();
|
|
2957
|
+
if (reinit === "unsupported") {
|
|
2958
|
+
this.setUnsupported();
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
if (!reinit) {
|
|
2962
|
+
this.error.value = /* @__PURE__ */ new Error("No streaming source available");
|
|
2963
|
+
this.status.value = "error";
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
if (!probe || this.source.value?._id === probedSourceId) break;
|
|
2918
2967
|
}
|
|
2919
|
-
this.initializeSource();
|
|
2920
2968
|
log.debug("start() — connecting WebSocket");
|
|
2921
2969
|
this.connectWebSocket();
|
|
2922
2970
|
} catch (err) {
|
|
@@ -2940,15 +2988,27 @@ var StreamConnection = class {
|
|
|
2940
2988
|
if (this.requestedMode.value === mode) return;
|
|
2941
2989
|
this.requestedMode.value = mode;
|
|
2942
2990
|
this.activeMode.value = mode === "auto" ? "webrtc" : mode;
|
|
2991
|
+
if (this.status.value === "unsupported") {
|
|
2992
|
+
this.stop();
|
|
2993
|
+
await this.start();
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2943
2996
|
if (this.status.value === "connected") await this.restart();
|
|
2944
2997
|
}
|
|
2945
2998
|
async setResolution(resolution) {
|
|
2946
2999
|
if (this.requestedResolution.value === resolution) return;
|
|
2947
3000
|
this.requestedResolution.value = resolution;
|
|
3001
|
+
if (this.status.value === "unsupported") {
|
|
3002
|
+
this.stop();
|
|
3003
|
+
await this.start();
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
2948
3006
|
const result = this.getSourceForResolution(resolution);
|
|
2949
|
-
if (result && result.source._id !== this.source.value?._id) {
|
|
3007
|
+
if (result && result !== "unsupported" && result.source._id !== this.source.value?._id) {
|
|
2950
3008
|
this.source.value = result.source;
|
|
2951
3009
|
this.activeResolution.value = result.effectiveResolution;
|
|
3010
|
+
this.degradedFrom.value = result.degradedFrom;
|
|
3011
|
+
this.degradeReason.value = result.degradedFrom ? "codec" : null;
|
|
2952
3012
|
if (this.status.value === "connected") await this.restart();
|
|
2953
3013
|
}
|
|
2954
3014
|
}
|
|
@@ -3023,7 +3083,12 @@ var StreamConnection = class {
|
|
|
3023
3083
|
return;
|
|
3024
3084
|
}
|
|
3025
3085
|
try {
|
|
3026
|
-
|
|
3086
|
+
const init = this.initializeSource();
|
|
3087
|
+
if (init === "unsupported") {
|
|
3088
|
+
this.setUnsupported();
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
if (!init) {
|
|
3027
3092
|
this.error.value = /* @__PURE__ */ new Error("No streaming source available");
|
|
3028
3093
|
this.status.value = "error";
|
|
3029
3094
|
return;
|
|
@@ -3057,6 +3122,12 @@ var StreamConnection = class {
|
|
|
3057
3122
|
this.restart();
|
|
3058
3123
|
});
|
|
3059
3124
|
this.stopWatchers.push(stopAuthWatch);
|
|
3125
|
+
const stopSourcesWatch = watch(() => this.camera.value?.sources.value, () => {
|
|
3126
|
+
if (this.status.value !== "unsupported") return;
|
|
3127
|
+
this.stop();
|
|
3128
|
+
this.start();
|
|
3129
|
+
});
|
|
3130
|
+
this.stopWatchers.push(stopSourcesWatch);
|
|
3060
3131
|
const stopModeWatch = watch(() => toValue(this.options.mode), (newMode) => {
|
|
3061
3132
|
if (newMode !== void 0 && newMode !== this.requestedMode.value) this.setMode(newMode);
|
|
3062
3133
|
});
|
|
@@ -3107,6 +3178,9 @@ var StreamConnection = class {
|
|
|
3107
3178
|
if (newCamera !== oldCamera) {
|
|
3108
3179
|
if (this.status.value === "connected") {
|
|
3109
3180
|
if (newCamera && newVideo && newClient) this.restart();
|
|
3181
|
+
} else if (this.status.value === "unsupported" && newCamera && newVideo && newClient) {
|
|
3182
|
+
this.stop();
|
|
3183
|
+
this.start();
|
|
3110
3184
|
}
|
|
3111
3185
|
} else if (newVideo !== oldVideo && newVideo) {
|
|
3112
3186
|
if (newVideo.srcObject instanceof MediaStream) {
|
|
@@ -3144,6 +3218,14 @@ var StreamConnection = class {
|
|
|
3144
3218
|
});
|
|
3145
3219
|
this.stopWatchers.push(stopCameraWatch);
|
|
3146
3220
|
}
|
|
3221
|
+
isSourcePlayable(source) {
|
|
3222
|
+
if (this.triedSourceIds.has(source._id)) return false;
|
|
3223
|
+
if (source.videoCodec !== "H265") return true;
|
|
3224
|
+
const mode = this.requestedMode.value;
|
|
3225
|
+
if (mode === "auto") return canPlayH265("auto");
|
|
3226
|
+
if (mode === "mse") return canPlayH265("mse");
|
|
3227
|
+
return canPlayH265("webrtc");
|
|
3228
|
+
}
|
|
3147
3229
|
getSourceForResolution(resolution) {
|
|
3148
3230
|
const cam = this.camera.value;
|
|
3149
3231
|
if (!cam) return void 0;
|
|
@@ -3152,43 +3234,89 @@ var StreamConnection = class {
|
|
|
3152
3234
|
"mid-resolution",
|
|
3153
3235
|
"low-resolution"
|
|
3154
3236
|
];
|
|
3155
|
-
const
|
|
3156
|
-
|
|
3157
|
-
source: exactSource,
|
|
3158
|
-
effectiveResolution: resolution
|
|
3159
|
-
};
|
|
3160
|
-
const startIndex = resolutionOrder.indexOf(resolution);
|
|
3237
|
+
const startIndex = Math.max(0, resolutionOrder.indexOf(resolution));
|
|
3238
|
+
let skippedUnplayable = false;
|
|
3161
3239
|
for (let i = startIndex; i < resolutionOrder.length; i++) {
|
|
3162
3240
|
const src = cam.sources.value.find((s) => s.role === resolutionOrder[i]);
|
|
3163
|
-
if (src)
|
|
3241
|
+
if (!src) continue;
|
|
3242
|
+
if (!this.isSourcePlayable(src)) {
|
|
3243
|
+
skippedUnplayable = true;
|
|
3244
|
+
continue;
|
|
3245
|
+
}
|
|
3246
|
+
return {
|
|
3164
3247
|
source: src,
|
|
3165
|
-
effectiveResolution: resolutionOrder[i]
|
|
3248
|
+
effectiveResolution: resolutionOrder[i],
|
|
3249
|
+
degradedFrom: skippedUnplayable ? resolution : null
|
|
3166
3250
|
};
|
|
3167
3251
|
}
|
|
3168
3252
|
const fallback = cam.streamSource.value;
|
|
3169
|
-
if (fallback) return {
|
|
3253
|
+
if (fallback && this.isSourcePlayable(fallback)) return {
|
|
3170
3254
|
source: fallback,
|
|
3171
|
-
effectiveResolution: fallback.role ?? "low-resolution"
|
|
3255
|
+
effectiveResolution: fallback.role ?? "low-resolution",
|
|
3256
|
+
degradedFrom: skippedUnplayable ? resolution : null
|
|
3172
3257
|
};
|
|
3258
|
+
if (skippedUnplayable || fallback) return "unsupported";
|
|
3173
3259
|
}
|
|
3174
3260
|
initializeSource() {
|
|
3175
3261
|
const result = this.getSourceForResolution(this.requestedResolution.value);
|
|
3176
3262
|
if (!result) return false;
|
|
3263
|
+
if (result === "unsupported") return "unsupported";
|
|
3177
3264
|
this.source.value = result.source;
|
|
3178
3265
|
this.activeResolution.value = result.effectiveResolution;
|
|
3266
|
+
this.degradedFrom.value = result.degradedFrom;
|
|
3267
|
+
this.degradeReason.value = result.degradedFrom ? "codec" : null;
|
|
3268
|
+
if (this.probedSourceId && this.probedSourceId !== result.source._id) {
|
|
3269
|
+
this.probeInfo.value = void 0;
|
|
3270
|
+
this.probedSourceId = void 0;
|
|
3271
|
+
this.hasVideo.value = false;
|
|
3272
|
+
this.hasAudio.value = false;
|
|
3273
|
+
this.hasBackchannel.value = false;
|
|
3274
|
+
}
|
|
3179
3275
|
return true;
|
|
3180
3276
|
}
|
|
3277
|
+
setUnsupported() {
|
|
3278
|
+
const codec = this.camera.value?.sources.value.find((s) => s.videoCodec)?.videoCodec ?? "H265";
|
|
3279
|
+
this.error.value = /* @__PURE__ */ new Error(`${codec} cannot be played on this device`);
|
|
3280
|
+
this.status.value = "unsupported";
|
|
3281
|
+
}
|
|
3282
|
+
probeSaysUnplayable(probe) {
|
|
3283
|
+
const video = probe.video.filter((v) => v.direction === "sendonly");
|
|
3284
|
+
if (!video.length) return false;
|
|
3285
|
+
if (!video.every((v) => v.codec === "H265")) return false;
|
|
3286
|
+
const mode = this.requestedMode.value;
|
|
3287
|
+
return !canPlayH265(mode === "auto" ? "auto" : mode === "mse" ? "mse" : "webrtc");
|
|
3288
|
+
}
|
|
3289
|
+
sourceLikelyH265() {
|
|
3290
|
+
const src = this.source.value;
|
|
3291
|
+
if (!src) return false;
|
|
3292
|
+
if (src.videoCodec === "H265") return true;
|
|
3293
|
+
if (src.videoCodec !== void 0) return false;
|
|
3294
|
+
const video = this.probeInfo.value?.video.filter((v) => v.direction === "sendonly") ?? [];
|
|
3295
|
+
return video.length > 0 && video.every((v) => v.codec === "H265");
|
|
3296
|
+
}
|
|
3297
|
+
markSourceUndecodable() {
|
|
3298
|
+
const src = this.source.value;
|
|
3299
|
+
if (!src || !this.sourceLikelyH265()) return;
|
|
3300
|
+
this.triedSourceIds.add(src._id);
|
|
3301
|
+
}
|
|
3302
|
+
handleNoFramesWhileConnected() {
|
|
3303
|
+
reportH265DecodeFailure(this.activeMode.value === "mse" ? "mse" : "webrtc");
|
|
3304
|
+
this.markSourceUndecodable();
|
|
3305
|
+
this.restart();
|
|
3306
|
+
}
|
|
3181
3307
|
async probeStream() {
|
|
3182
3308
|
const cam = this.camera.value;
|
|
3183
|
-
|
|
3309
|
+
const source = this.source.value;
|
|
3310
|
+
if (!cam || !source || this.abortController.signal.aborted) return void 0;
|
|
3184
3311
|
try {
|
|
3185
|
-
const probe = await cam.probeStream(
|
|
3312
|
+
const probe = await cam.probeStream(source._id, {
|
|
3186
3313
|
video: true,
|
|
3187
3314
|
audio: ["pcma", "opus"],
|
|
3188
3315
|
microphone: true
|
|
3189
3316
|
});
|
|
3190
3317
|
if (probe && !this.abortController.signal.aborted) {
|
|
3191
3318
|
this.probeInfo.value = probe;
|
|
3319
|
+
this.probedSourceId = source._id;
|
|
3192
3320
|
this.hasBackchannel.value = probe.audio.some((a) => a.direction === "recvonly");
|
|
3193
3321
|
this.hasAudio.value = probe.audio.filter((a) => a.direction === "sendonly").length > 0;
|
|
3194
3322
|
this.hasVideo.value = probe.video.filter((v) => v.direction === "sendonly").length > 0;
|
|
@@ -3294,7 +3422,6 @@ var StreamConnection = class {
|
|
|
3294
3422
|
if (this.abortController.signal.aborted) return;
|
|
3295
3423
|
const video = this.videoElement.value;
|
|
3296
3424
|
if (!video) return;
|
|
3297
|
-
this.stopConnectTimeout();
|
|
3298
3425
|
this.activeMode.value = this.requestedMode.value === "auto" ? "webrtc" : this.requestedMode.value;
|
|
3299
3426
|
this.lastMediaStream = stream;
|
|
3300
3427
|
video.srcObject = stream;
|
|
@@ -3393,7 +3520,10 @@ var StreamConnection = class {
|
|
|
3393
3520
|
this.startWebRTC("webrtc");
|
|
3394
3521
|
}
|
|
3395
3522
|
handleFirstFrame() {
|
|
3396
|
-
if (!this.isPlaying.value && !this.abortController.signal.aborted)
|
|
3523
|
+
if (!this.isPlaying.value && !this.abortController.signal.aborted) {
|
|
3524
|
+
this.stopConnectTimeout();
|
|
3525
|
+
this.isPlaying.value = true;
|
|
3526
|
+
}
|
|
3397
3527
|
}
|
|
3398
3528
|
monitorMseBuffer() {
|
|
3399
3529
|
const video = this.videoElement.value;
|
|
@@ -3800,11 +3930,13 @@ function useCameraStream(options) {
|
|
|
3800
3930
|
const isPlaying = computed(() => currentStream.value?.isPlaying.value ?? false);
|
|
3801
3931
|
const activeMode = computed(() => currentStream.value?.activeMode.value ?? "webrtc");
|
|
3802
3932
|
const activeResolution = computed(() => currentStream.value?.activeResolution.value ?? "low-resolution");
|
|
3933
|
+
const degradedFrom = computed(() => currentStream.value?.degradedFrom.value ?? null);
|
|
3934
|
+
const unsupported = computed(() => status.value === "unsupported");
|
|
3803
3935
|
const hasAudio = computed(() => currentStream.value?.hasAudio.value ?? false);
|
|
3804
3936
|
const hasBackchannel = computed(() => currentStream.value?.hasBackchannel.value ?? false);
|
|
3805
3937
|
const error = computed(() => currentStream.value?.error.value);
|
|
3806
3938
|
const isReconnecting = computed(() => status.value === "reconnecting");
|
|
3807
|
-
const isBusy = computed(() => cameraDeviceLoading.value || !isPlaying.value && status.value !== "error");
|
|
3939
|
+
const isBusy = computed(() => cameraDeviceLoading.value || !isPlaying.value && status.value !== "error" && status.value !== "unsupported");
|
|
3808
3940
|
const hasSound = computed(() => hasAudio.value && status.value === "connected");
|
|
3809
3941
|
const hasIntercom = computed(() => Boolean(typeof navigator !== "undefined" && navigator.mediaDevices) && hasBackchannel.value);
|
|
3810
3942
|
const muted = computed(() => currentStream.value?.muted.value ?? true);
|
|
@@ -3848,7 +3980,6 @@ function useCameraStream(options) {
|
|
|
3848
3980
|
video.disablePictureInPicture = false;
|
|
3849
3981
|
video.preload = "auto";
|
|
3850
3982
|
video.poster = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
|
|
3851
|
-
video.style.backgroundColor = "#000";
|
|
3852
3983
|
video.style.position = "absolute";
|
|
3853
3984
|
video.style.width = "100%";
|
|
3854
3985
|
video.style.height = "100%";
|
|
@@ -4197,6 +4328,8 @@ function useCameraStream(options) {
|
|
|
4197
4328
|
isPlaying,
|
|
4198
4329
|
activeMode,
|
|
4199
4330
|
activeResolution,
|
|
4331
|
+
degradedFrom,
|
|
4332
|
+
unsupported,
|
|
4200
4333
|
hasAudio,
|
|
4201
4334
|
hasBackchannel,
|
|
4202
4335
|
error,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camera.ui/browser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.147",
|
|
4
4
|
"description": "camera.ui browser client",
|
|
5
5
|
"author": "seydx (https://github.com/cameraui/clients)",
|
|
6
6
|
"type": "module",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@camera.ui/logger": ">=0.0.2",
|
|
33
33
|
"@camera.ui/rpc": ">=1.0.10",
|
|
34
|
-
"@camera.ui/sdk": ">=0.0.
|
|
34
|
+
"@camera.ui/sdk": ">=0.0.33",
|
|
35
35
|
"@camera.ui/transport": ">=0.0.1",
|
|
36
36
|
"@vueuse/core": ">=14.3.0",
|
|
37
37
|
"vue": ">=3.5.40"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@camera.ui/logger": "file:../../packages/logger",
|
|
41
41
|
"@camera.ui/rpc": "^1.0.10",
|
|
42
|
-
"@camera.ui/sdk": "~0.0.
|
|
42
|
+
"@camera.ui/sdk": "~0.0.33",
|
|
43
43
|
"@camera.ui/transport": "file:../../packages/transport",
|
|
44
44
|
"@eneris/push-receiver": "^4.3.1",
|
|
45
45
|
"@microsoft/api-extractor": "^7.58.12",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"typescript": "5.9.3",
|
|
62
62
|
"typescript-eslint": "^8.65.0",
|
|
63
63
|
"unplugin-dts": "^1.0.3",
|
|
64
|
-
"updates": "^17.19.
|
|
64
|
+
"updates": "^17.19.2",
|
|
65
65
|
"vite": "^8.1.5",
|
|
66
66
|
"vitest": "^4.1.10",
|
|
67
67
|
"vue": "^3.5.40",
|