UNPKG

@selfcommunity/react-ui

Version:

React UI Components to integrate a Community created with SelfCommunity Platform.

301 lines (300 loc) • 18.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PreJoin = exports.usePreviewDevice = exports.usePreviewTracks = void 0; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); const livekit_client_1 = require("livekit-client"); const React = tslib_1.__importStar(require("react")); const components_core_1 = require("@livekit/components-core"); const components_core_2 = require("@livekit/components-core"); const components_react_1 = require("@livekit/components-react"); const react_core_1 = require("@selfcommunity/react-core"); const ParticipantTileAvatar_1 = tslib_1.__importDefault(require("./ParticipantTileAvatar")); const react_1 = require("react"); const TrackToggle_1 = require("./TrackToggle"); const LiveStreamProvider_1 = require("./LiveStreamProvider"); const track_processors_1 = require("@livekit/track-processors"); const LiveStreamSettingsMenu_1 = tslib_1.__importDefault(require("./LiveStreamSettingsMenu")); const utils_1 = require("@selfcommunity/utils"); const LiveStream_1 = require("../../../constants/LiveStream"); const react_intl_1 = require("react-intl"); const notistack_1 = require("notistack"); /** @alpha */ function usePreviewTracks(options, onError) { const [tracks, setTracks] = React.useState(); const [error, setError] = React.useState(false); const trackLock = React.useMemo(() => new livekit_client_1.Mutex(), []); React.useEffect(() => { let needsCleanup = false; let localTracks = []; trackLock.lock().then((unlock) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { if (options.audio || options.video) { localTracks = yield (0, livekit_client_1.createLocalTracks)(options); if (needsCleanup) { localTracks.forEach((tr) => tr.stop()); } else { setTracks(localTracks); } setError(false); } } catch (e) { if (onError && e instanceof Error) { onError(e); } else { components_core_1.log.error(e); } setError(true); } finally { unlock(); } })); return () => { needsCleanup = true; localTracks.forEach((track) => { track.stop(); }); setError(false); }; }, [JSON.stringify(options), onError, trackLock]); return { tracks, error }; } exports.usePreviewTracks = usePreviewTracks; /** @public */ function usePreviewDevice(enabled, deviceId, kind) { const [deviceError, setDeviceError] = React.useState(null); const [isCreatingTrack, setIsCreatingTrack] = React.useState(false); const devices = (0, components_react_1.useMediaDevices)({ kind }); const [selectedDevice, setSelectedDevice] = React.useState(undefined); const [localTrack, setLocalTrack] = React.useState(); const [localDeviceId, setLocalDeviceId] = React.useState(deviceId); React.useEffect(() => { setLocalDeviceId(deviceId); }, [deviceId]); const createTrack = (deviceId, kind) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const track = kind === 'videoinput' ? yield (0, livekit_client_1.createLocalVideoTrack)({ deviceId: deviceId, resolution: livekit_client_1.VideoPresets.h720.resolution }) : yield (0, livekit_client_1.createLocalAudioTrack)({ deviceId }); const newDeviceId = yield track.getDeviceId(); if (newDeviceId && deviceId !== newDeviceId) { prevDeviceId.current = newDeviceId; setLocalDeviceId(newDeviceId); } setLocalTrack(track); } catch (e) { if (e instanceof Error) { setDeviceError(e); } } }); const switchDevice = (track, id) => tslib_1.__awaiter(this, void 0, void 0, function* () { yield track.setDeviceId(id); prevDeviceId.current = id; }); const prevDeviceId = React.useRef(localDeviceId); React.useEffect(() => { if (enabled && !localTrack && !deviceError && !isCreatingTrack) { components_core_1.log.debug('creating track', kind); setIsCreatingTrack(true); createTrack(localDeviceId, kind).finally(() => { setIsCreatingTrack(false); }); } }, [enabled, localTrack, deviceError, isCreatingTrack]); // switch camera device React.useEffect(() => { if (!localTrack) { return; } if (!enabled) { components_core_1.log.debug(`muting ${kind} track`); localTrack.mute().then(() => components_core_1.log.debug(localTrack.mediaStreamTrack)); } else if ((selectedDevice === null || selectedDevice === void 0 ? void 0 : selectedDevice.deviceId) && prevDeviceId.current !== (selectedDevice === null || selectedDevice === void 0 ? void 0 : selectedDevice.deviceId)) { components_core_1.log.debug(`switching ${kind} device from`, prevDeviceId.current, selectedDevice.deviceId); switchDevice(localTrack, selectedDevice.deviceId); } else { components_core_1.log.debug(`unmuting local ${kind} track`); localTrack.unmute(); } }, [localTrack, selectedDevice, enabled, kind]); React.useEffect(() => { return () => { if (localTrack) { components_core_1.log.debug(`stopping local ${kind} track`); localTrack.stop(); localTrack.mute(); } }; }, []); React.useEffect(() => { setSelectedDevice(devices === null || devices === void 0 ? void 0 : devices.find((dev) => dev.deviceId === localDeviceId)); }, [localDeviceId, devices]); return { selectedDevice, localTrack, deviceError }; } exports.usePreviewDevice = usePreviewDevice; /** * The `PreJoin` prefab component is normally presented to the user before he enters a room. * This component allows the user to check and select the preferred media device (camera und microphone). * On submit the user decisions are returned, which can then be passed on to the `LiveKitRoom` so that the user enters the room with the correct media devices. * * @remarks * This component is independent of the `LiveKitRoom` component and should not be nested within it. * Because it only accesses the local media tracks this component is self-contained and works without connection to the LiveKit server. * * @example * ```tsx * <PreJoin /> * ``` * @public */ function PreJoin(_a) { var _b; var { defaults = {}, onValidate, onSubmit, onError, debug, joinLabel = 'Join Room', micLabel = 'Microphone', camLabel = 'Camera', userLabel = 'Username', persistUserChoices = true, videoProcessor } = _a, htmlProps = tslib_1.__rest(_a, ["defaults", "onValidate", "onSubmit", "onError", "debug", "joinLabel", "micLabel", "camLabel", "userLabel", "persistUserChoices", "videoProcessor"]); const { liveStream } = (0, LiveStreamProvider_1.useLiveStream)(); const scUserContext = (0, react_core_1.useSCUser)(); const [userChoices, setUserChoices] = React.useState(components_core_2.defaultUserChoices); const canUseAudio = (0, react_1.useMemo)(() => { var _a; return scUserContext.user && liveStream && (liveStream.host.id === scUserContext.user.id || (liveStream && !((_a = liveStream === null || liveStream === void 0 ? void 0 : liveStream.settings) === null || _a === void 0 ? void 0 : _a.muteParticipants))); }, [scUserContext, liveStream]); const canUseVideo = (0, react_1.useMemo)(() => { var _a; return scUserContext.user && liveStream && (liveStream.host.id === scUserContext.user.id || (liveStream && !((_a = liveStream === null || liveStream === void 0 ? void 0 : liveStream.settings) === null || _a === void 0 ? void 0 : _a.disableVideo))); }, [scUserContext, liveStream]); // TODO: Remove and pipe `defaults` object directly into `usePersistentUserChoices` once we fully switch from type `LocalUserChoices` to `UserChoices`. const partialDefaults = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (defaults.audioDeviceId !== undefined && { audioDeviceId: defaults.audioDeviceId })), (defaults.videoDeviceId !== undefined && { videoDeviceId: defaults.videoDeviceId })), (defaults.audioEnabled !== undefined && { audioEnabled: defaults.audioEnabled })), (defaults.videoEnabled !== undefined && { videoEnabled: defaults.videoEnabled })), (defaults.username !== undefined && { username: defaults.username })); const { userChoices: initialUserChoices, saveAudioInputDeviceId, saveAudioInputEnabled, saveVideoInputDeviceId, saveVideoInputEnabled, saveUsername } = (0, components_react_1.usePersistentUserChoices)({ defaults: partialDefaults, preventSave: !persistUserChoices, preventLoad: !persistUserChoices }); const { enqueueSnackbar } = (0, notistack_1.useSnackbar)(); // Initialize device settings const [audioEnabled, setAudioEnabled] = React.useState(initialUserChoices.audioEnabled && canUseAudio); const [videoEnabled, setVideoEnabled] = React.useState(initialUserChoices.videoEnabled && canUseVideo); const [audioDeviceId, setAudioDeviceId] = React.useState(initialUserChoices.audioDeviceId); const [videoDeviceId, setVideoDeviceId] = React.useState(initialUserChoices.videoDeviceId); const [username, setUsername] = React.useState(initialUserChoices.username); // Processors const [blurEnabled, setBlurEnabled] = React.useState((0, utils_1.isClientSideRendering)() ? ((_b = window === null || window === void 0 ? void 0 : window.localStorage) === null || _b === void 0 ? void 0 : _b.getItem(LiveStream_1.CHOICE_VIDEO_BLUR_EFFECT)) === 'true' : false); const [processorPending, setProcessorPending] = React.useState(false); // Save user choices to persistent storage. React.useEffect(() => { saveAudioInputEnabled(audioEnabled && canUseAudio); }, [audioEnabled, saveAudioInputEnabled, canUseAudio]); React.useEffect(() => { saveVideoInputEnabled(videoEnabled && canUseVideo); }, [videoEnabled, saveVideoInputEnabled, canUseVideo]); React.useEffect(() => { saveAudioInputDeviceId(audioDeviceId); }, [audioDeviceId, saveAudioInputDeviceId]); React.useEffect(() => { saveVideoInputDeviceId(videoDeviceId); }, [videoDeviceId, saveVideoInputDeviceId]); React.useEffect(() => { if (scUserContext.user) { saveUsername(scUserContext.user.username); } }, [username, saveUsername, scUserContext.user]); const { tracks, error } = usePreviewTracks({ audio: audioEnabled ? { deviceId: initialUserChoices.audioDeviceId } : false, video: videoEnabled ? { deviceId: initialUserChoices.videoDeviceId } : false }, onError); const videoEl = React.useRef(null); const videoTrack = React.useMemo(() => tracks === null || tracks === void 0 ? void 0 : tracks.filter((track) => track.kind === livekit_client_1.Track.Kind.Video)[0], [tracks]); const facingMode = React.useMemo(() => { if (videoTrack) { const { facingMode } = (0, livekit_client_1.facingModeFromLocalTrack)(videoTrack); return facingMode; } else { return 'undefined'; } }, [videoTrack]); const audioTrack = React.useMemo(() => tracks === null || tracks === void 0 ? void 0 : tracks.filter((track) => track.kind === livekit_client_1.Track.Kind.Audio)[0], [tracks]); React.useEffect(() => { if (videoEl.current && videoTrack) { videoTrack.unmute(); videoTrack.attach(videoEl.current); } return () => { videoTrack === null || videoTrack === void 0 ? void 0 : videoTrack.detach(); }; }, [videoTrack]); const [isValid, setIsValid] = React.useState(); const handleValidation = React.useCallback((values) => { if (typeof onValidate === 'function') { return onValidate(values); } else { return Boolean(values.username !== '' && ((values.audioEnabled && values.audioDeviceId) || !values.audioEnabled) && ((values.videoEnabled && values.videoDeviceId) || !values.videoEnabled)); } }, [onValidate]); const handleBlur = React.useCallback(() => { var _a; const _blur = !blurEnabled; setBlurEnabled(_blur); (_a = window === null || window === void 0 ? void 0 : window.localStorage) === null || _a === void 0 ? void 0 : _a.setItem(LiveStream_1.CHOICE_VIDEO_BLUR_EFFECT, _blur.toString()); }, [setBlurEnabled, blurEnabled]); (0, react_1.useEffect)(() => { const newUserChoices = { username, videoEnabled, videoDeviceId, audioEnabled, audioDeviceId }; setUserChoices(newUserChoices); setIsValid(handleValidation(newUserChoices)); }, [username, scUserContext.user, videoEnabled, handleValidation, audioEnabled, audioDeviceId, videoDeviceId]); (0, react_1.useEffect)(() => { var _a; if (videoTrack && videoEnabled) { setProcessorPending(true); try { if (blurEnabled && !videoTrack.getProcessor()) { videoTrack.setProcessor((0, track_processors_1.BackgroundBlur)(20)); } else if (!blurEnabled) { videoTrack.stopProcessor(); } } catch (e) { console.log(e); setBlurEnabled(false); (_a = window === null || window === void 0 ? void 0 : window.localStorage) === null || _a === void 0 ? void 0 : _a.setItem(LiveStream_1.CHOICE_VIDEO_BLUR_EFFECT, false.toString()); enqueueSnackbar((0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.liveStreamRoom.errorApplyVideoEffect", defaultMessage: "ui.contributionActionMenu.errorApplyVideoEffect" }), { variant: 'warning', autoHideDuration: 3000 }); } finally { setProcessorPending(false); } } }, [blurEnabled, videoTrack, videoEnabled]); function handleSubmit(event) { event.preventDefault(); if (handleValidation(userChoices)) { if (typeof onSubmit === 'function') { onSubmit(userChoices); } } else { components_core_1.log.warn('Validation failed with: ', userChoices); } } return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-prejoin" }, htmlProps, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-video-container" }, { children: [videoTrack && (0, jsx_runtime_1.jsx)("video", { ref: videoEl, width: "1280", height: "720", "data-lk-facing-mode": facingMode }), (!videoTrack || !videoEnabled) && ((0, jsx_runtime_1.jsx)("div", Object.assign({ className: "lk-camera-off-note" }, { children: (0, jsx_runtime_1.jsx)(ParticipantTileAvatar_1.default, { user: scUserContext.user }) })))] })), (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-button-group-container" }, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-button-group audio" }, { children: [(0, jsx_runtime_1.jsx)(TrackToggle_1.TrackToggle, Object.assign({ disabled: !canUseAudio, initialState: audioEnabled, source: livekit_client_1.Track.Source.Microphone, onChange: (enabled) => setAudioEnabled(enabled) }, { children: micLabel })), (0, jsx_runtime_1.jsx)("div", Object.assign({ className: "lk-button-group-menu" }, { children: (0, jsx_runtime_1.jsx)(components_react_1.MediaDeviceMenu, { initialSelection: audioDeviceId, kind: "audioinput", disabled: !audioTrack || !canUseAudio || !audioEnabled, tracks: { audioinput: audioTrack }, onActiveDeviceChange: (_, id) => setAudioDeviceId(id) }) }))] })), (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-button-group video" }, { children: [(0, jsx_runtime_1.jsx)(TrackToggle_1.TrackToggle, Object.assign({ disabled: !canUseVideo, initialState: videoEnabled, source: livekit_client_1.Track.Source.Camera, onChange: (enabled) => setVideoEnabled(enabled) }, { children: camLabel })), (0, jsx_runtime_1.jsx)("div", Object.assign({ className: "lk-button-group-menu" }, { children: (0, jsx_runtime_1.jsx)(components_react_1.MediaDeviceMenu, { initialSelection: videoDeviceId, kind: "videoinput", disabled: !videoTrack || !canUseVideo || !videoEnabled, tracks: { videoinput: videoTrack }, onActiveDeviceChange: (_, id) => setVideoDeviceId(id) }) }))] })), (0, jsx_runtime_1.jsx)(LiveStreamSettingsMenu_1.default, { actionBlurDisabled: !canUseVideo || !videoEnabled, blurEnabled: blurEnabled, handleBlur: handleBlur })] })), (0, jsx_runtime_1.jsx)("form", Object.assign({ className: "lk-username-container" }, { children: (0, jsx_runtime_1.jsx)("button", Object.assign({ className: "lk-button lk-join-button", type: "submit", onClick: handleSubmit, disabled: !isValid || error }, { children: joinLabel })) })), debug && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("strong", { children: "User Choices:" }), (0, jsx_runtime_1.jsxs)("ul", Object.assign({ className: "lk-list", style: { overflow: 'hidden', maxWidth: '15rem' } }, { children: [(0, jsx_runtime_1.jsxs)("li", { children: ["Username: ", `${userChoices.username}`] }), (0, jsx_runtime_1.jsxs)("li", { children: ["Video Enabled: ", `${userChoices.videoEnabled}`] }), (0, jsx_runtime_1.jsxs)("li", { children: ["Audio Enabled: ", `${userChoices.audioEnabled}`] }), (0, jsx_runtime_1.jsxs)("li", { children: ["Video Device: ", `${userChoices.videoDeviceId}`] }), (0, jsx_runtime_1.jsxs)("li", { children: ["Audio Device: ", `${userChoices.audioDeviceId}`] })] }))] }))] }))); } exports.PreJoin = PreJoin;