@stream-io/video-react-sdk 1.41.0 → 1.42.0
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/CHANGELOG.md +22 -6
- package/dist/embedded.cjs.js +1 -1
- package/dist/esm/embedded.js +3 -0
- package/dist/esm/embedded.js.map +1 -0
- package/dist/esm/index.js +92 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/src/components/Avatar/Avatar.js +14 -0
- package/dist/esm/src/components/Avatar/Avatar.js.map +1 -0
- package/dist/{background-filters-D2U5UBbH.es.js → esm/src/components/BackgroundFilters/BackgroundFilters.js} +1 -1
- package/dist/esm/src/components/BackgroundFilters/BackgroundFilters.js.map +1 -0
- package/dist/esm/src/components/BackgroundFilters/BackgroundFiltersProvider.js +33 -0
- package/dist/esm/src/components/BackgroundFilters/BackgroundFiltersProvider.js.map +1 -0
- package/dist/esm/src/components/Button/CompositeButton.js +29 -0
- package/dist/esm/src/components/Button/CompositeButton.js.map +1 -0
- package/dist/esm/src/components/Button/IconButton.js +18 -0
- package/dist/esm/src/components/Button/IconButton.js.map +1 -0
- package/dist/esm/src/components/Button/TextButton.js +8 -0
- package/dist/esm/src/components/Button/TextButton.js.map +1 -0
- package/dist/esm/src/components/CallControls/AcceptCallButton.js +28 -0
- package/dist/esm/src/components/CallControls/AcceptCallButton.js.map +1 -0
- package/dist/esm/src/components/CallControls/CallControls.js +21 -0
- package/dist/esm/src/components/CallControls/CallControls.js.map +1 -0
- package/dist/esm/src/components/CallControls/CallStatsButton.js +18 -0
- package/dist/esm/src/components/CallControls/CallStatsButton.js.map +1 -0
- package/dist/esm/src/components/CallControls/CancelCallButton.js +78 -0
- package/dist/esm/src/components/CallControls/CancelCallButton.js.map +1 -0
- package/dist/esm/src/components/CallControls/ReactionsButton.js +61 -0
- package/dist/esm/src/components/CallControls/ReactionsButton.js.map +1 -0
- package/dist/esm/src/components/CallControls/RecordCallButton.js +64 -0
- package/dist/esm/src/components/CallControls/RecordCallButton.js.map +1 -0
- package/dist/esm/src/components/CallControls/ScreenShareButton.js +44 -0
- package/dist/esm/src/components/CallControls/ScreenShareButton.js.map +1 -0
- package/dist/esm/src/components/CallControls/ToggleAudioButton.js +72 -0
- package/dist/esm/src/components/CallControls/ToggleAudioButton.js.map +1 -0
- package/dist/esm/src/components/CallControls/ToggleAudioOutputButton.js +27 -0
- package/dist/esm/src/components/CallControls/ToggleAudioOutputButton.js.map +1 -0
- package/dist/esm/src/components/CallControls/ToggleVideoButton.js +81 -0
- package/dist/esm/src/components/CallControls/ToggleVideoButton.js.map +1 -0
- package/dist/esm/src/components/CallParticipantsList/BlockedUserListing.js +23 -0
- package/dist/esm/src/components/CallParticipantsList/BlockedUserListing.js.map +1 -0
- package/dist/esm/src/components/CallParticipantsList/CallParticipantListHeader.js +15 -0
- package/dist/esm/src/components/CallParticipantsList/CallParticipantListHeader.js.map +1 -0
- package/dist/esm/src/components/CallParticipantsList/CallParticipantListing.js +7 -0
- package/dist/esm/src/components/CallParticipantsList/CallParticipantListing.js.map +1 -0
- package/dist/esm/src/components/CallParticipantsList/CallParticipantListingItem.js +46 -0
- package/dist/esm/src/components/CallParticipantsList/CallParticipantListingItem.js.map +1 -0
- package/dist/esm/src/components/CallParticipantsList/CallParticipantsList.js +81 -0
- package/dist/esm/src/components/CallParticipantsList/CallParticipantsList.js.map +1 -0
- package/dist/esm/src/components/CallParticipantsList/EmptyParticipantSearchList.js +10 -0
- package/dist/esm/src/components/CallParticipantsList/EmptyParticipantSearchList.js.map +1 -0
- package/dist/esm/src/components/CallPreview/CallPreview.js +24 -0
- package/dist/esm/src/components/CallPreview/CallPreview.js.map +1 -0
- package/dist/esm/src/components/CallRecordingList/CallRecordingList.js +12 -0
- package/dist/esm/src/components/CallRecordingList/CallRecordingList.js.map +1 -0
- package/dist/esm/src/components/CallRecordingList/CallRecordingListHeader.js +12 -0
- package/dist/esm/src/components/CallRecordingList/CallRecordingListHeader.js.map +1 -0
- package/dist/esm/src/components/CallRecordingList/CallRecordingListItem.js +14 -0
- package/dist/esm/src/components/CallRecordingList/CallRecordingListItem.js.map +1 -0
- package/dist/esm/src/components/CallRecordingList/EmptyCallRecordingListing.js +8 -0
- package/dist/esm/src/components/CallRecordingList/EmptyCallRecordingListing.js.map +1 -0
- package/dist/esm/src/components/CallRecordingList/LoadingCallRecordingListing.js +10 -0
- package/dist/esm/src/components/CallRecordingList/LoadingCallRecordingListing.js.map +1 -0
- package/dist/esm/src/components/CallStats/CallStats.js +180 -0
- package/dist/esm/src/components/CallStats/CallStats.js.map +1 -0
- package/dist/{latency-chart-Bj5OSYzg.es.js → esm/src/components/CallStats/CallStatsLatencyChart.js} +1 -1
- package/dist/esm/src/components/CallStats/CallStatsLatencyChart.js.map +1 -0
- package/dist/esm/src/components/DeviceSettings/AudioVolumeIndicator.js +27 -0
- package/dist/esm/src/components/DeviceSettings/AudioVolumeIndicator.js.map +1 -0
- package/dist/esm/src/components/DeviceSettings/DeviceAudioPreviewItem.js +42 -0
- package/dist/esm/src/components/DeviceSettings/DeviceAudioPreviewItem.js.map +1 -0
- package/dist/esm/src/components/DeviceSettings/DeviceSelector.js +67 -0
- package/dist/esm/src/components/DeviceSettings/DeviceSelector.js.map +1 -0
- package/dist/esm/src/components/DeviceSettings/DeviceSelectorAudio.js +26 -0
- package/dist/esm/src/components/DeviceSettings/DeviceSelectorAudio.js.map +1 -0
- package/dist/esm/src/components/DeviceSettings/DeviceSelectorVideo.js +15 -0
- package/dist/esm/src/components/DeviceSettings/DeviceSelectorVideo.js.map +1 -0
- package/dist/esm/src/components/DeviceSettings/DeviceSettings.js +26 -0
- package/dist/esm/src/components/DeviceSettings/DeviceSettings.js.map +1 -0
- package/dist/esm/src/components/DeviceSettings/DeviceVideoPreviewItem.js +42 -0
- package/dist/esm/src/components/DeviceSettings/DeviceVideoPreviewItem.js.map +1 -0
- package/dist/esm/src/components/DeviceSettings/SpeakerTest.js +57 -0
- package/dist/esm/src/components/DeviceSettings/SpeakerTest.js.map +1 -0
- package/dist/esm/src/components/DropdownSelect/DropdownSelect.js +79 -0
- package/dist/esm/src/components/DropdownSelect/DropdownSelect.js.map +1 -0
- package/dist/esm/src/components/Icon/Icon.js +7 -0
- package/dist/esm/src/components/Icon/Icon.js.map +1 -0
- package/dist/esm/src/components/LoadingIndicator/LoadingIndicator.js +9 -0
- package/dist/esm/src/components/LoadingIndicator/LoadingIndicator.js.map +1 -0
- package/dist/esm/src/components/Menu/GenericMenu.js +19 -0
- package/dist/esm/src/components/Menu/GenericMenu.js.map +1 -0
- package/dist/esm/src/components/Menu/MenuToggle.js +75 -0
- package/dist/esm/src/components/Menu/MenuToggle.js.map +1 -0
- package/dist/esm/src/components/NoiseCancellation/NoiseCancellationProvider.js +96 -0
- package/dist/esm/src/components/NoiseCancellation/NoiseCancellationProvider.js.map +1 -0
- package/dist/esm/src/components/Notification/MicCaptureErrorNotification.js +23 -0
- package/dist/esm/src/components/Notification/MicCaptureErrorNotification.js.map +1 -0
- package/dist/esm/src/components/Notification/Notification.js +31 -0
- package/dist/esm/src/components/Notification/Notification.js.map +1 -0
- package/dist/esm/src/components/Notification/PermissionNotification.js +32 -0
- package/dist/esm/src/components/Notification/PermissionNotification.js.map +1 -0
- package/dist/esm/src/components/Notification/RecordingInProgressNotification.js +23 -0
- package/dist/esm/src/components/Notification/RecordingInProgressNotification.js.map +1 -0
- package/dist/esm/src/components/Notification/SpeakingWhileMutedNotification.js +14 -0
- package/dist/esm/src/components/Notification/SpeakingWhileMutedNotification.js.map +1 -0
- package/dist/esm/src/components/Permissions/PermissionRequests.js +99 -0
- package/dist/esm/src/components/Permissions/PermissionRequests.js.map +1 -0
- package/dist/esm/src/components/Reaction/Reaction.js +32 -0
- package/dist/esm/src/components/Reaction/Reaction.js.map +1 -0
- package/dist/esm/src/components/RingingCall/RingingCall.js +48 -0
- package/dist/esm/src/components/RingingCall/RingingCall.js.map +1 -0
- package/dist/esm/src/components/RingingCall/RingingCallControls.js +34 -0
- package/dist/esm/src/components/RingingCall/RingingCallControls.js.map +1 -0
- package/dist/esm/src/components/Search/SearchInput.js +27 -0
- package/dist/esm/src/components/Search/SearchInput.js.map +1 -0
- package/dist/esm/src/components/Search/SearchResults.js +15 -0
- package/dist/esm/src/components/Search/SearchResults.js.map +1 -0
- package/dist/esm/src/components/Search/hooks/useSearch.js +38 -0
- package/dist/esm/src/components/Search/hooks/useSearch.js.map +1 -0
- package/dist/esm/src/components/StreamTheme/StreamTheme.js +9 -0
- package/dist/esm/src/components/StreamTheme/StreamTheme.js.map +1 -0
- package/dist/esm/src/components/Tooltip/Tooltip.js +29 -0
- package/dist/esm/src/components/Tooltip/Tooltip.js.map +1 -0
- package/dist/esm/src/components/Tooltip/WithTooltip.js +15 -0
- package/dist/esm/src/components/Tooltip/WithTooltip.js.map +1 -0
- package/dist/esm/src/components/Tooltip/hooks/useEnterLeaveHandlers.js +17 -0
- package/dist/esm/src/components/Tooltip/hooks/useEnterLeaveHandlers.js.map +1 -0
- package/dist/esm/src/components/VideoPreview/VideoPreview.js +37 -0
- package/dist/esm/src/components/VideoPreview/VideoPreview.js.map +1 -0
- package/dist/esm/src/core/components/Audio/Audio.js +22 -0
- package/dist/esm/src/core/components/Audio/Audio.js.map +1 -0
- package/dist/esm/src/core/components/Audio/ParticipantsAudio.js +23 -0
- package/dist/esm/src/core/components/Audio/ParticipantsAudio.js.map +1 -0
- package/dist/esm/src/core/components/CallLayout/LivestreamLayout.js +150 -0
- package/dist/esm/src/core/components/CallLayout/LivestreamLayout.js.map +1 -0
- package/dist/esm/src/core/components/CallLayout/PaginatedGridLayout.js +79 -0
- package/dist/esm/src/core/components/CallLayout/PaginatedGridLayout.js.map +1 -0
- package/dist/esm/src/core/components/CallLayout/PipLayout/Grid.js +80 -0
- package/dist/esm/src/core/components/CallLayout/PipLayout/Grid.js.map +1 -0
- package/dist/esm/src/core/components/CallLayout/PipLayout/Host.js +13 -0
- package/dist/esm/src/core/components/CallLayout/PipLayout/Host.js.map +1 -0
- package/dist/esm/src/core/components/CallLayout/PipLayout/Pip.js +57 -0
- package/dist/esm/src/core/components/CallLayout/PipLayout/Pip.js.map +1 -0
- package/dist/esm/src/core/components/CallLayout/PipLayout/index.js +8 -0
- package/dist/esm/src/core/components/CallLayout/PipLayout/index.js.map +1 -0
- package/dist/esm/src/core/components/CallLayout/SpeakerLayout.js +103 -0
- package/dist/esm/src/core/components/CallLayout/SpeakerLayout.js.map +1 -0
- package/dist/esm/src/core/components/CallLayout/hooks.js +62 -0
- package/dist/esm/src/core/components/CallLayout/hooks.js.map +1 -0
- package/dist/esm/src/core/components/ParticipantView/DefaultParticipantViewUI.js +94 -0
- package/dist/esm/src/core/components/ParticipantView/DefaultParticipantViewUI.js.map +1 -0
- package/dist/esm/src/core/components/ParticipantView/ParticipantActionsContextMenu.js +97 -0
- package/dist/esm/src/core/components/ParticipantView/ParticipantActionsContextMenu.js.map +1 -0
- package/dist/esm/src/core/components/ParticipantView/ParticipantView.js +65 -0
- package/dist/esm/src/core/components/ParticipantView/ParticipantView.js.map +1 -0
- package/dist/esm/src/core/components/ParticipantView/ParticipantViewContext.js +7 -0
- package/dist/esm/src/core/components/ParticipantView/ParticipantViewContext.js.map +1 -0
- package/dist/esm/src/core/components/StreamCall/StreamCall.js +8 -0
- package/dist/esm/src/core/components/StreamCall/StreamCall.js.map +1 -0
- package/dist/esm/src/core/components/StreamVideo/StreamVideo.js +11 -0
- package/dist/esm/src/core/components/StreamVideo/StreamVideo.js.map +1 -0
- package/dist/esm/src/core/components/Video/BaseVideo.js +41 -0
- package/dist/esm/src/core/components/Video/BaseVideo.js.map +1 -0
- package/dist/esm/src/core/components/Video/BaseVideoPlaceholder.js +21 -0
- package/dist/esm/src/core/components/Video/BaseVideoPlaceholder.js.map +1 -0
- package/dist/esm/src/core/components/Video/DefaultPictureInPicturePlaceholder.js +12 -0
- package/dist/esm/src/core/components/Video/DefaultPictureInPicturePlaceholder.js.map +1 -0
- package/dist/esm/src/core/components/Video/DefaultVideoPlaceholder.js +12 -0
- package/dist/esm/src/core/components/Video/DefaultVideoPlaceholder.js.map +1 -0
- package/dist/esm/src/core/components/Video/Video.js +88 -0
- package/dist/esm/src/core/components/Video/Video.js.map +1 -0
- package/dist/esm/src/core/hooks/useCalculateHardLimit.js +59 -0
- package/dist/esm/src/core/hooks/useCalculateHardLimit.js.map +1 -0
- package/dist/esm/src/core/hooks/usePictureInPictureState.js +24 -0
- package/dist/esm/src/core/hooks/usePictureInPictureState.js.map +1 -0
- package/dist/esm/src/core/hooks/useTrackElementVisibility.js +14 -0
- package/dist/esm/src/core/hooks/useTrackElementVisibility.js.map +1 -0
- package/dist/esm/src/embedded/EmbeddedClientProvider.js +83 -0
- package/dist/esm/src/embedded/EmbeddedClientProvider.js.map +1 -0
- package/dist/esm/src/embedded/call/CallControls.js +65 -0
- package/dist/esm/src/embedded/call/CallControls.js.map +1 -0
- package/dist/esm/src/embedded/call/CallHeader.js +57 -0
- package/dist/esm/src/embedded/call/CallHeader.js.map +1 -0
- package/dist/esm/src/embedded/call/CallLayout.js +63 -0
- package/dist/esm/src/embedded/call/CallLayout.js.map +1 -0
- package/dist/esm/src/embedded/call/CallStateRouter.js +87 -0
- package/dist/esm/src/embedded/call/CallStateRouter.js.map +1 -0
- package/dist/esm/src/embedded/call/EmbeddedCall.js +12 -0
- package/dist/esm/src/embedded/call/EmbeddedCall.js.map +1 -0
- package/dist/esm/src/embedded/context/ConfigurationContext.js +21 -0
- package/dist/esm/src/embedded/context/ConfigurationContext.js.map +1 -0
- package/dist/esm/src/embedded/hooks/useCallDuration.js +32 -0
- package/dist/esm/src/embedded/hooks/useCallDuration.js.map +1 -0
- package/dist/esm/src/embedded/hooks/useEmbeddedClient.js +40 -0
- package/dist/esm/src/embedded/hooks/useEmbeddedClient.js.map +1 -0
- package/dist/esm/src/embedded/hooks/useInitializeCall.js +39 -0
- package/dist/esm/src/embedded/hooks/useInitializeCall.js.map +1 -0
- package/dist/esm/src/embedded/hooks/useInitializeVideoClient.js +93 -0
- package/dist/esm/src/embedded/hooks/useInitializeVideoClient.js.map +1 -0
- package/dist/esm/src/embedded/hooks/useIsLivestreamPaused.js +42 -0
- package/dist/esm/src/embedded/hooks/useIsLivestreamPaused.js.map +1 -0
- package/dist/esm/src/embedded/hooks/useLayout.js +122 -0
- package/dist/esm/src/embedded/hooks/useLayout.js.map +1 -0
- package/dist/esm/src/embedded/hooks/useNoiseCancellationLoader.js +48 -0
- package/dist/esm/src/embedded/hooks/useNoiseCancellationLoader.js.map +1 -0
- package/dist/esm/src/embedded/hooks/useWakeLock.js +34 -0
- package/dist/esm/src/embedded/hooks/useWakeLock.js.map +1 -0
- package/dist/esm/src/embedded/livestream/EmbeddedLivestream.js +11 -0
- package/dist/esm/src/embedded/livestream/EmbeddedLivestream.js.map +1 -0
- package/dist/esm/src/embedded/livestream/LivestreamUI.js +17 -0
- package/dist/esm/src/embedded/livestream/LivestreamUI.js.map +1 -0
- package/dist/esm/src/embedded/livestream/host/HostLayout.js +76 -0
- package/dist/esm/src/embedded/livestream/host/HostLayout.js.map +1 -0
- package/dist/esm/src/embedded/livestream/host/HostStateRouter.js +110 -0
- package/dist/esm/src/embedded/livestream/host/HostStateRouter.js.map +1 -0
- package/dist/esm/src/embedded/livestream/viewer/ViewerLayout.js +74 -0
- package/dist/esm/src/embedded/livestream/viewer/ViewerLayout.js.map +1 -0
- package/dist/esm/src/embedded/livestream/viewer/ViewerLobby.js +109 -0
- package/dist/esm/src/embedded/livestream/viewer/ViewerLobby.js.map +1 -0
- package/dist/esm/src/embedded/livestream/viewer/ViewerStateRouter.js +105 -0
- package/dist/esm/src/embedded/livestream/viewer/ViewerStateRouter.js.map +1 -0
- package/dist/esm/src/embedded/shared/BlurToggleButton/BlurToggleButton.js +65 -0
- package/dist/esm/src/embedded/shared/BlurToggleButton/BlurToggleButton.js.map +1 -0
- package/dist/esm/src/embedded/shared/CallFeedback/CallEndedScreen.js +34 -0
- package/dist/esm/src/embedded/shared/CallFeedback/CallEndedScreen.js.map +1 -0
- package/dist/esm/src/embedded/shared/CallFeedback/CallFeedback.js +35 -0
- package/dist/esm/src/embedded/shared/CallFeedback/CallFeedback.js.map +1 -0
- package/dist/esm/src/embedded/shared/CallFeedback/RatingScreen.js +18 -0
- package/dist/esm/src/embedded/shared/CallFeedback/RatingScreen.js.map +1 -0
- package/dist/esm/src/embedded/shared/CallFeedback/StarRating.js +37 -0
- package/dist/esm/src/embedded/shared/CallFeedback/StarRating.js.map +1 -0
- package/dist/esm/src/embedded/shared/CallFeedback/ThankYouScreen.js +34 -0
- package/dist/esm/src/embedded/shared/CallFeedback/ThankYouScreen.js.map +1 -0
- package/dist/esm/src/embedded/shared/ConnectionNotification/ConnectionNotification.js +56 -0
- package/dist/esm/src/embedded/shared/ConnectionNotification/ConnectionNotification.js.map +1 -0
- package/dist/esm/src/embedded/shared/EmbeddedParticipantViewUI/EmbeddedParticipantViewUI.js +42 -0
- package/dist/esm/src/embedded/shared/EmbeddedParticipantViewUI/EmbeddedParticipantViewUI.js.map +1 -0
- package/dist/esm/src/embedded/shared/JoinError/JoinError.js +34 -0
- package/dist/esm/src/embedded/shared/JoinError/JoinError.js.map +1 -0
- package/dist/esm/src/embedded/shared/Lobby/DeviceControls.js +44 -0
- package/dist/esm/src/embedded/shared/Lobby/DeviceControls.js.map +1 -0
- package/dist/esm/src/embedded/shared/Lobby/DisabledDeviceButton.js +31 -0
- package/dist/esm/src/embedded/shared/Lobby/DisabledDeviceButton.js.map +1 -0
- package/dist/esm/src/embedded/shared/Lobby/Lobby.js +45 -0
- package/dist/esm/src/embedded/shared/Lobby/Lobby.js.map +1 -0
- package/dist/esm/src/embedded/shared/Lobby/ToggleCameraButton.js +40 -0
- package/dist/esm/src/embedded/shared/Lobby/ToggleCameraButton.js.map +1 -0
- package/dist/esm/src/embedded/shared/Lobby/ToggleMicButton.js +41 -0
- package/dist/esm/src/embedded/shared/Lobby/ToggleMicButton.js.map +1 -0
- package/dist/esm/src/embedded/shared/Lobby/VideoPreviewFallbacks.js +50 -0
- package/dist/esm/src/embedded/shared/Lobby/VideoPreviewFallbacks.js.map +1 -0
- package/dist/esm/src/embedded/shared/ViewersCount/ViewersCount.js +31 -0
- package/dist/esm/src/embedded/shared/ViewersCount/ViewersCount.js.map +1 -0
- package/dist/esm/src/hooks/useDeviceList.js +40 -0
- package/dist/esm/src/hooks/useDeviceList.js.map +1 -0
- package/dist/esm/src/hooks/useDragToScroll.js +116 -0
- package/dist/esm/src/hooks/useDragToScroll.js.map +1 -0
- package/dist/esm/src/hooks/useFloatingUIPreset.js +34 -0
- package/dist/esm/src/hooks/useFloatingUIPreset.js.map +1 -0
- package/dist/esm/src/hooks/useLoopbackRecording.js +138 -0
- package/dist/esm/src/hooks/useLoopbackRecording.js.map +1 -0
- package/dist/esm/src/hooks/useModeration.js +135 -0
- package/dist/esm/src/hooks/useModeration.js.map +1 -0
- package/dist/esm/src/hooks/usePersistedDevicePreferences.js +16 -0
- package/dist/esm/src/hooks/usePersistedDevicePreferences.js.map +1 -0
- package/dist/esm/src/hooks/useRequestPermission.js +41 -0
- package/dist/esm/src/hooks/useRequestPermission.js.map +1 -0
- package/dist/esm/src/hooks/useScrollPosition.js +66 -0
- package/dist/esm/src/hooks/useScrollPosition.js.map +1 -0
- package/dist/esm/src/translations/en.json.js +177 -0
- package/dist/esm/src/translations/en.json.js.map +1 -0
- package/dist/esm/src/translations/index.js +6 -0
- package/dist/esm/src/translations/index.js.map +1 -0
- package/dist/esm/src/utilities/applyElementToRef.js +10 -0
- package/dist/esm/src/utilities/applyElementToRef.js.map +1 -0
- package/dist/esm/src/utilities/callControlHandler.js +29 -0
- package/dist/esm/src/utilities/callControlHandler.js.map +1 -0
- package/dist/esm/src/utilities/chunk.js +7 -0
- package/dist/esm/src/utilities/chunk.js.map +1 -0
- package/dist/esm/src/utilities/filter.js +94 -0
- package/dist/esm/src/utilities/filter.js.map +1 -0
- package/dist/esm/src/utilities/isComponentType.js +10 -0
- package/dist/esm/src/utilities/isComponentType.js.map +1 -0
- package/dist/esm/src/utilities/normalizeString.js +11 -0
- package/dist/esm/src/utilities/normalizeString.js.map +1 -0
- package/dist/esm/src/wrappers/LivestreamPlayer/LivestreamPlayer.js +105 -0
- package/dist/esm/src/wrappers/LivestreamPlayer/LivestreamPlayer.js.map +1 -0
- package/dist/index.cjs.js +135 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/src/hooks/index.d.ts +1 -0
- package/dist/src/hooks/useLoopbackRecording.d.ts +23 -0
- package/index.ts +4 -0
- package/package.json +14 -10
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useLoopbackRecording.ts +207 -0
- package/dist/background-filters-D2U5UBbH.es.js.map +0 -1
- package/dist/embedded-BackgroundFilters-D2U5UBbH.es.js +0 -420
- package/dist/embedded-BackgroundFilters-D2U5UBbH.es.js.map +0 -1
- package/dist/embedded-CallStatsLatencyChart-Bj5OSYzg.es.js +0 -57
- package/dist/embedded-CallStatsLatencyChart-Bj5OSYzg.es.js.map +0 -1
- package/dist/embedded.es.js +0 -3536
- package/dist/embedded.es.js.map +0 -1
- package/dist/index.es.js +0 -3291
- package/dist/index.es.js.map +0 -1
- package/dist/latency-chart-Bj5OSYzg.es.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useI18n, useCallStateHooks, useConnectedUser } from '@stream-io/video-react-bindings';
|
|
3
|
+
import { Avatar } from '../../../components/Avatar/Avatar.js';
|
|
4
|
+
import '../../../components/BackgroundFilters/BackgroundFiltersProvider.js';
|
|
5
|
+
import '../../../components/Button/CompositeButton.js';
|
|
6
|
+
import '../../../components/Button/IconButton.js';
|
|
7
|
+
import 'react';
|
|
8
|
+
import '@stream-io/video-client';
|
|
9
|
+
import 'clsx';
|
|
10
|
+
import '@floating-ui/react';
|
|
11
|
+
import '../../../components/CallControls/RecordCallButton.js';
|
|
12
|
+
import '../../../components/CallControls/ReactionsButton.js';
|
|
13
|
+
import '../../../components/DeviceSettings/DeviceSettings.js';
|
|
14
|
+
import '../../../components/DropdownSelect/DropdownSelect.js';
|
|
15
|
+
import '../../../components/Menu/MenuToggle.js';
|
|
16
|
+
import '../../../components/CallControls/CancelCallButton.js';
|
|
17
|
+
import '../../../components/CallControls/CallStatsButton.js';
|
|
18
|
+
import '../../../components/CallParticipantsList/CallParticipantsList.js';
|
|
19
|
+
import '../../../components/CallParticipantsList/CallParticipantListingItem.js';
|
|
20
|
+
import '../../../components/CallStats/CallStats.js';
|
|
21
|
+
import '../../../components/NoiseCancellation/NoiseCancellationProvider.js';
|
|
22
|
+
import '../../../components/RingingCall/RingingCall.js';
|
|
23
|
+
import '../../../components/Permissions/PermissionRequests.js';
|
|
24
|
+
import '../../../core/components/Video/Video.js';
|
|
25
|
+
import '../../../core/components/Video/BaseVideo.js';
|
|
26
|
+
import '../../../core/components/Video/DefaultVideoPlaceholder.js';
|
|
27
|
+
|
|
28
|
+
const DisabledVideoPreview = () => {
|
|
29
|
+
const { t } = useI18n();
|
|
30
|
+
const user = useConnectedUser();
|
|
31
|
+
const { useCameraState, useMicrophoneState } = useCallStateHooks();
|
|
32
|
+
const { hasBrowserPermission: hasCameraPermission } = useCameraState();
|
|
33
|
+
const { hasBrowserPermission: hasMicPermission } = useMicrophoneState();
|
|
34
|
+
const hasBrowserMediaPermission = hasCameraPermission && hasMicPermission;
|
|
35
|
+
return (jsx("div", { className: "str-video__embedded-lobby__no-permission", children: hasBrowserMediaPermission ? (jsx(Avatar, { imageSrc: user?.image, name: user?.name || user?.id })) : (t('Please grant your browser permission to access your camera and microphone.')) }));
|
|
36
|
+
};
|
|
37
|
+
const NoCameraPreview = () => {
|
|
38
|
+
const { t } = useI18n();
|
|
39
|
+
const { useCameraState, useMicrophoneState } = useCallStateHooks();
|
|
40
|
+
const { hasBrowserPermission: hasCameraPermission } = useCameraState();
|
|
41
|
+
const { hasBrowserPermission: hasMicPermission } = useMicrophoneState();
|
|
42
|
+
const hasBrowserMediaPermission = hasCameraPermission && hasMicPermission;
|
|
43
|
+
if (!hasBrowserMediaPermission) {
|
|
44
|
+
return (jsx("div", { className: "str-video__embedded-lobby__no-permission", children: t('Please grant your browser permission to access your camera and microphone.') }));
|
|
45
|
+
}
|
|
46
|
+
return (jsx("div", { className: "str-video__video-preview__no-camera-preview", children: t('No camera found') }));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { DisabledVideoPreview, NoCameraPreview };
|
|
50
|
+
//# sourceMappingURL=VideoPreviewFallbacks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VideoPreviewFallbacks.js","sources":["../../../../../../../../src/embedded/shared/Lobby/VideoPreviewFallbacks.tsx"],"sourcesContent":["import {\n useCallStateHooks,\n useConnectedUser,\n useI18n,\n} from '@stream-io/video-react-bindings';\nimport { Avatar } from '../../../components';\n\nexport const DisabledVideoPreview = () => {\n const { t } = useI18n();\n\n const user = useConnectedUser();\n const { useCameraState, useMicrophoneState } = useCallStateHooks();\n\n const { hasBrowserPermission: hasCameraPermission } = useCameraState();\n const { hasBrowserPermission: hasMicPermission } = useMicrophoneState();\n\n const hasBrowserMediaPermission = hasCameraPermission && hasMicPermission;\n\n return (\n <div className=\"str-video__embedded-lobby__no-permission\">\n {hasBrowserMediaPermission ? (\n <Avatar imageSrc={user?.image} name={user?.name || user?.id} />\n ) : (\n t(\n 'Please grant your browser permission to access your camera and microphone.',\n )\n )}\n </div>\n );\n};\n\nexport const NoCameraPreview = () => {\n const { t } = useI18n();\n const { useCameraState, useMicrophoneState } = useCallStateHooks();\n const { hasBrowserPermission: hasCameraPermission } = useCameraState();\n const { hasBrowserPermission: hasMicPermission } = useMicrophoneState();\n\n const hasBrowserMediaPermission = hasCameraPermission && hasMicPermission;\n\n if (!hasBrowserMediaPermission) {\n return (\n <div className=\"str-video__embedded-lobby__no-permission\">\n {t(\n 'Please grant your browser permission to access your camera and microphone.',\n )}\n </div>\n );\n }\n\n return (\n <div className=\"str-video__video-preview__no-camera-preview\">\n {t('No camera found')}\n </div>\n );\n};\n"],"names":["_jsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAOO,MAAM,oBAAoB,GAAG,MAAK;AACvC,IAAA,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE;AAEvB,IAAA,MAAM,IAAI,GAAG,gBAAgB,EAAE;IAC/B,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,iBAAiB,EAAE;IAElE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,GAAG,cAAc,EAAE;IACtE,MAAM,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,GAAG,kBAAkB,EAAE;AAEvE,IAAA,MAAM,yBAAyB,GAAG,mBAAmB,IAAI,gBAAgB;AAEzE,IAAA,QACEA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,0CAA0C,EAAA,QAAA,EACtD,yBAAyB,IACxBA,GAAA,CAAC,MAAM,EAAA,EAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,EAAE,GAAI,KAE/D,CAAC,CACC,4EAA4E,CAC7E,CACF,EAAA,CACG;AAEV;AAEO,MAAM,eAAe,GAAG,MAAK;AAClC,IAAA,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE;IACvB,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,iBAAiB,EAAE;IAClE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,GAAG,cAAc,EAAE;IACtE,MAAM,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,GAAG,kBAAkB,EAAE;AAEvE,IAAA,MAAM,yBAAyB,GAAG,mBAAmB,IAAI,gBAAgB;IAEzE,IAAI,CAAC,yBAAyB,EAAE;AAC9B,QAAA,QACEA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,0CAA0C,EAAA,QAAA,EACtD,CAAC,CACA,4EAA4E,CAC7E,EAAA,CACG;IAEV;AAEA,IAAA,QACEA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,6CAA6C,EAAA,QAAA,EACzD,CAAC,CAAC,iBAAiB,CAAC,EAAA,CACjB;AAEV;;;;"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { humanize } from '@stream-io/video-client';
|
|
3
|
+
import 'react';
|
|
4
|
+
import 'clsx';
|
|
5
|
+
import '../../../components/BackgroundFilters/BackgroundFiltersProvider.js';
|
|
6
|
+
import '../../../components/Button/CompositeButton.js';
|
|
7
|
+
import '../../../components/Button/IconButton.js';
|
|
8
|
+
import '@stream-io/video-react-bindings';
|
|
9
|
+
import '@floating-ui/react';
|
|
10
|
+
import '../../../components/CallControls/RecordCallButton.js';
|
|
11
|
+
import '../../../components/CallControls/ReactionsButton.js';
|
|
12
|
+
import { Icon } from '../../../components/Icon/Icon.js';
|
|
13
|
+
import '../../../components/DeviceSettings/DeviceSettings.js';
|
|
14
|
+
import '../../../components/DropdownSelect/DropdownSelect.js';
|
|
15
|
+
import '../../../components/Menu/MenuToggle.js';
|
|
16
|
+
import '../../../components/CallControls/CancelCallButton.js';
|
|
17
|
+
import '../../../components/CallControls/CallStatsButton.js';
|
|
18
|
+
import '../../../components/CallParticipantsList/CallParticipantsList.js';
|
|
19
|
+
import '../../../components/CallParticipantsList/CallParticipantListingItem.js';
|
|
20
|
+
import '../../../components/CallStats/CallStats.js';
|
|
21
|
+
import '../../../components/NoiseCancellation/NoiseCancellationProvider.js';
|
|
22
|
+
import '../../../components/RingingCall/RingingCall.js';
|
|
23
|
+
import '../../../components/Permissions/PermissionRequests.js';
|
|
24
|
+
import '../../../core/components/Video/Video.js';
|
|
25
|
+
import '../../../core/components/Video/BaseVideo.js';
|
|
26
|
+
import '../../../core/components/Video/DefaultVideoPlaceholder.js';
|
|
27
|
+
|
|
28
|
+
const ViewersCount = ({ count }) => (jsxs("div", { className: "str-video__embedded-livestream-duration__viewers", children: [jsx(Icon, { icon: "eye", className: "str-video__embedded-livestream-duration__eye-icon" }), jsx("span", { className: "str-video__embedded-livestream-duration__count", children: humanize(count) })] }));
|
|
29
|
+
|
|
30
|
+
export { ViewersCount };
|
|
31
|
+
//# sourceMappingURL=ViewersCount.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ViewersCount.js","sources":["../../../../../../../../src/embedded/shared/ViewersCount/ViewersCount.tsx"],"sourcesContent":["import { humanize } from '@stream-io/video-client';\nimport { Icon } from '../../../components';\n\ninterface ViewersCountProps {\n count: number;\n}\n\nexport const ViewersCount = ({ count }: ViewersCountProps) => (\n <div className=\"str-video__embedded-livestream-duration__viewers\">\n <Icon\n icon=\"eye\"\n className=\"str-video__embedded-livestream-duration__eye-icon\"\n />\n <span className=\"str-video__embedded-livestream-duration__count\">\n {humanize(count)}\n </span>\n </div>\n);\n"],"names":["_jsxs","_jsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAOO,MAAM,YAAY,GAAG,CAAC,EAAE,KAAK,EAAqB,MACvDA,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,kDAAkD,EAAA,QAAA,EAAA,CAC/DC,IAAC,IAAI,EAAA,EACH,IAAI,EAAC,KAAK,EACV,SAAS,EAAC,mDAAmD,EAAA,CAC7D,EACFA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,gDAAgD,EAAA,QAAA,EAC7D,QAAQ,CAAC,KAAK,CAAC,EAAA,CACX,CAAA,EAAA,CACH;;;;"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useI18n } from '@stream-io/video-react-bindings';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utility hook that helps render a list of devices or implement a device selector.
|
|
6
|
+
* Compared to someting like `useCameraState().devices`, it has some handy features:
|
|
7
|
+
* 1. Adds the "Default" device to the list if applicable (either the user did not
|
|
8
|
+
* select a device, or a previously selected device is no longer available).
|
|
9
|
+
* 2. Maps the device list to a format more suitable for rendering.
|
|
10
|
+
*/
|
|
11
|
+
function useDeviceList(devices, selectedDeviceId) {
|
|
12
|
+
const { t } = useI18n();
|
|
13
|
+
return useMemo(() => {
|
|
14
|
+
let selectedDeviceInfo = null;
|
|
15
|
+
let selectedIndex = null;
|
|
16
|
+
const deviceList = devices.map((d, i) => {
|
|
17
|
+
const isSelected = d.deviceId === selectedDeviceId;
|
|
18
|
+
const device = { deviceId: d.deviceId, label: d.label, isSelected };
|
|
19
|
+
if (isSelected) {
|
|
20
|
+
selectedDeviceInfo = device;
|
|
21
|
+
selectedIndex = i;
|
|
22
|
+
}
|
|
23
|
+
return device;
|
|
24
|
+
});
|
|
25
|
+
if (selectedDeviceInfo === null || selectedIndex === null) {
|
|
26
|
+
const defaultDevice = {
|
|
27
|
+
deviceId: 'default',
|
|
28
|
+
label: t('Default'),
|
|
29
|
+
isSelected: true,
|
|
30
|
+
};
|
|
31
|
+
selectedDeviceInfo = defaultDevice;
|
|
32
|
+
selectedIndex = 0;
|
|
33
|
+
deviceList.unshift(defaultDevice);
|
|
34
|
+
}
|
|
35
|
+
return { deviceList, selectedDeviceInfo, selectedIndex };
|
|
36
|
+
}, [devices, selectedDeviceId, t]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { useDeviceList };
|
|
40
|
+
//# sourceMappingURL=useDeviceList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDeviceList.js","sources":["../../../../../../src/hooks/useDeviceList.tsx"],"sourcesContent":["import { useI18n } from '@stream-io/video-react-bindings';\nimport { useMemo } from 'react';\n\nexport interface DeviceListItem {\n deviceId: string;\n label: string;\n isSelected: boolean;\n}\n\n/**\n * Utility hook that helps render a list of devices or implement a device selector.\n * Compared to someting like `useCameraState().devices`, it has some handy features:\n * 1. Adds the \"Default\" device to the list if applicable (either the user did not\n * select a device, or a previously selected device is no longer available).\n * 2. Maps the device list to a format more suitable for rendering.\n */\nexport function useDeviceList(\n devices: MediaDeviceInfo[],\n selectedDeviceId: string | undefined,\n): {\n deviceList: DeviceListItem[];\n selectedDeviceInfo: DeviceListItem;\n selectedIndex: number;\n} {\n const { t } = useI18n();\n\n return useMemo(() => {\n let selectedDeviceInfo: DeviceListItem | null = null;\n let selectedIndex: number | null = null;\n\n const deviceList: DeviceListItem[] = devices.map((d, i) => {\n const isSelected = d.deviceId === selectedDeviceId;\n const device = { deviceId: d.deviceId, label: d.label, isSelected };\n\n if (isSelected) {\n selectedDeviceInfo = device;\n selectedIndex = i;\n }\n\n return device;\n });\n\n if (selectedDeviceInfo === null || selectedIndex === null) {\n const defaultDevice = {\n deviceId: 'default',\n label: t('Default'),\n isSelected: true,\n };\n\n selectedDeviceInfo = defaultDevice;\n selectedIndex = 0;\n deviceList.unshift(defaultDevice);\n }\n\n return { deviceList, selectedDeviceInfo, selectedIndex };\n }, [devices, selectedDeviceId, t]);\n}\n"],"names":[],"mappings":";;;AASA;;;;;;AAMG;AACG,SAAU,aAAa,CAC3B,OAA0B,EAC1B,gBAAoC,EAAA;AAMpC,IAAA,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE;IAEvB,OAAO,OAAO,CAAC,MAAK;QAClB,IAAI,kBAAkB,GAA0B,IAAI;QACpD,IAAI,aAAa,GAAkB,IAAI;QAEvC,MAAM,UAAU,GAAqB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAI;AACxD,YAAA,MAAM,UAAU,GAAG,CAAC,CAAC,QAAQ,KAAK,gBAAgB;AAClD,YAAA,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE;YAEnE,IAAI,UAAU,EAAE;gBACd,kBAAkB,GAAG,MAAM;gBAC3B,aAAa,GAAG,CAAC;YACnB;AAEA,YAAA,OAAO,MAAM;AACf,QAAA,CAAC,CAAC;QAEF,IAAI,kBAAkB,KAAK,IAAI,IAAI,aAAa,KAAK,IAAI,EAAE;AACzD,YAAA,MAAM,aAAa,GAAG;AACpB,gBAAA,QAAQ,EAAE,SAAS;AACnB,gBAAA,KAAK,EAAE,CAAC,CAAC,SAAS,CAAC;AACnB,gBAAA,UAAU,EAAE,IAAI;aACjB;YAED,kBAAkB,GAAG,aAAa;YAClC,aAAa,GAAG,CAAC;AACjB,YAAA,UAAU,CAAC,OAAO,CAAC,aAAa,CAAC;QACnC;AAEA,QAAA,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,aAAa,EAAE;IAC1D,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;AACpC;;;;"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enables drag-to-scroll functionality with momentum scrolling on a scrollable element.
|
|
5
|
+
*
|
|
6
|
+
* This hook allows users to click and drag to scroll an element, with momentum scrolling
|
|
7
|
+
* that continues after the drag ends. The drag only activates after moving beyond a threshold
|
|
8
|
+
* distance, which prevents accidental drags from clicks.
|
|
9
|
+
*
|
|
10
|
+
* @param element - The HTML element to enable drag to scroll on.
|
|
11
|
+
* @param options - Options for customizing the drag-to-scroll behavior.
|
|
12
|
+
*/
|
|
13
|
+
function useDragToScroll(element, options = {}) {
|
|
14
|
+
const { enabled, decay = 0.95, minVelocity = 0.5, dragThreshold = 5, } = options;
|
|
15
|
+
const stateRef = useRef({
|
|
16
|
+
isDragging: false,
|
|
17
|
+
isPointerActive: false,
|
|
18
|
+
prevX: 0,
|
|
19
|
+
prevY: 0,
|
|
20
|
+
velocityX: 0,
|
|
21
|
+
velocityY: 0,
|
|
22
|
+
rafId: 0,
|
|
23
|
+
startX: 0,
|
|
24
|
+
startY: 0,
|
|
25
|
+
});
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!element || !enabled)
|
|
28
|
+
return;
|
|
29
|
+
const state = stateRef.current;
|
|
30
|
+
const stopMomentum = () => {
|
|
31
|
+
if (state.rafId) {
|
|
32
|
+
cancelAnimationFrame(state.rafId);
|
|
33
|
+
state.rafId = 0;
|
|
34
|
+
}
|
|
35
|
+
state.velocityX = 0;
|
|
36
|
+
state.velocityY = 0;
|
|
37
|
+
};
|
|
38
|
+
const momentumStep = () => {
|
|
39
|
+
state.velocityX *= decay;
|
|
40
|
+
state.velocityY *= decay;
|
|
41
|
+
element.scrollLeft -= state.velocityX;
|
|
42
|
+
element.scrollTop -= state.velocityY;
|
|
43
|
+
if (Math.abs(state.velocityX) < minVelocity &&
|
|
44
|
+
Math.abs(state.velocityY) < minVelocity) {
|
|
45
|
+
state.rafId = 0;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
state.rafId = requestAnimationFrame(momentumStep);
|
|
49
|
+
};
|
|
50
|
+
const onPointerDown = (e) => {
|
|
51
|
+
if (e.pointerType !== 'mouse')
|
|
52
|
+
return;
|
|
53
|
+
stopMomentum();
|
|
54
|
+
state.isDragging = false;
|
|
55
|
+
state.isPointerActive = true;
|
|
56
|
+
state.prevX = e.clientX;
|
|
57
|
+
state.prevY = e.clientY;
|
|
58
|
+
state.startX = e.clientX;
|
|
59
|
+
state.startY = e.clientY;
|
|
60
|
+
};
|
|
61
|
+
const onPointerMove = (e) => {
|
|
62
|
+
if (e.pointerType !== 'mouse')
|
|
63
|
+
return;
|
|
64
|
+
if (!state.isPointerActive)
|
|
65
|
+
return;
|
|
66
|
+
const dx = e.clientX - state.startX;
|
|
67
|
+
const dy = e.clientY - state.startY;
|
|
68
|
+
if (!state.isDragging && Math.hypot(dx, dy) > dragThreshold) {
|
|
69
|
+
state.isDragging = true;
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
}
|
|
72
|
+
if (!state.isDragging)
|
|
73
|
+
return;
|
|
74
|
+
const moveDx = e.clientX - state.prevX;
|
|
75
|
+
const moveDy = e.clientY - state.prevY;
|
|
76
|
+
element.scrollLeft -= moveDx;
|
|
77
|
+
element.scrollTop -= moveDy;
|
|
78
|
+
state.velocityX = moveDx;
|
|
79
|
+
state.velocityY = moveDy;
|
|
80
|
+
state.prevX = e.clientX;
|
|
81
|
+
state.prevY = e.clientY;
|
|
82
|
+
};
|
|
83
|
+
const onPointerUpOrCancel = () => {
|
|
84
|
+
const wasDragging = state.isDragging;
|
|
85
|
+
state.isDragging = false;
|
|
86
|
+
state.isPointerActive = false;
|
|
87
|
+
state.prevX = 0;
|
|
88
|
+
state.prevY = 0;
|
|
89
|
+
state.startX = 0;
|
|
90
|
+
state.startY = 0;
|
|
91
|
+
if (!wasDragging) {
|
|
92
|
+
stopMomentum();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (Math.hypot(state.velocityX, state.velocityY) < minVelocity) {
|
|
96
|
+
stopMomentum();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
state.rafId = requestAnimationFrame(momentumStep);
|
|
100
|
+
};
|
|
101
|
+
element.addEventListener('pointerdown', onPointerDown);
|
|
102
|
+
element.addEventListener('pointermove', onPointerMove);
|
|
103
|
+
window.addEventListener('pointerup', onPointerUpOrCancel);
|
|
104
|
+
window.addEventListener('pointercancel', onPointerUpOrCancel);
|
|
105
|
+
return () => {
|
|
106
|
+
element.removeEventListener('pointerdown', onPointerDown);
|
|
107
|
+
element.removeEventListener('pointermove', onPointerMove);
|
|
108
|
+
window.removeEventListener('pointerup', onPointerUpOrCancel);
|
|
109
|
+
window.removeEventListener('pointercancel', onPointerUpOrCancel);
|
|
110
|
+
stopMomentum();
|
|
111
|
+
};
|
|
112
|
+
}, [element, enabled, decay, minVelocity, dragThreshold]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { useDragToScroll };
|
|
116
|
+
//# sourceMappingURL=useDragToScroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDragToScroll.js","sources":["../../../../../../src/hooks/useDragToScroll.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\ninterface DragScrollState {\n isDragging: boolean;\n isPointerActive: boolean;\n prevX: number;\n prevY: number;\n velocityX: number;\n velocityY: number;\n rafId: number;\n startX: number;\n startY: number;\n}\n\ninterface DragToScrollOptions {\n decay?: number;\n minVelocity?: number;\n dragThreshold?: number;\n enabled?: boolean;\n}\n\n/**\n * Enables drag-to-scroll functionality with momentum scrolling on a scrollable element.\n *\n * This hook allows users to click and drag to scroll an element, with momentum scrolling\n * that continues after the drag ends. The drag only activates after moving beyond a threshold\n * distance, which prevents accidental drags from clicks.\n *\n * @param element - The HTML element to enable drag to scroll on.\n * @param options - Options for customizing the drag-to-scroll behavior.\n */\nexport function useDragToScroll(\n element: HTMLElement | null,\n options: DragToScrollOptions = {},\n) {\n const {\n enabled,\n decay = 0.95,\n minVelocity = 0.5,\n dragThreshold = 5,\n } = options;\n\n const stateRef = useRef<DragScrollState>({\n isDragging: false,\n isPointerActive: false,\n prevX: 0,\n prevY: 0,\n velocityX: 0,\n velocityY: 0,\n rafId: 0,\n startX: 0,\n startY: 0,\n });\n\n useEffect(() => {\n if (!element || !enabled) return;\n\n const state = stateRef.current;\n\n const stopMomentum = () => {\n if (state.rafId) {\n cancelAnimationFrame(state.rafId);\n state.rafId = 0;\n }\n state.velocityX = 0;\n state.velocityY = 0;\n };\n\n const momentumStep = () => {\n state.velocityX *= decay;\n state.velocityY *= decay;\n\n element.scrollLeft -= state.velocityX;\n element.scrollTop -= state.velocityY;\n\n if (\n Math.abs(state.velocityX) < minVelocity &&\n Math.abs(state.velocityY) < minVelocity\n ) {\n state.rafId = 0;\n return;\n }\n\n state.rafId = requestAnimationFrame(momentumStep);\n };\n\n const onPointerDown = (e: PointerEvent) => {\n if (e.pointerType !== 'mouse') return;\n\n stopMomentum();\n\n state.isDragging = false;\n state.isPointerActive = true;\n\n state.prevX = e.clientX;\n state.prevY = e.clientY;\n state.startX = e.clientX;\n state.startY = e.clientY;\n };\n\n const onPointerMove = (e: PointerEvent) => {\n if (e.pointerType !== 'mouse') return;\n\n if (!state.isPointerActive) return;\n\n const dx = e.clientX - state.startX;\n const dy = e.clientY - state.startY;\n\n if (!state.isDragging && Math.hypot(dx, dy) > dragThreshold) {\n state.isDragging = true;\n e.preventDefault();\n }\n\n if (!state.isDragging) return;\n\n const moveDx = e.clientX - state.prevX;\n const moveDy = e.clientY - state.prevY;\n\n element.scrollLeft -= moveDx;\n element.scrollTop -= moveDy;\n\n state.velocityX = moveDx;\n state.velocityY = moveDy;\n\n state.prevX = e.clientX;\n state.prevY = e.clientY;\n };\n\n const onPointerUpOrCancel = () => {\n const wasDragging = state.isDragging;\n\n state.isDragging = false;\n state.isPointerActive = false;\n\n state.prevX = 0;\n state.prevY = 0;\n state.startX = 0;\n state.startY = 0;\n\n if (!wasDragging) {\n stopMomentum();\n return;\n }\n\n if (Math.hypot(state.velocityX, state.velocityY) < minVelocity) {\n stopMomentum();\n return;\n }\n\n state.rafId = requestAnimationFrame(momentumStep);\n };\n\n element.addEventListener('pointerdown', onPointerDown);\n element.addEventListener('pointermove', onPointerMove);\n window.addEventListener('pointerup', onPointerUpOrCancel);\n window.addEventListener('pointercancel', onPointerUpOrCancel);\n\n return () => {\n element.removeEventListener('pointerdown', onPointerDown);\n element.removeEventListener('pointermove', onPointerMove);\n window.removeEventListener('pointerup', onPointerUpOrCancel);\n window.removeEventListener('pointercancel', onPointerUpOrCancel);\n\n stopMomentum();\n };\n }, [element, enabled, decay, minVelocity, dragThreshold]);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useFloating, offset, shift, flip, size, autoUpdate } from '@floating-ui/react';
|
|
3
|
+
|
|
4
|
+
const useFloatingUIPreset = ({ middleware = [], placement, strategy, offset: offsetInPx = 10, }) => {
|
|
5
|
+
const { refs, x, y, update, elements: { domReference, floating }, context, } = useFloating({
|
|
6
|
+
placement,
|
|
7
|
+
strategy,
|
|
8
|
+
middleware: [
|
|
9
|
+
offset(offsetInPx),
|
|
10
|
+
shift(),
|
|
11
|
+
flip(),
|
|
12
|
+
size({
|
|
13
|
+
padding: 10,
|
|
14
|
+
apply: ({ availableHeight, elements }) => {
|
|
15
|
+
Object.assign(elements.floating.style, {
|
|
16
|
+
maxHeight: `${availableHeight}px`,
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
...middleware,
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
// handle window resizing
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!domReference || !floating)
|
|
26
|
+
return;
|
|
27
|
+
const cleanup = autoUpdate(domReference, floating, update);
|
|
28
|
+
return () => cleanup();
|
|
29
|
+
}, [domReference, floating, update]);
|
|
30
|
+
return { refs, x, y, domReference, floating, strategy, context };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { useFloatingUIPreset };
|
|
34
|
+
//# sourceMappingURL=useFloatingUIPreset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFloatingUIPreset.js","sources":["../../../../../../src/hooks/useFloatingUIPreset.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport type { UseFloatingOptions } from '@floating-ui/react';\nimport {\n autoUpdate,\n flip,\n offset,\n shift,\n size,\n useFloating,\n} from '@floating-ui/react';\n\nexport const useFloatingUIPreset = ({\n middleware = [],\n placement,\n strategy,\n offset: offsetInPx = 10,\n}: Pick<UseFloatingOptions, 'placement' | 'strategy' | 'middleware'> & {\n offset?: number;\n}) => {\n const {\n refs,\n x,\n y,\n update,\n elements: { domReference, floating },\n context,\n } = useFloating({\n placement,\n strategy,\n middleware: [\n offset(offsetInPx),\n shift(),\n flip(),\n size({\n padding: 10,\n apply: ({ availableHeight, elements }) => {\n Object.assign(elements.floating.style, {\n maxHeight: `${availableHeight}px`,\n });\n },\n }),\n ...middleware,\n ],\n });\n\n // handle window resizing\n useEffect(() => {\n if (!domReference || !floating) return;\n\n const cleanup = autoUpdate(domReference, floating, update);\n\n return () => cleanup();\n }, [domReference, floating, update]);\n\n return { refs, x, y, domReference, floating, strategy, context };\n};\n"],"names":[],"mappings":";;;MAWa,mBAAmB,GAAG,CAAC,EAClC,UAAU,GAAG,EAAE,EACf,SAAS,EACT,QAAQ,EACR,MAAM,EAAE,UAAU,GAAG,EAAE,GAGxB,KAAI;IACH,MAAM,EACJ,IAAI,EACJ,CAAC,EACD,CAAC,EACD,MAAM,EACN,QAAQ,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,EACpC,OAAO,GACR,GAAG,WAAW,CAAC;QACd,SAAS;QACT,QAAQ;AACR,QAAA,UAAU,EAAE;YACV,MAAM,CAAC,UAAU,CAAC;AAClB,YAAA,KAAK,EAAE;AACP,YAAA,IAAI,EAAE;AACN,YAAA,IAAI,CAAC;AACH,gBAAA,OAAO,EAAE,EAAE;gBACX,KAAK,EAAE,CAAC,EAAE,eAAe,EAAE,QAAQ,EAAE,KAAI;oBACvC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE;wBACrC,SAAS,EAAE,CAAA,EAAG,eAAe,CAAA,EAAA,CAAI;AAClC,qBAAA,CAAC;gBACJ,CAAC;aACF,CAAC;AACF,YAAA,GAAG,UAAU;AACd,SAAA;AACF,KAAA,CAAC;;IAGF,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,YAAY,IAAI,CAAC,QAAQ;YAAE;QAEhC,MAAM,OAAO,GAAG,UAAU,CAAC,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC;AAE1D,QAAA,OAAO,MAAM,OAAO,EAAE;IACxB,CAAC,EAAE,CAAC,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAEpC,IAAA,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE;AAClE;;;;"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useState, useRef, useMemo, useCallback, useEffect } from 'react';
|
|
2
|
+
import { getLoopbackStreams, videoLoggerSystem, DEFAULT_LOOPBACK_RECORDING_DURATION_MS, CallingState, waitForLoopbackStreams, LOOPBACK_STREAMS_WAIT_TIMEOUT_MS, enableLoopbackAudio, MediaStreamRecorder, clampLoopbackRecordingDuration } from '@stream-io/video-client';
|
|
3
|
+
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
4
|
+
|
|
5
|
+
const attachHiddenMediaSink = (stream) => {
|
|
6
|
+
const logger = videoLoggerSystem.getLogger('useLoopbackRecording');
|
|
7
|
+
const element = document.createElement('video');
|
|
8
|
+
element.muted = true;
|
|
9
|
+
element.autoplay = true;
|
|
10
|
+
element.playsInline = true;
|
|
11
|
+
element.srcObject = stream;
|
|
12
|
+
element.play().catch((error) => {
|
|
13
|
+
logger.warn('sink element failed to play; recording may be empty', error);
|
|
14
|
+
});
|
|
15
|
+
return () => {
|
|
16
|
+
element.pause();
|
|
17
|
+
element.srcObject = null;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/** Records SFU loopback streams for pre-call testing. */
|
|
21
|
+
const useLoopbackRecording = () => {
|
|
22
|
+
const call = useCall();
|
|
23
|
+
const { useCallCallingState, useParticipantCount, useLocalParticipant } = useCallStateHooks();
|
|
24
|
+
const callingState = useCallCallingState();
|
|
25
|
+
const participantCount = useParticipantCount();
|
|
26
|
+
const localParticipant = useLocalParticipant();
|
|
27
|
+
const [recordingState, setRecordingState] = useState('idle');
|
|
28
|
+
const recordingStateRef = useRef('idle');
|
|
29
|
+
const isMountedRef = useRef(true);
|
|
30
|
+
const awaitAbortRef = useRef(null);
|
|
31
|
+
const stopRef = useRef(null);
|
|
32
|
+
const runRef = useRef(null);
|
|
33
|
+
const loopbackStreams = useMemo(() => getLoopbackStreams(call, localParticipant), [call, localParticipant]);
|
|
34
|
+
const updateState = useCallback((next) => {
|
|
35
|
+
recordingStateRef.current = next;
|
|
36
|
+
if (isMountedRef.current) {
|
|
37
|
+
setRecordingState(next);
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
const stopRecording = useCallback(async () => {
|
|
41
|
+
const current = recordingStateRef.current;
|
|
42
|
+
if (current === 'idle')
|
|
43
|
+
return;
|
|
44
|
+
if (current === 'awaiting-streams') {
|
|
45
|
+
videoLoggerSystem
|
|
46
|
+
.getLogger('useLoopbackRecording')
|
|
47
|
+
.debug('aborting the awaiting-streams wait');
|
|
48
|
+
awaitAbortRef.current?.abort();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
stopRef.current?.();
|
|
52
|
+
await runRef.current;
|
|
53
|
+
}, []);
|
|
54
|
+
const startRecording = useCallback(async ({ includeVideo = true, maxDurationMs = DEFAULT_LOOPBACK_RECORDING_DURATION_MS, } = {}) => {
|
|
55
|
+
const logger = videoLoggerSystem.getLogger('useLoopbackRecording');
|
|
56
|
+
if (!call)
|
|
57
|
+
return null;
|
|
58
|
+
if (recordingStateRef.current !== 'idle') {
|
|
59
|
+
logger.warn('a recording is already running');
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (call.state.participantCount > 1) {
|
|
63
|
+
logger.warn('cannot start recording with other participants present');
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const run = async () => {
|
|
67
|
+
awaitAbortRef.current = new AbortController();
|
|
68
|
+
updateState('awaiting-streams');
|
|
69
|
+
try {
|
|
70
|
+
const tracks = await waitForLoopbackStreams(call, {
|
|
71
|
+
includeVideo,
|
|
72
|
+
signal: awaitAbortRef.current.signal,
|
|
73
|
+
timeoutMs: LOOPBACK_STREAMS_WAIT_TIMEOUT_MS,
|
|
74
|
+
});
|
|
75
|
+
if (tracks === null) {
|
|
76
|
+
logger.debug('loopback stream wait was aborted');
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
updateState('recording');
|
|
80
|
+
const restoreAudio = enableLoopbackAudio(tracks);
|
|
81
|
+
const stream = new MediaStream([tracks.audioTrack, tracks.videoTrack].filter((track) => !!track));
|
|
82
|
+
const detachSink = attachHiddenMediaSink(stream);
|
|
83
|
+
const recorder = new MediaStreamRecorder(stream, {
|
|
84
|
+
maxDurationMs: clampLoopbackRecordingDuration(maxDurationMs),
|
|
85
|
+
});
|
|
86
|
+
stopRef.current = recorder.stop;
|
|
87
|
+
try {
|
|
88
|
+
return await recorder.start();
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
detachSink();
|
|
92
|
+
restoreAudio();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
logger.error('loopback recording failed', err);
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
awaitAbortRef.current = null;
|
|
101
|
+
stopRef.current = null;
|
|
102
|
+
updateState('idle');
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const promise = run();
|
|
106
|
+
runRef.current = promise.catch(() => undefined);
|
|
107
|
+
return promise;
|
|
108
|
+
}, [call, updateState]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (callingState === CallingState.LEFT ||
|
|
111
|
+
callingState === CallingState.IDLE) {
|
|
112
|
+
stopRecording().catch(() => { });
|
|
113
|
+
}
|
|
114
|
+
}, [callingState, stopRecording]);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (recordingState !== 'idle' && participantCount > 1) {
|
|
117
|
+
stopRecording().catch(() => { });
|
|
118
|
+
}
|
|
119
|
+
}, [participantCount, recordingState, stopRecording]);
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
isMountedRef.current = true;
|
|
122
|
+
return () => {
|
|
123
|
+
isMountedRef.current = false;
|
|
124
|
+
awaitAbortRef.current?.abort('the component unmounted');
|
|
125
|
+
stopRef.current?.();
|
|
126
|
+
};
|
|
127
|
+
}, []);
|
|
128
|
+
return {
|
|
129
|
+
startRecording,
|
|
130
|
+
stopRecording,
|
|
131
|
+
recordingState,
|
|
132
|
+
loopbackVideoStream: loopbackStreams.loopbackVideoStream,
|
|
133
|
+
loopbackAudioStream: loopbackStreams.loopbackAudioStream,
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export { useLoopbackRecording };
|
|
138
|
+
//# sourceMappingURL=useLoopbackRecording.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLoopbackRecording.js","sources":["../../../../../../src/hooks/useLoopbackRecording.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n CallingState,\n DEFAULT_LOOPBACK_RECORDING_DURATION_MS,\n LOOPBACK_STREAMS_WAIT_TIMEOUT_MS,\n MediaStreamRecorder,\n clampLoopbackRecordingDuration,\n getLoopbackStreams,\n type MediaRecordingResult,\n videoLoggerSystem,\n waitForLoopbackStreams,\n enableLoopbackAudio,\n type LoopbackStreams,\n} from '@stream-io/video-client';\nimport { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';\n\n/** Lifecycle phase of a loopback recording. */\nexport type LoopbackRecordingState = 'idle' | 'awaiting-streams' | 'recording';\n\nconst attachHiddenMediaSink = (stream: MediaStream): (() => void) => {\n const logger = videoLoggerSystem.getLogger('useLoopbackRecording');\n const element = document.createElement('video');\n element.muted = true;\n element.autoplay = true;\n element.playsInline = true;\n element.srcObject = stream;\n\n element.play().catch((error) => {\n logger.warn('sink element failed to play; recording may be empty', error);\n });\n\n return () => {\n element.pause();\n element.srcObject = null;\n };\n};\n\nexport type StartLoopbackRecordingOptions = {\n /** Whether to include loopback video. Audio is always recorded. */\n includeVideo?: boolean;\n /** Maximum recording duration in milliseconds. */\n maxDurationMs?: number;\n};\n\nexport type UseLoopbackRecordingResult = {\n /** Starts recording and resolves with the recorded blob. */\n startRecording: (\n options?: StartLoopbackRecordingOptions,\n ) => Promise<MediaRecordingResult | null>;\n /** Stops the current recording or stream wait. */\n stopRecording: () => Promise<void>;\n /** Lifecycle phase of the recording. */\n recordingState: LoopbackRecordingState;\n /** SFU-echoed video stream on the local participant. */\n loopbackVideoStream?: MediaStream;\n /** SFU-echoed audio stream on the local participant. */\n loopbackAudioStream?: MediaStream;\n};\n\n/** Records SFU loopback streams for pre-call testing. */\nexport const useLoopbackRecording = (): UseLoopbackRecordingResult => {\n const call = useCall();\n const { useCallCallingState, useParticipantCount, useLocalParticipant } =\n useCallStateHooks();\n const callingState = useCallCallingState();\n const participantCount = useParticipantCount();\n const localParticipant = useLocalParticipant();\n\n const [recordingState, setRecordingState] =\n useState<LoopbackRecordingState>('idle');\n\n const recordingStateRef = useRef<LoopbackRecordingState>('idle');\n const isMountedRef = useRef(true);\n const awaitAbortRef = useRef<AbortController | null>(null);\n const stopRef = useRef<(() => void) | null>(null);\n const runRef = useRef<Promise<unknown> | null>(null);\n\n const loopbackStreams = useMemo<LoopbackStreams>(\n () => getLoopbackStreams(call, localParticipant),\n [call, localParticipant],\n );\n\n const updateState = useCallback((next: LoopbackRecordingState) => {\n recordingStateRef.current = next;\n if (isMountedRef.current) {\n setRecordingState(next);\n }\n }, []);\n\n const stopRecording = useCallback(async (): Promise<void> => {\n const current = recordingStateRef.current;\n if (current === 'idle') return;\n\n if (current === 'awaiting-streams') {\n videoLoggerSystem\n .getLogger('useLoopbackRecording')\n .debug('aborting the awaiting-streams wait');\n awaitAbortRef.current?.abort();\n return;\n }\n\n stopRef.current?.();\n await runRef.current;\n }, []);\n\n const startRecording = useCallback(\n async ({\n includeVideo = true,\n maxDurationMs = DEFAULT_LOOPBACK_RECORDING_DURATION_MS,\n }: StartLoopbackRecordingOptions = {}): Promise<MediaRecordingResult | null> => {\n const logger = videoLoggerSystem.getLogger('useLoopbackRecording');\n if (!call) return null;\n\n if (recordingStateRef.current !== 'idle') {\n logger.warn('a recording is already running');\n return null;\n }\n\n if (call.state.participantCount > 1) {\n logger.warn('cannot start recording with other participants present');\n return null;\n }\n\n const run = async (): Promise<MediaRecordingResult | null> => {\n awaitAbortRef.current = new AbortController();\n updateState('awaiting-streams');\n\n try {\n const tracks = await waitForLoopbackStreams(call, {\n includeVideo,\n signal: awaitAbortRef.current.signal,\n timeoutMs: LOOPBACK_STREAMS_WAIT_TIMEOUT_MS,\n });\n\n if (tracks === null) {\n logger.debug('loopback stream wait was aborted');\n return null;\n }\n\n updateState('recording');\n\n const restoreAudio = enableLoopbackAudio(tracks);\n const stream = new MediaStream(\n [tracks.audioTrack, tracks.videoTrack].filter(\n (track): track is MediaStreamTrack => !!track,\n ),\n );\n const detachSink = attachHiddenMediaSink(stream);\n const recorder = new MediaStreamRecorder(stream, {\n maxDurationMs: clampLoopbackRecordingDuration(maxDurationMs),\n });\n stopRef.current = recorder.stop;\n try {\n return await recorder.start();\n } finally {\n detachSink();\n restoreAudio();\n }\n } catch (err) {\n logger.error('loopback recording failed', err);\n throw err;\n } finally {\n awaitAbortRef.current = null;\n stopRef.current = null;\n updateState('idle');\n }\n };\n\n const promise = run();\n runRef.current = promise.catch(() => undefined);\n return promise;\n },\n [call, updateState],\n );\n\n useEffect(() => {\n if (\n callingState === CallingState.LEFT ||\n callingState === CallingState.IDLE\n ) {\n stopRecording().catch(() => {});\n }\n }, [callingState, stopRecording]);\n\n useEffect(() => {\n if (recordingState !== 'idle' && participantCount > 1) {\n stopRecording().catch(() => {});\n }\n }, [participantCount, recordingState, stopRecording]);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n awaitAbortRef.current?.abort('the component unmounted');\n stopRef.current?.();\n };\n }, []);\n\n return {\n startRecording,\n stopRecording,\n recordingState,\n loopbackVideoStream: loopbackStreams.loopbackVideoStream,\n loopbackAudioStream: loopbackStreams.loopbackAudioStream,\n };\n};\n"],"names":[],"mappings":";;;;AAmBA,MAAM,qBAAqB,GAAG,CAAC,MAAmB,KAAkB;IAClE,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,sBAAsB,CAAC;IAClE,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC;AAC/C,IAAA,OAAO,CAAC,KAAK,GAAG,IAAI;AACpB,IAAA,OAAO,CAAC,QAAQ,GAAG,IAAI;AACvB,IAAA,OAAO,CAAC,WAAW,GAAG,IAAI;AAC1B,IAAA,OAAO,CAAC,SAAS,GAAG,MAAM;IAE1B,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,KAAI;AAC7B,QAAA,MAAM,CAAC,IAAI,CAAC,qDAAqD,EAAE,KAAK,CAAC;AAC3E,IAAA,CAAC,CAAC;AAEF,IAAA,OAAO,MAAK;QACV,OAAO,CAAC,KAAK,EAAE;AACf,QAAA,OAAO,CAAC,SAAS,GAAG,IAAI;AAC1B,IAAA,CAAC;AACH,CAAC;AAwBD;AACO,MAAM,oBAAoB,GAAG,MAAiC;AACnE,IAAA,MAAM,IAAI,GAAG,OAAO,EAAE;IACtB,MAAM,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,GACrE,iBAAiB,EAAE;AACrB,IAAA,MAAM,YAAY,GAAG,mBAAmB,EAAE;AAC1C,IAAA,MAAM,gBAAgB,GAAG,mBAAmB,EAAE;AAC9C,IAAA,MAAM,gBAAgB,GAAG,mBAAmB,EAAE;IAE9C,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GACvC,QAAQ,CAAyB,MAAM,CAAC;AAE1C,IAAA,MAAM,iBAAiB,GAAG,MAAM,CAAyB,MAAM,CAAC;AAChE,IAAA,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC;AACjC,IAAA,MAAM,aAAa,GAAG,MAAM,CAAyB,IAAI,CAAC;AAC1D,IAAA,MAAM,OAAO,GAAG,MAAM,CAAsB,IAAI,CAAC;AACjD,IAAA,MAAM,MAAM,GAAG,MAAM,CAA0B,IAAI,CAAC;IAEpD,MAAM,eAAe,GAAG,OAAO,CAC7B,MAAM,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,CAAC,EAChD,CAAC,IAAI,EAAE,gBAAgB,CAAC,CACzB;AAED,IAAA,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,IAA4B,KAAI;AAC/D,QAAA,iBAAiB,CAAC,OAAO,GAAG,IAAI;AAChC,QAAA,IAAI,YAAY,CAAC,OAAO,EAAE;YACxB,iBAAiB,CAAC,IAAI,CAAC;QACzB;IACF,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,aAAa,GAAG,WAAW,CAAC,YAA0B;AAC1D,QAAA,MAAM,OAAO,GAAG,iBAAiB,CAAC,OAAO;QACzC,IAAI,OAAO,KAAK,MAAM;YAAE;AAExB,QAAA,IAAI,OAAO,KAAK,kBAAkB,EAAE;YAClC;iBACG,SAAS,CAAC,sBAAsB;iBAChC,KAAK,CAAC,oCAAoC,CAAC;AAC9C,YAAA,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE;YAC9B;QACF;AAEA,QAAA,OAAO,CAAC,OAAO,IAAI;QACnB,MAAM,MAAM,CAAC,OAAO;IACtB,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,cAAc,GAAG,WAAW,CAChC,OAAO,EACL,YAAY,GAAG,IAAI,EACnB,aAAa,GAAG,sCAAsC,GAAA,GACrB,EAAE,KAA0C;QAC7E,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,sBAAsB,CAAC;AAClE,QAAA,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,IAAI;AAEtB,QAAA,IAAI,iBAAiB,CAAC,OAAO,KAAK,MAAM,EAAE;AACxC,YAAA,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC;AAC7C,YAAA,OAAO,IAAI;QACb;QAEA,IAAI,IAAI,CAAC,KAAK,CAAC,gBAAgB,GAAG,CAAC,EAAE;AACnC,YAAA,MAAM,CAAC,IAAI,CAAC,wDAAwD,CAAC;AACrE,YAAA,OAAO,IAAI;QACb;AAEA,QAAA,MAAM,GAAG,GAAG,YAAiD;AAC3D,YAAA,aAAa,CAAC,OAAO,GAAG,IAAI,eAAe,EAAE;YAC7C,WAAW,CAAC,kBAAkB,CAAC;AAE/B,YAAA,IAAI;AACF,gBAAA,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,IAAI,EAAE;oBAChD,YAAY;AACZ,oBAAA,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,MAAM;AACpC,oBAAA,SAAS,EAAE,gCAAgC;AAC5C,iBAAA,CAAC;AAEF,gBAAA,IAAI,MAAM,KAAK,IAAI,EAAE;AACnB,oBAAA,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC;AAChD,oBAAA,OAAO,IAAI;gBACb;gBAEA,WAAW,CAAC,WAAW,CAAC;AAExB,gBAAA,MAAM,YAAY,GAAG,mBAAmB,CAAC,MAAM,CAAC;gBAChD,MAAM,MAAM,GAAG,IAAI,WAAW,CAC5B,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAC3C,CAAC,KAAK,KAAgC,CAAC,CAAC,KAAK,CAC9C,CACF;AACD,gBAAA,MAAM,UAAU,GAAG,qBAAqB,CAAC,MAAM,CAAC;AAChD,gBAAA,MAAM,QAAQ,GAAG,IAAI,mBAAmB,CAAC,MAAM,EAAE;AAC/C,oBAAA,aAAa,EAAE,8BAA8B,CAAC,aAAa,CAAC;AAC7D,iBAAA,CAAC;AACF,gBAAA,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,IAAI;AAC/B,gBAAA,IAAI;AACF,oBAAA,OAAO,MAAM,QAAQ,CAAC,KAAK,EAAE;gBAC/B;wBAAU;AACR,oBAAA,UAAU,EAAE;AACZ,oBAAA,YAAY,EAAE;gBAChB;YACF;YAAE,OAAO,GAAG,EAAE;AACZ,gBAAA,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,CAAC;AAC9C,gBAAA,MAAM,GAAG;YACX;oBAAU;AACR,gBAAA,aAAa,CAAC,OAAO,GAAG,IAAI;AAC5B,gBAAA,OAAO,CAAC,OAAO,GAAG,IAAI;gBACtB,WAAW,CAAC,MAAM,CAAC;YACrB;AACF,QAAA,CAAC;AAED,QAAA,MAAM,OAAO,GAAG,GAAG,EAAE;AACrB,QAAA,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,SAAS,CAAC;AAC/C,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC,EACD,CAAC,IAAI,EAAE,WAAW,CAAC,CACpB;IAED,SAAS,CAAC,MAAK;AACb,QAAA,IACE,YAAY,KAAK,YAAY,CAAC,IAAI;AAClC,YAAA,YAAY,KAAK,YAAY,CAAC,IAAI,EAClC;YACA,aAAa,EAAE,CAAC,KAAK,CAAC,MAAK,EAAE,CAAC,CAAC;QACjC;AACF,IAAA,CAAC,EAAE,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;IAEjC,SAAS,CAAC,MAAK;QACb,IAAI,cAAc,KAAK,MAAM,IAAI,gBAAgB,GAAG,CAAC,EAAE;YACrD,aAAa,EAAE,CAAC,KAAK,CAAC,MAAK,EAAE,CAAC,CAAC;QACjC;IACF,CAAC,EAAE,CAAC,gBAAgB,EAAE,cAAc,EAAE,aAAa,CAAC,CAAC;IAErD,SAAS,CAAC,MAAK;AACb,QAAA,YAAY,CAAC,OAAO,GAAG,IAAI;AAC3B,QAAA,OAAO,MAAK;AACV,YAAA,YAAY,CAAC,OAAO,GAAG,KAAK;AAC5B,YAAA,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,yBAAyB,CAAC;AACvD,YAAA,OAAO,CAAC,OAAO,IAAI;AACrB,QAAA,CAAC;IACH,CAAC,EAAE,EAAE,CAAC;IAEN,OAAO;QACL,cAAc;QACd,aAAa;QACb,cAAc;QACd,mBAAmB,EAAE,eAAe,CAAC,mBAAmB;QACxD,mBAAmB,EAAE,eAAe,CAAC,mBAAmB;KACzD;AACH;;;;"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { useCall } from '@stream-io/video-react-bindings';
|
|
3
|
+
|
|
4
|
+
const isFullScreenBlurPlatformSupported = () => {
|
|
5
|
+
if (typeof window === 'undefined' ||
|
|
6
|
+
typeof OffscreenCanvas === 'undefined' ||
|
|
7
|
+
typeof VideoFrame === 'undefined' ||
|
|
8
|
+
!window.WebGL2RenderingContext) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const canvas = new OffscreenCanvas(1, 1);
|
|
13
|
+
return !!canvas.getContext('webgl2', {
|
|
14
|
+
alpha: false,
|
|
15
|
+
antialias: false,
|
|
16
|
+
desynchronized: true,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const useModeration = (options) => {
|
|
24
|
+
const { duration = 5000 } = options || {};
|
|
25
|
+
const call = useCall();
|
|
26
|
+
const timeoutRef = useRef(null);
|
|
27
|
+
const processorRef = useRef(null);
|
|
28
|
+
const unregisterRef = useRef(null);
|
|
29
|
+
const blurModulePromise = useRef(null);
|
|
30
|
+
/**
|
|
31
|
+
* Lazily loads and caches the video-filters-web module.
|
|
32
|
+
*/
|
|
33
|
+
const loadVideoFiltersWebModule = useCallback(() => {
|
|
34
|
+
if (!blurModulePromise.current) {
|
|
35
|
+
blurModulePromise.current = import('@stream-io/video-filters-web')
|
|
36
|
+
.then((module) => module.FullScreenBlur)
|
|
37
|
+
.catch((error) => {
|
|
38
|
+
console.error('[moderation] Failed to import blur module:', error);
|
|
39
|
+
throw error;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return blurModulePromise.current;
|
|
43
|
+
}, []);
|
|
44
|
+
const disableBlur = useCallback(() => {
|
|
45
|
+
if (timeoutRef.current) {
|
|
46
|
+
clearTimeout(timeoutRef.current);
|
|
47
|
+
timeoutRef.current = null;
|
|
48
|
+
}
|
|
49
|
+
unregisterRef
|
|
50
|
+
.current?.()
|
|
51
|
+
.catch((err) => console.error('[moderation] unregister error:', err));
|
|
52
|
+
unregisterRef.current = null;
|
|
53
|
+
}, []);
|
|
54
|
+
const handleFallback = useCallback(async () => {
|
|
55
|
+
try {
|
|
56
|
+
await call?.camera.disable();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error('[moderation] Failed to disable camera:', error);
|
|
60
|
+
}
|
|
61
|
+
}, [call]);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!call)
|
|
64
|
+
return;
|
|
65
|
+
return call.on('call.moderation_warning', async () => {
|
|
66
|
+
try {
|
|
67
|
+
await loadVideoFiltersWebModule();
|
|
68
|
+
}
|
|
69
|
+
catch (importErr) {
|
|
70
|
+
console.error('[moderation] Failed to import blur module:', importErr);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}, [call, loadVideoFiltersWebModule]);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!call)
|
|
76
|
+
return;
|
|
77
|
+
return call.on('call.moderation_blur', async () => {
|
|
78
|
+
if (unregisterRef.current)
|
|
79
|
+
return;
|
|
80
|
+
let FullScreenBlurClass;
|
|
81
|
+
try {
|
|
82
|
+
FullScreenBlurClass = await loadVideoFiltersWebModule();
|
|
83
|
+
}
|
|
84
|
+
catch (importErr) {
|
|
85
|
+
console.error('[moderation] Failed to import blur module:', importErr);
|
|
86
|
+
await handleFallback();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!isFullScreenBlurPlatformSupported()) {
|
|
90
|
+
console.warn('[moderation] Blur not supported on this platform');
|
|
91
|
+
await handleFallback();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const { unregister } = call.camera.registerFilter((inputStream) => {
|
|
95
|
+
unregisterRef.current = unregister;
|
|
96
|
+
const outputPromise = new Promise(async (resolve, reject) => {
|
|
97
|
+
const [track] = inputStream.getVideoTracks();
|
|
98
|
+
let processor;
|
|
99
|
+
try {
|
|
100
|
+
processor = new FullScreenBlurClass(track);
|
|
101
|
+
processorRef.current = processor;
|
|
102
|
+
const result = await processor.start();
|
|
103
|
+
const output = new MediaStream([result]);
|
|
104
|
+
resolve(output);
|
|
105
|
+
if (duration > 0) {
|
|
106
|
+
timeoutRef.current = setTimeout(disableBlur, duration);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
reject(error);
|
|
111
|
+
console.error('[moderation] Processor init failed:', error);
|
|
112
|
+
await unregisterRef.current?.();
|
|
113
|
+
unregisterRef.current = null;
|
|
114
|
+
processorRef.current = null;
|
|
115
|
+
await handleFallback();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
output: outputPromise,
|
|
121
|
+
stop: () => {
|
|
122
|
+
if (processorRef.current) {
|
|
123
|
+
processorRef.current.stop();
|
|
124
|
+
processorRef.current = null;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}, [call, loadVideoFiltersWebModule, disableBlur, handleFallback, duration]);
|
|
131
|
+
useEffect(() => disableBlur, [disableBlur]);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export { useModeration };
|
|
135
|
+
//# sourceMappingURL=useModeration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useModeration.js","sources":["../../../../../../src/hooks/useModeration.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { useCall } from '@stream-io/video-react-bindings';\n\ntype FullScreenBlurType =\n typeof import('@stream-io/video-filters-web').FullScreenBlur;\n\nconst isFullScreenBlurPlatformSupported = (): boolean => {\n if (\n typeof window === 'undefined' ||\n typeof OffscreenCanvas === 'undefined' ||\n typeof VideoFrame === 'undefined' ||\n !window.WebGL2RenderingContext\n ) {\n return false;\n }\n\n try {\n const canvas = new OffscreenCanvas(1, 1);\n return !!canvas.getContext('webgl2', {\n alpha: false,\n antialias: false,\n desynchronized: true,\n });\n } catch {\n return false;\n }\n};\n\nexport interface ModerationOptions {\n /**\n * How long the moderation effect should stay active before being disabled.\n * Set to `0` to keep it active indefinitely. Defaults to 5000 ms.\n */\n duration?: number;\n}\n\nexport const useModeration = (options?: ModerationOptions) => {\n const { duration = 5000 } = options || {};\n\n const call = useCall();\n\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const processorRef = useRef<InstanceType<FullScreenBlurType> | null>(null);\n const unregisterRef = useRef<(() => Promise<void>) | null>(null);\n\n const blurModulePromise = useRef<Promise<FullScreenBlurType> | null>(null);\n\n /**\n * Lazily loads and caches the video-filters-web module.\n */\n const loadVideoFiltersWebModule = useCallback(() => {\n if (!blurModulePromise.current) {\n blurModulePromise.current = import('@stream-io/video-filters-web')\n .then((module) => module.FullScreenBlur)\n .catch((error) => {\n console.error('[moderation] Failed to import blur module:', error);\n throw error;\n });\n }\n\n return blurModulePromise.current;\n }, []);\n\n const disableBlur = useCallback(() => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n\n unregisterRef\n .current?.()\n .catch((err) => console.error('[moderation] unregister error:', err));\n\n unregisterRef.current = null;\n }, []);\n\n const handleFallback = useCallback(async () => {\n try {\n await call?.camera.disable();\n } catch (error) {\n console.error('[moderation] Failed to disable camera:', error);\n }\n }, [call]);\n\n useEffect(() => {\n if (!call) return;\n\n return call.on('call.moderation_warning', async () => {\n try {\n await loadVideoFiltersWebModule();\n } catch (importErr) {\n console.error('[moderation] Failed to import blur module:', importErr);\n }\n });\n }, [call, loadVideoFiltersWebModule]);\n\n useEffect(() => {\n if (!call) return;\n\n return call.on('call.moderation_blur', async () => {\n if (unregisterRef.current) return;\n\n let FullScreenBlurClass: FullScreenBlurType;\n\n try {\n FullScreenBlurClass = await loadVideoFiltersWebModule();\n } catch (importErr) {\n console.error('[moderation] Failed to import blur module:', importErr);\n await handleFallback();\n return;\n }\n\n if (!isFullScreenBlurPlatformSupported()) {\n console.warn('[moderation] Blur not supported on this platform');\n await handleFallback();\n return;\n }\n\n const { unregister } = call.camera.registerFilter((inputStream) => {\n unregisterRef.current = unregister;\n\n const outputPromise = new Promise<MediaStream>(\n async (resolve, reject) => {\n const [track] = inputStream.getVideoTracks();\n\n let processor: InstanceType<FullScreenBlurType>;\n\n try {\n processor = new FullScreenBlurClass(track);\n processorRef.current = processor;\n\n const result = await processor.start();\n const output = new MediaStream([result]);\n resolve(output);\n\n if (duration > 0) {\n timeoutRef.current = setTimeout(disableBlur, duration);\n }\n } catch (error) {\n reject(error);\n console.error('[moderation] Processor init failed:', error);\n\n await unregisterRef.current?.();\n unregisterRef.current = null;\n processorRef.current = null;\n\n await handleFallback();\n return;\n }\n },\n );\n\n return {\n output: outputPromise,\n stop: () => {\n if (processorRef.current) {\n processorRef.current.stop();\n processorRef.current = null;\n }\n },\n };\n });\n });\n }, [call, loadVideoFiltersWebModule, disableBlur, handleFallback, duration]);\n\n useEffect(() => disableBlur, [disableBlur]);\n};\n"],"names":[],"mappings":"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"}
|