|
| 1 | +import { RecordingPictureInPicturePanel } from '@/features/recordings/components/RecordingPictureInPicturePanel.tsx' |
1 | 2 | import { SignalLevelMeter } from '@/features/recordings/components/SignalLevelMeter.tsx' |
| 3 | +import { useDocumentPictureInPicture } from '@/features/recordings/hooks/useDocumentPictureInPicture.tsx' |
2 | 4 | import { useRecordingController } from '@/features/recordings/hooks/useRecordingController.ts' |
3 | 5 | import { useDisablePageRefresh } from '@/hooks/disablePageRegresh.ts' |
4 | 6 | import { Button } from '@gouvfr-lasuite/cunningham-react' |
@@ -46,34 +48,48 @@ export default function RecordComponent() { |
46 | 48 | resumeRecording, |
47 | 49 | stopAndDispose, |
48 | 50 | switchAudioInput, |
| 51 | + tabAudioSupported, |
| 52 | + tabAudioActive, |
| 53 | + tabAudioLabel, |
| 54 | + enableTabAudioCapture, |
| 55 | + disableTabAudioCapture, |
49 | 56 | } = useRecordingController(true) |
50 | 57 |
|
51 | 58 | const [, navigate] = useLocation() |
52 | 59 | const isRecordingInProgress = |
53 | 60 | recorderState === 'recording' || recorderState === 'paused' |
54 | 61 | const isStarting = recorderState === 'starting' |
55 | 62 | const isStopping = recorderState === 'stopping' |
56 | | - |
57 | 63 | const isBusy = isStarting || isRecordingInProgress || isStopping |
| 64 | + const durationLabel = formatDuration(recordingDurationMs) |
| 65 | + |
58 | 66 | useDisablePageRefresh(isBusy, t('record:preventGoBackAlert')) |
59 | 67 |
|
60 | 68 | const [openInputSelection, setOpenInputSelection] = useState(false) |
| 69 | + const audioInputLabels = useMemo( |
| 70 | + () => |
| 71 | + audioInputs.map((input, index) => ({ |
| 72 | + deviceId: input.deviceId, |
| 73 | + label: |
| 74 | + input.label || |
| 75 | + t('record:source.fallbackLabel', { |
| 76 | + index: index + 1, |
| 77 | + }), |
| 78 | + })), |
| 79 | + [audioInputs, t] |
| 80 | + ) |
61 | 81 | const audioInputOptions = useMemo<DropdownMenuOption[]>( |
62 | 82 | () => |
63 | | - audioInputs.map( |
64 | | - (input, index) => |
| 83 | + audioInputLabels.map( |
| 84 | + (input) => |
65 | 85 | ({ |
66 | 86 | value: input.deviceId, |
67 | 87 | isChecked: input.deviceId === selectedAudioInputId, |
68 | 88 | callback: () => switchAudioInput(input.deviceId), |
69 | | - label: |
70 | | - input.label || |
71 | | - t('record:source.fallbackLabel', { |
72 | | - index: index + 1, |
73 | | - }), |
| 89 | + label: input.label, |
74 | 90 | }) satisfies DropdownMenuOption |
75 | 91 | ), |
76 | | - [audioInputs, selectedAudioInputId, switchAudioInput, t] |
| 92 | + [audioInputLabels, selectedAudioInputId, switchAudioInput] |
77 | 93 | ) |
78 | 94 | const selectedSourceLabel = useMemo( |
79 | 95 | () => audioInputOptions.find((option) => option.isChecked)?.label, |
@@ -144,6 +160,57 @@ export default function RecordComponent() { |
144 | 160 | } |
145 | 161 | }, [isRecordingInProgress, navigate, stopAndDispose]) |
146 | 162 |
|
| 163 | + const handleTabAudioAction = useCallback(async () => { |
| 164 | + if (tabAudioActive) { |
| 165 | + await disableTabAudioCapture() |
| 166 | + return |
| 167 | + } |
| 168 | + await enableTabAudioCapture() |
| 169 | + }, [disableTabAudioCapture, enableTabAudioCapture, tabAudioActive]) |
| 170 | + |
| 171 | + const { |
| 172 | + portal: pictureInPicturePortal, |
| 173 | + openWindow: openPictureInPictureWindow, |
| 174 | + isOpen: isPictureInPictureOpen, |
| 175 | + supported: isPictureInPictureSupported, |
| 176 | + } = useDocumentPictureInPicture({ |
| 177 | + enabled: isRecordingInProgress, |
| 178 | + width: 320, |
| 179 | + height: 280, |
| 180 | + children: ( |
| 181 | + <RecordingPictureInPicturePanel |
| 182 | + statusLabel={statusLabel} |
| 183 | + durationLabel={durationLabel} |
| 184 | + analyserNode={analyserNode} |
| 185 | + isRecording={recorderState === 'recording'} |
| 186 | + isPaused={isPaused} |
| 187 | + onPauseResume={() => |
| 188 | + void (isPaused ? resumeRecording() : pauseRecording()) |
| 189 | + } |
| 190 | + onStop={() => void handleStop()} |
| 191 | + audioInputs={audioInputLabels} |
| 192 | + selectedAudioInputId={selectedAudioInputId} |
| 193 | + onSelectAudioInput={(deviceId) => void switchAudioInput(deviceId)} |
| 194 | + sourceLabel={t('record:pip.source')} |
| 195 | + tabAudioSupported={tabAudioSupported} |
| 196 | + tabAudioLabel={tabAudioLabel} |
| 197 | + tabAudioButtonLabel={ |
| 198 | + tabAudioActive |
| 199 | + ? t('record:tabAudio.remove') |
| 200 | + : t('record:tabAudio.add') |
| 201 | + } |
| 202 | + onTabAudioAction={() => void handleTabAudioAction()} |
| 203 | + soundLevelAriaLabel={t('record:source.signalLevelAriaLabel')} |
| 204 | + noSoundDetectedLabel={t('record:source.noSoundDetected')} |
| 205 | + lowSoundLabel={t('record:source.lowSound')} |
| 206 | + soundOkLabel={t('record:source.soundOk')} |
| 207 | + pauseLabel={t('record:pauseRecording')} |
| 208 | + resumeLabel={t('record:resumeRecording')} |
| 209 | + stopLabel={t('record:stopRecording')} |
| 210 | + /> |
| 211 | + ), |
| 212 | + }) |
| 213 | + |
147 | 214 | return ( |
148 | 215 | <div className="record-component"> |
149 | 216 | <div className="record-component__content"> |
@@ -186,7 +253,7 @@ export default function RecordComponent() { |
186 | 253 | recorderState === 'paused' ? 'paused' : '' |
187 | 254 | )} |
188 | 255 | > |
189 | | - {formatDuration(recordingDurationMs)} |
| 256 | + {durationLabel} |
190 | 257 | </p> |
191 | 258 | </div> |
192 | 259 | <div className="record-component__footer"> |
@@ -229,6 +296,38 @@ export default function RecordComponent() { |
229 | 296 | </Button> |
230 | 297 | )} |
231 | 298 |
|
| 299 | + {isRecordingInProgress && tabAudioSupported && ( |
| 300 | + <Button |
| 301 | + color="neutral" |
| 302 | + variant="tertiary" |
| 303 | + size="nano" |
| 304 | + onClick={() => void handleTabAudioAction()} |
| 305 | + > |
| 306 | + {tabAudioActive |
| 307 | + ? t('record:tabAudio.remove') |
| 308 | + : t('record:tabAudio.add')} |
| 309 | + </Button> |
| 310 | + )} |
| 311 | + |
| 312 | + {isRecordingInProgress && |
| 313 | + isPictureInPictureSupported && |
| 314 | + !isPictureInPictureOpen && ( |
| 315 | + <Button |
| 316 | + color="neutral" |
| 317 | + variant="tertiary" |
| 318 | + size="nano" |
| 319 | + onClick={() => void openPictureInPictureWindow()} |
| 320 | + > |
| 321 | + {t('record:pip.open')} |
| 322 | + </Button> |
| 323 | + )} |
| 324 | + |
| 325 | + {tabAudioSupported && tabAudioActive && tabAudioLabel && ( |
| 326 | + <p className="record-component__tab-audio-label"> |
| 327 | + {t('record:tabAudio.activeLabel', { label: tabAudioLabel })} |
| 328 | + </p> |
| 329 | + )} |
| 330 | + |
232 | 331 | <div className="record-component__controls"> |
233 | 332 | {isRecordingInProgress && ( |
234 | 333 | <> |
@@ -275,6 +374,7 @@ export default function RecordComponent() { |
275 | 374 | {recordingError} |
276 | 375 | </div> |
277 | 376 | )} |
| 377 | + {pictureInPicturePortal} |
278 | 378 | </div> |
279 | 379 | ) |
280 | 380 | } |
0 commit comments