@uniai-fe/uds-templates 0.6.13 → 0.6.14
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/package.json
CHANGED
|
@@ -49,6 +49,15 @@ const attachVideo = (video: HTMLVideoElement, stream: MediaStream | null) => {
|
|
|
49
49
|
video.srcObject = stream;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
const clearVideoIfAttachedStream = (
|
|
53
|
+
video: HTMLVideoElement,
|
|
54
|
+
stream: MediaStream | null,
|
|
55
|
+
) => {
|
|
56
|
+
if (stream && video.srcObject === stream) {
|
|
57
|
+
video.srcObject = null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
52
61
|
const notifyEntry = (entry: CctvRtcStreamEntry) => {
|
|
53
62
|
const snapshot = getEntrySnapshot(entry);
|
|
54
63
|
entry.listeners.forEach(listener => listener(snapshot));
|
|
@@ -125,9 +134,7 @@ const closeEntry = (entry: CctvRtcStreamEntry) => {
|
|
|
125
134
|
entry.isStreaming = false;
|
|
126
135
|
|
|
127
136
|
entry.attachedVideos.forEach(video => {
|
|
128
|
-
|
|
129
|
-
video.srcObject = null;
|
|
130
|
-
}
|
|
137
|
+
clearVideoIfAttachedStream(video, entry.stream);
|
|
131
138
|
});
|
|
132
139
|
|
|
133
140
|
notifyEntry(entry);
|
|
@@ -166,13 +173,13 @@ export function createCctvRtcStreamRegistry(): CctvRtcStreamRegistry {
|
|
|
166
173
|
}
|
|
167
174
|
|
|
168
175
|
entry.attachedVideos.add(video);
|
|
169
|
-
|
|
176
|
+
if (entry.stream || !video.srcObject) {
|
|
177
|
+
attachVideo(video, entry.stream);
|
|
178
|
+
}
|
|
170
179
|
|
|
171
180
|
return () => {
|
|
172
181
|
entry.attachedVideos.delete(video);
|
|
173
|
-
|
|
174
|
-
video.srcObject = null;
|
|
175
|
-
}
|
|
182
|
+
clearVideoIfAttachedStream(video, entry.stream);
|
|
176
183
|
};
|
|
177
184
|
},
|
|
178
185
|
closeAll() {
|
|
@@ -233,7 +240,12 @@ export function createCctvRtcStreamRegistry(): CctvRtcStreamRegistry {
|
|
|
233
240
|
};
|
|
234
241
|
|
|
235
242
|
entries.set(streamKey, entry);
|
|
236
|
-
|
|
243
|
+
if (!video.srcObject) {
|
|
244
|
+
attachVideo(video, null);
|
|
245
|
+
} else {
|
|
246
|
+
video.playsInline = true;
|
|
247
|
+
video.autoplay = true;
|
|
248
|
+
}
|
|
237
249
|
logCctvDebugEvent({
|
|
238
250
|
event: "start:create-entry",
|
|
239
251
|
payload: {
|
|
@@ -328,7 +340,7 @@ export function createCctvRtcStreamRegistry(): CctvRtcStreamRegistry {
|
|
|
328
340
|
source: "streamRegistry",
|
|
329
341
|
});
|
|
330
342
|
entry.attachedVideos.forEach(attachedVideo =>
|
|
331
|
-
|
|
343
|
+
clearVideoIfAttachedStream(attachedVideo, entry.stream),
|
|
332
344
|
);
|
|
333
345
|
notifyEntry(entry);
|
|
334
346
|
});
|
|
@@ -140,8 +140,8 @@ const canUseTokenForNewConnection = ({
|
|
|
140
140
|
* videoRef, // WebRTC MediaStream을 연결할 video 요소 ref
|
|
141
141
|
* connectionState, // RTCPeerConnectionState 상태값
|
|
142
142
|
* streamError, // 스트림 시도 중 발생한 오류 메시지
|
|
143
|
-
* isStreaming, //
|
|
144
|
-
* isTokenLoading, // 토큰 발급
|
|
143
|
+
* isStreaming, // UI 표시 기준 스트림 연결 절차 진행 여부
|
|
144
|
+
* isTokenLoading, // UI 표시 기준 토큰 발급 요청 진행 여부
|
|
145
145
|
* isTokenError, // 토큰 발급 요청이 실패했는지 여부
|
|
146
146
|
* canReconnect, // UDS 내부 grace/stagger 이후 재연결 호출이 가능한지 여부
|
|
147
147
|
* refetchToken, // 토큰 발급을 재시도하는 함수
|
|
@@ -328,16 +328,30 @@ export function useCctvRtcStream({
|
|
|
328
328
|
streamIdentityKey,
|
|
329
329
|
]);
|
|
330
330
|
|
|
331
|
-
const
|
|
331
|
+
const isPostConnectedReplacementLoading =
|
|
332
332
|
hasConnected &&
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
Boolean(streamIdentityKey) &&
|
|
334
|
+
!isTokenError &&
|
|
335
|
+
!streamError &&
|
|
336
|
+
(isTokenLoading || isStreaming);
|
|
337
|
+
|
|
338
|
+
const shouldPreserveConnectedDisplay =
|
|
339
|
+
isPostConnectedReplacementLoading ||
|
|
340
|
+
(hasConnected &&
|
|
341
|
+
!isPostConnectedReconnectReady &&
|
|
342
|
+
DISPLAY_CONNECTED_DURING_RECOVERY_STATES.has(connectionState));
|
|
335
343
|
|
|
336
344
|
// 반환 state는 CamList/Viewer/overlay의 live/error/message 계산에 직접 쓰인다.
|
|
337
|
-
// post-connected
|
|
345
|
+
// post-connected recovery/replacement 중에는 기존 화면을 유지해 UI reset 파동을 막는다.
|
|
338
346
|
const displayConnectionState = shouldPreserveConnectedDisplay
|
|
339
347
|
? "connected"
|
|
340
348
|
: connectionState;
|
|
349
|
+
const displayIsStreaming = shouldPreserveConnectedDisplay
|
|
350
|
+
? false
|
|
351
|
+
: isStreaming;
|
|
352
|
+
const displayIsTokenLoading = shouldPreserveConnectedDisplay
|
|
353
|
+
? false
|
|
354
|
+
: isTokenLoading;
|
|
341
355
|
|
|
342
356
|
const skippedDisconnectedDebugKeyRef = useRef<string | null>(null);
|
|
343
357
|
|
|
@@ -468,20 +482,39 @@ export function useCctvRtcStream({
|
|
|
468
482
|
if (!streamKey || !tokenQuery.data?.token || !endpoint || !currentVideo)
|
|
469
483
|
return;
|
|
470
484
|
|
|
471
|
-
|
|
485
|
+
const previousStreamKey =
|
|
472
486
|
activeStreamIdentityKeyRef.current === streamIdentityKey &&
|
|
473
487
|
activeStreamKeyRef.current !== streamKey
|
|
474
|
-
|
|
488
|
+
? activeStreamKeyRef.current
|
|
489
|
+
: null;
|
|
490
|
+
let didClosePreviousStream = false;
|
|
491
|
+
|
|
492
|
+
const closePreviousStreamAfterTrack = () => {
|
|
493
|
+
if (!previousStreamKey || didClosePreviousStream) return;
|
|
494
|
+
|
|
495
|
+
didClosePreviousStream = true;
|
|
475
496
|
logCctvDebugEvent({
|
|
476
|
-
event: "stream-effect:close-previous-
|
|
497
|
+
event: "stream-effect:close-previous-after-track",
|
|
477
498
|
payload: {
|
|
478
499
|
...debugBasePayload,
|
|
479
|
-
|
|
480
|
-
|
|
500
|
+
previousStreamKey: getCctvDebugKeyLabel(previousStreamKey),
|
|
501
|
+
streamKey: getCctvDebugKeyLabel(streamKey),
|
|
481
502
|
},
|
|
482
503
|
source: "useRtcStream",
|
|
483
504
|
});
|
|
484
505
|
streamRegistry.closeByIdentity(streamIdentityKey, streamKey);
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
if (previousStreamKey) {
|
|
509
|
+
logCctvDebugEvent({
|
|
510
|
+
event: "stream-effect:prepare-replacement",
|
|
511
|
+
payload: {
|
|
512
|
+
...debugBasePayload,
|
|
513
|
+
previousStreamKey: getCctvDebugKeyLabel(previousStreamKey),
|
|
514
|
+
streamKey: getCctvDebugKeyLabel(streamKey),
|
|
515
|
+
},
|
|
516
|
+
source: "useRtcStream",
|
|
517
|
+
});
|
|
485
518
|
}
|
|
486
519
|
|
|
487
520
|
activeStreamKeyRef.current = streamKey;
|
|
@@ -520,8 +553,11 @@ export function useCctvRtcStream({
|
|
|
520
553
|
});
|
|
521
554
|
setHasConnected(true);
|
|
522
555
|
}
|
|
523
|
-
if (snapshot.stream
|
|
524
|
-
currentVideo.srcObject
|
|
556
|
+
if (snapshot.stream) {
|
|
557
|
+
if (currentVideo.srcObject !== snapshot.stream) {
|
|
558
|
+
currentVideo.srcObject = snapshot.stream;
|
|
559
|
+
}
|
|
560
|
+
closePreviousStreamAfterTrack();
|
|
525
561
|
}
|
|
526
562
|
});
|
|
527
563
|
const snapshot = streamRegistry.getSnapshot(streamKey);
|
|
@@ -530,8 +566,11 @@ export function useCctvRtcStream({
|
|
|
530
566
|
setStreaming(snapshot.isStreaming);
|
|
531
567
|
if (snapshot.connectionState === "connected") setHasConnected(true);
|
|
532
568
|
|
|
533
|
-
if (snapshot.stream
|
|
534
|
-
currentVideo.srcObject
|
|
569
|
+
if (snapshot.stream) {
|
|
570
|
+
if (currentVideo.srcObject !== snapshot.stream) {
|
|
571
|
+
currentVideo.srcObject = snapshot.stream;
|
|
572
|
+
}
|
|
573
|
+
closePreviousStreamAfterTrack();
|
|
535
574
|
}
|
|
536
575
|
|
|
537
576
|
// effect cleanup: current video attach만 해제하고 registry stream은 유지한다.
|
|
@@ -602,17 +641,9 @@ export function useCctvRtcStream({
|
|
|
602
641
|
},
|
|
603
642
|
source: "useRtcStream",
|
|
604
643
|
});
|
|
605
|
-
if (result.isSuccess && streamIdentityKey) {
|
|
606
|
-
logCctvDebugEvent({
|
|
607
|
-
event: "reconnect-stream:close-identity",
|
|
608
|
-
payload: resultDebugState.debugBasePayload,
|
|
609
|
-
source: "useRtcStream",
|
|
610
|
-
});
|
|
611
|
-
streamRegistry.closeByIdentity(streamIdentityKey);
|
|
612
|
-
}
|
|
613
644
|
return result;
|
|
614
645
|
},
|
|
615
|
-
[refetchRtcToken
|
|
646
|
+
[refetchRtcToken],
|
|
616
647
|
);
|
|
617
648
|
|
|
618
649
|
const canReconnect = useMemo(() => {
|
|
@@ -681,18 +712,18 @@ export function useCctvRtcStream({
|
|
|
681
712
|
? getIsLive({
|
|
682
713
|
cam,
|
|
683
714
|
connectionState: displayConnectionState,
|
|
684
|
-
isTokenLoading,
|
|
715
|
+
isTokenLoading: displayIsTokenLoading,
|
|
685
716
|
isTokenError,
|
|
686
|
-
isStreaming,
|
|
717
|
+
isStreaming: displayIsStreaming,
|
|
687
718
|
streamError,
|
|
688
719
|
})
|
|
689
720
|
: false,
|
|
690
721
|
[
|
|
691
722
|
cam,
|
|
692
723
|
displayConnectionState,
|
|
693
|
-
|
|
724
|
+
displayIsTokenLoading,
|
|
694
725
|
isTokenError,
|
|
695
|
-
|
|
726
|
+
displayIsStreaming,
|
|
696
727
|
streamError,
|
|
697
728
|
],
|
|
698
729
|
);
|
|
@@ -706,7 +737,10 @@ export function useCctvRtcStream({
|
|
|
706
737
|
connectionState,
|
|
707
738
|
displayConnectionState,
|
|
708
739
|
hasConnected,
|
|
740
|
+
isPostConnectedReplacementLoading,
|
|
709
741
|
isPostConnectedReconnectReady,
|
|
742
|
+
displayIsStreaming,
|
|
743
|
+
displayIsTokenLoading,
|
|
710
744
|
isStreaming,
|
|
711
745
|
isTokenError,
|
|
712
746
|
isTokenLoading,
|
|
@@ -721,7 +755,10 @@ export function useCctvRtcStream({
|
|
|
721
755
|
connectionState,
|
|
722
756
|
debugBasePayload,
|
|
723
757
|
displayConnectionState,
|
|
758
|
+
displayIsStreaming,
|
|
759
|
+
displayIsTokenLoading,
|
|
724
760
|
hasConnected,
|
|
761
|
+
isPostConnectedReplacementLoading,
|
|
725
762
|
isPostConnectedReconnectReady,
|
|
726
763
|
isStreaming,
|
|
727
764
|
isTokenError,
|
|
@@ -770,8 +807,8 @@ export function useCctvRtcStream({
|
|
|
770
807
|
videoRef,
|
|
771
808
|
connectionState: displayConnectionState,
|
|
772
809
|
streamError,
|
|
773
|
-
isStreaming,
|
|
774
|
-
isTokenLoading,
|
|
810
|
+
isStreaming: displayIsStreaming,
|
|
811
|
+
isTokenLoading: displayIsTokenLoading,
|
|
775
812
|
isTokenError,
|
|
776
813
|
canReconnect,
|
|
777
814
|
refetchToken,
|
package/src/cctv/types/hook.ts
CHANGED
|
@@ -60,8 +60,8 @@ export interface UseCctvRtcStreamError {
|
|
|
60
60
|
/**
|
|
61
61
|
* CCTV; useCctvRtcStream 연결 상태
|
|
62
62
|
* @property {RTCPeerConnectionState} connectionState WebRTC 연결 상태
|
|
63
|
-
* @property {boolean} isStreaming startWhepStream 진행 여부
|
|
64
|
-
* @property {boolean} isTokenLoading 토큰 발급 요청 진행 여부
|
|
63
|
+
* @property {boolean} isStreaming UI 표시 기준 startWhepStream 진행 여부
|
|
64
|
+
* @property {boolean} isTokenLoading UI 표시 기준 토큰 발급 요청 진행 여부
|
|
65
65
|
* @property {boolean} isTokenError 토큰 발급 실패 여부
|
|
66
66
|
* @property {string | null} streamError 스트림 오류 메시지
|
|
67
67
|
*/
|
|
@@ -71,11 +71,17 @@ export interface UseCctvRtcStreamState extends UseCctvRtcStreamError {
|
|
|
71
71
|
*/
|
|
72
72
|
connectionState: RTCPeerConnectionState;
|
|
73
73
|
/**
|
|
74
|
-
* startWhepStream 진행 여부
|
|
74
|
+
* UI 표시 기준 startWhepStream 진행 여부
|
|
75
|
+
* @desc
|
|
76
|
+
* 최초 연결 전에는 실제 진행 상태를 나타낸다. 이미 한 번 연결된 stream을 교체하는 동안에는
|
|
77
|
+
* 기존 화면과 live count를 유지하기 위해 false로 smoothing될 수 있다.
|
|
75
78
|
*/
|
|
76
79
|
isStreaming: boolean;
|
|
77
80
|
/**
|
|
78
|
-
* 토큰 발급 요청 진행 여부
|
|
81
|
+
* UI 표시 기준 토큰 발급 요청 진행 여부
|
|
82
|
+
* @desc
|
|
83
|
+
* 최초 연결 전에는 실제 요청 상태를 나타낸다. 이미 한 번 연결된 stream의 재연결 token
|
|
84
|
+
* 재발급 중에는 기존 화면과 live count를 유지하기 위해 false로 smoothing될 수 있다.
|
|
79
85
|
*/
|
|
80
86
|
isTokenLoading: boolean;
|
|
81
87
|
}
|
|
@@ -85,8 +91,8 @@ export interface UseCctvRtcStreamState extends UseCctvRtcStreamError {
|
|
|
85
91
|
* @property {React.RefObject<HTMLVideoElement>} videoRef <video /> ref
|
|
86
92
|
* @property {RTCPeerConnectionState} connectionState WebRTC 연결 상태
|
|
87
93
|
* @property {string | null} streamError 스트림 오류 메시지
|
|
88
|
-
* @property {boolean} isStreaming startWhepStream 진행 여부
|
|
89
|
-
* @property {boolean} isTokenLoading 토큰 발급 요청 진행 여부
|
|
94
|
+
* @property {boolean} isStreaming UI 표시 기준 startWhepStream 진행 여부
|
|
95
|
+
* @property {boolean} isTokenLoading UI 표시 기준 토큰 발급 요청 진행 여부
|
|
90
96
|
* @property {boolean} isTokenError 토큰 발급 실패 여부
|
|
91
97
|
* @property {boolean} canReconnect UDS 내부 허용 사유와 grace/stagger를 통과해 재연결 호출이 가능한지 여부
|
|
92
98
|
* @property {() => Promise<void>} refetchToken 토큰 재발급 함수
|
|
@@ -7,8 +7,8 @@ import type { UseCctvRtcStreamError, UseCctvRtcStreamState } from "./hook";
|
|
|
7
7
|
* @property {boolean} [hasCamProp] 직접 cam prop을 받은 경우 true
|
|
8
8
|
* @property {boolean} [isFetching] 서버에서 cam 리스트를 가져오는 중인지 여부
|
|
9
9
|
* @property {RTCPeerConnectionState} connectionState WebRTC 연결 상태
|
|
10
|
-
* @property {boolean} isStreaming startWhepStream 진행 여부
|
|
11
|
-
* @property {boolean} isTokenLoading 토큰 발급 요청 진행 여부
|
|
10
|
+
* @property {boolean} isStreaming UI 표시 기준 startWhepStream 진행 여부
|
|
11
|
+
* @property {boolean} isTokenLoading UI 표시 기준 토큰 발급 요청 진행 여부
|
|
12
12
|
* @property {boolean} isTokenError 토큰 발급 실패 여부
|
|
13
13
|
* @property {string | null} streamError 스트림 오류 메시지
|
|
14
14
|
* @property {boolean} [canReconnect] UDS 내부 허용 사유와 grace/stagger를 통과해 재연결 호출이 가능한지 여부
|
|
@@ -36,8 +36,8 @@ export interface CctvVideoOverlayMessageParams extends UseCctvRtcStreamState {
|
|
|
36
36
|
* CCTV; getIsLive() params
|
|
37
37
|
* @property {CctvCompanyCameraData} [cam] 카메라 데이터
|
|
38
38
|
* @property {RTCPeerConnectionState} connectionState WebRTC 연결 상태
|
|
39
|
-
* @property {boolean} isStreaming startWhepStream 진행 여부
|
|
40
|
-
* @property {boolean} isTokenLoading 토큰 발급 요청 진행 여부
|
|
39
|
+
* @property {boolean} isStreaming UI 표시 기준 startWhepStream 진행 여부
|
|
40
|
+
* @property {boolean} isTokenLoading UI 표시 기준 토큰 발급 요청 진행 여부
|
|
41
41
|
* @property {boolean} isTokenError 토큰 발급 실패 여부
|
|
42
42
|
* @property {string | null} streamError 스트림 오류 메시지
|
|
43
43
|
*/
|
|
@@ -32,8 +32,8 @@ const RTC_SESSION_ENDED_RECONNECT_STATES = new Set<RTCPeerConnectionState>([
|
|
|
32
32
|
* @property {boolean} [hasCamProp] 직접 cam prop을 받은 경우 true
|
|
33
33
|
* @property {boolean} [isFetching] 서버에서 cam 리스트를 가져오는 중인지 여부
|
|
34
34
|
* @property {RTCPeerConnectionState} connectionState WebRTC 연결 상태
|
|
35
|
-
* @property {boolean} isStreaming startWhepStream 진행 여부
|
|
36
|
-
* @property {boolean} isTokenLoading 토큰 발급 요청 진행 여부
|
|
35
|
+
* @property {boolean} isStreaming UI 표시 기준 startWhepStream 진행 여부
|
|
36
|
+
* @property {boolean} isTokenLoading UI 표시 기준 토큰 발급 요청 진행 여부
|
|
37
37
|
* @property {boolean} isTokenError 토큰 발급 실패 여부
|
|
38
38
|
* @property {string | null} streamError 스트림 오류 메시지
|
|
39
39
|
* @returns {string | null} 상태에 따른 메시지 반환 (문제가 없으면 null)
|
|
@@ -73,8 +73,8 @@ export function getOverlayMessage({
|
|
|
73
73
|
* @param {CctvVideoLiveParams} params 상태 파라미터
|
|
74
74
|
* @property {CctvCompanyCameraData} [cam] 카메라 데이터
|
|
75
75
|
* @property {RTCPeerConnectionState} connectionState WebRTC 연결 상태
|
|
76
|
-
* @property {boolean} isStreaming startWhepStream 진행 여부
|
|
77
|
-
* @property {boolean} isTokenLoading 토큰 발급 요청 진행 여부
|
|
76
|
+
* @property {boolean} isStreaming UI 표시 기준 startWhepStream 진행 여부
|
|
77
|
+
* @property {boolean} isTokenLoading UI 표시 기준 토큰 발급 요청 진행 여부
|
|
78
78
|
* @property {boolean} isTokenError 토큰 발급 실패 여부
|
|
79
79
|
* @property {string | null} streamError 스트림 오류 메시지
|
|
80
80
|
* @returns {boolean} 라이브 상태 여부 반환
|