@stream-io/video-react-sdk 1.38.0 → 1.39.1

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +31 -6
  2. package/dist/css/embedded.css +1 -0
  3. package/dist/css/embedded.css.map +1 -1
  4. package/dist/css/styles.css +1 -0
  5. package/dist/css/styles.css.map +1 -1
  6. package/dist/embedded.cjs.js +57 -38
  7. package/dist/embedded.cjs.js.map +1 -1
  8. package/dist/embedded.es.js +59 -40
  9. package/dist/embedded.es.js.map +1 -1
  10. package/dist/index.cjs.js +57 -39
  11. package/dist/index.cjs.js.map +1 -1
  12. package/dist/index.es.js +59 -41
  13. package/dist/index.es.js.map +1 -1
  14. package/dist/src/components/CallParticipantsList/CallParticipantListingItem.d.ts +1 -1
  15. package/dist/src/core/components/Audio/Audio.d.ts +1 -4
  16. package/dist/src/core/components/ParticipantView/ParticipantView.d.ts +1 -1
  17. package/package.json +9 -7
  18. package/src/components/CallParticipantsList/CallParticipantListingItem.tsx +50 -46
  19. package/src/components/CallParticipantsList/CallParticipantsList.tsx +17 -6
  20. package/src/components/DeviceSettings/AudioVolumeIndicator.tsx +8 -4
  21. package/src/components/Search/SearchInput.tsx +4 -2
  22. package/src/core/components/Audio/Audio.tsx +4 -4
  23. package/src/core/components/ParticipantView/ParticipantView.tsx +4 -3
  24. package/src/core/components/Video/BaseVideo.tsx +3 -1
  25. package/src/core/components/Video/Video.tsx +5 -2
  26. package/src/embedded/hooks/useWakeLock.ts +1 -0
  27. package/src/embedded/livestream/viewer/ViewerLobby.tsx +5 -6
  28. package/src/hooks/useDragToScroll.ts +9 -4
  29. package/src/hooks/useScrollPosition.ts +2 -2
  30. package/src/wrappers/LivestreamPlayer/LivestreamPlayer.tsx +5 -6
@@ -1,11 +1,11 @@
1
1
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
- import { useState, useEffect, Fragment as Fragment$1, createContext, useContext, useCallback, useMemo, useRef, isValidElement, forwardRef, useLayoutEffect, lazy, Suspense } from 'react';
3
- import { useCall, useCallStateHooks, useI18n, Restricted, useToggleCallRecording, useConnectedUser, useIsAudioConnecting, useIsVideoConnecting, StreamCallProvider, StreamVideoProvider, useEffectEvent } from '@stream-io/video-react-bindings';
2
+ import { memo, useState, useEffect, Fragment as Fragment$1, createContext, useContext, useCallback, useMemo, useRef, isValidElement, forwardRef, useLayoutEffect, lazy, Suspense } from 'react';
3
+ import { useCall, useCallStateHooks, useI18n, Restricted, useToggleCallRecording, useConnectedUser, useEffectEvent, useIsAudioConnecting, useIsVideoConnecting, StreamCallProvider, StreamVideoProvider } from '@stream-io/video-react-bindings';
4
4
  import { hasAudio, hasScreenShareAudio, hasVideo, hasScreenShare, OwnCapability, Browsers, VisibilityState, hasPausedTrack, createSoundDetector, SfuModels, isPinned, name, NoiseCancellationSettingsModeEnum, CallingState, combineComparators, screenSharing, speakerLayoutSortPreset, paginatedLayoutSortPreset, CallTypes, defaultSortPreset, humanize, StreamVideoClient } from '@stream-io/video-client';
5
5
  import { useFloating, offset, shift, flip, size, autoUpdate, FloatingOverlay, FloatingPortal, arrow, FloatingArrow, useListItem, useListNavigation, useTypeahead, useClick, useDismiss, useRole, useInteractions, FloatingFocusManager, FloatingList } from '@floating-ui/react';
6
6
  import clsx from 'clsx';
7
7
 
8
- const Audio = ({ participant, trackType = 'audioTrack', ...rest }) => {
8
+ const Audio = memo(function Audio({ participant, trackType = 'audioTrack', ...rest }) {
9
9
  const call = useCall();
10
10
  const [audioElement, setAudioElement] = useState(null);
11
11
  const { userId, sessionId } = participant;
@@ -18,7 +18,7 @@ const Audio = ({ participant, trackType = 'audioTrack', ...rest }) => {
18
18
  };
19
19
  }, [call, sessionId, audioElement, trackType]);
20
20
  return (jsx("audio", { autoPlay: true, ...rest, ref: setAudioElement, "data-user-id": userId, "data-session-id": sessionId, "data-track-type": trackType }));
21
- };
21
+ });
22
22
  Audio.displayName = 'Audio';
23
23
 
24
24
  const ParticipantsAudio = (props) => {
@@ -94,7 +94,7 @@ const useVerticalScrollPosition = (scrollElement, threshold = SCROLL_THRESHOLD)
94
94
  };
95
95
  const resizeObserver = new ResizeObserver(scrollHandler);
96
96
  resizeObserver.observe(scrollElement);
97
- scrollElement.addEventListener('scroll', scrollHandler);
97
+ scrollElement.addEventListener('scroll', scrollHandler, { passive: true });
98
98
  return () => {
99
99
  scrollElement.removeEventListener('scroll', scrollHandler);
100
100
  resizeObserver.disconnect();
@@ -122,7 +122,7 @@ const useHorizontalScrollPosition = (scrollElement, threshold = SCROLL_THRESHOLD
122
122
  };
123
123
  const resizeObserver = new ResizeObserver(scrollHandler);
124
124
  resizeObserver.observe(scrollElement);
125
- scrollElement.addEventListener('scroll', scrollHandler);
125
+ scrollElement.addEventListener('scroll', scrollHandler, { passive: true });
126
126
  return () => {
127
127
  scrollElement.removeEventListener('scroll', scrollHandler);
128
128
  resizeObserver.disconnect();
@@ -213,6 +213,7 @@ function useDeviceList(devices, selectedDeviceId) {
213
213
  * @param options - Options for customizing the drag-to-scroll behavior.
214
214
  */
215
215
  function useDragToScroll(element, options = {}) {
216
+ const { enabled, decay = 0.95, minVelocity = 0.5, dragThreshold = 5, } = options;
216
217
  const stateRef = useRef({
217
218
  isDragging: false,
218
219
  isPointerActive: false,
@@ -225,9 +226,8 @@ function useDragToScroll(element, options = {}) {
225
226
  startY: 0,
226
227
  });
227
228
  useEffect(() => {
228
- if (!element || !options.enabled)
229
+ if (!element || !enabled)
229
230
  return;
230
- const { decay = 0.95, minVelocity = 0.5, dragThreshold = 5 } = options;
231
231
  const state = stateRef.current;
232
232
  const stopMomentum = () => {
233
233
  if (state.rafId) {
@@ -311,7 +311,7 @@ function useDragToScroll(element, options = {}) {
311
311
  window.removeEventListener('pointercancel', onPointerUpOrCancel);
312
312
  stopMomentum();
313
313
  };
314
- }, [element, options]);
314
+ }, [element, enabled, decay, minVelocity, dragThreshold]);
315
315
  }
316
316
 
317
317
  var MenuVisualType;
@@ -541,9 +541,10 @@ const BaseVideo = forwardRef(function BaseVideo({ stream, ...rest }, ref) {
541
541
  if (stream === videoElement.srcObject)
542
542
  return;
543
543
  videoElement.srcObject = stream;
544
+ let timeoutId;
544
545
  if (Browsers.isSafari() || Browsers.isFirefox()) {
545
546
  // Firefox and Safari have some timing issue
546
- setTimeout(() => {
547
+ timeoutId = setTimeout(() => {
547
548
  videoElement.srcObject = stream;
548
549
  videoElement.play().catch((e) => {
549
550
  console.error(`Failed to play stream`, e);
@@ -551,6 +552,7 @@ const BaseVideo = forwardRef(function BaseVideo({ stream, ...rest }, ref) {
551
552
  }, 0);
552
553
  }
553
554
  return () => {
555
+ clearTimeout(timeoutId);
554
556
  videoElement.pause();
555
557
  videoElement.srcObject = null;
556
558
  };
@@ -587,6 +589,7 @@ const DefaultPictureInPicturePlaceholder = forwardRef(function DefaultPictureInP
587
589
  return (jsx(BaseVideoPlaceholder, { ref: ref, ...props, children: t('Video is playing in a popup') }));
588
590
  });
589
591
 
592
+ const ABSOLUTE_POSITION_STYLE = { position: 'absolute' };
590
593
  const Video$1 = ({ enabled = true, mirror, trackType, participant, className, VideoPlaceholder = DefaultVideoPlaceholder, PictureInPicturePlaceholder = DefaultPictureInPicturePlaceholder, refs, ...rest }) => {
591
594
  const { sessionId, videoStream, screenShareStream, viewportVisibilityState, isLocalParticipant, userId, } = participant;
592
595
  const call = useCall();
@@ -659,7 +662,7 @@ const Video$1 = ({ enabled = true, mirror, trackType, participant, className, Vi
659
662
  }), "data-user-id": userId, "data-session-id": sessionId, ref: (element) => {
660
663
  setVideoElement(element);
661
664
  refs?.setVideoElement?.(element);
662
- } })), isPiP && PictureInPicturePlaceholder && (jsx(PictureInPicturePlaceholder, { style: { position: 'absolute' }, participant: participant })), (hasNoVideoOrInvisible || isVideoPaused) && VideoPlaceholder && (jsx(VideoPlaceholder, { style: { position: 'absolute' }, participant: participant, ref: refs?.setVideoPlaceholderElement }))] }));
665
+ } })), isPiP && PictureInPicturePlaceholder && (jsx(PictureInPicturePlaceholder, { style: ABSOLUTE_POSITION_STYLE, participant: participant })), (hasNoVideoOrInvisible || isVideoPaused) && VideoPlaceholder && (jsx(VideoPlaceholder, { style: ABSOLUTE_POSITION_STYLE, participant: participant, ref: refs?.setVideoPlaceholderElement }))] }));
663
666
  };
664
667
  Video$1.displayName = 'Video';
665
668
 
@@ -1018,16 +1021,20 @@ const ScreenShareButton = (props) => {
1018
1021
  const AudioVolumeIndicator = () => {
1019
1022
  const { useMicrophoneState } = useCallStateHooks();
1020
1023
  const { isEnabled, mediaStream } = useMicrophoneState();
1021
- const [audioLevel, setAudioLevel] = useState(0);
1024
+ const barRef = useRef(null);
1022
1025
  useEffect(() => {
1023
1026
  if (!isEnabled || !mediaStream)
1024
1027
  return;
1025
- const disposeSoundDetector = createSoundDetector(mediaStream, ({ audioLevel: al }) => setAudioLevel(al), { detectionFrequencyInMs: 80, destroyStreamOnStop: false });
1028
+ const disposeSoundDetector = createSoundDetector(mediaStream, ({ audioLevel: al }) => {
1029
+ if (barRef.current) {
1030
+ barRef.current.style.transform = `scaleX(${al / 100})`;
1031
+ }
1032
+ }, { detectionFrequencyInMs: 80, destroyStreamOnStop: false });
1026
1033
  return () => {
1027
1034
  disposeSoundDetector().catch(console.error);
1028
1035
  };
1029
1036
  }, [isEnabled, mediaStream]);
1030
- return (jsxs("div", { className: "str-video__audio-volume-indicator", children: [jsx(Icon, { icon: isEnabled ? 'mic' : 'mic-off' }), jsx("div", { className: "str-video__audio-volume-indicator__bar", children: jsx("div", { className: "str-video__audio-volume-indicator__bar-value", style: { transform: `scaleX(${audioLevel / 100})` } }) })] }));
1037
+ return (jsxs("div", { className: "str-video__audio-volume-indicator", children: [jsx(Icon, { icon: isEnabled ? 'mic' : 'mic-off' }), jsx("div", { className: "str-video__audio-volume-indicator__bar", children: jsx("div", { ref: barRef, className: "str-video__audio-volume-indicator__bar-value" }) })] }));
1031
1038
  };
1032
1039
 
1033
1040
  const LEVEL_BARS = 5;
@@ -1201,7 +1208,7 @@ const SpeakerTest = (props) => {
1201
1208
  const audioElementRef = useRef(null);
1202
1209
  const [isPlaying, setIsPlaying] = useState(false);
1203
1210
  const { t } = useI18n();
1204
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.38.0"}/assets/piano.mp3`, } = props;
1211
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.39.1"}/assets/piano.mp3`, } = props;
1205
1212
  // Update audio output device when selection changes
1206
1213
  useEffect(() => {
1207
1214
  const audio = audioElementRef.current;
@@ -1500,13 +1507,14 @@ const CallParticipantListHeader = ({ onClose, }) => {
1500
1507
  return (jsxs("div", { className: "str-video__participant-list-header", children: [jsxs("div", { className: "str-video__participant-list-header__title", children: [t('Participants'), ' ', jsxs("span", { className: "str-video__participant-list-header__title-count", children: ["[", participants.length, "]"] }), anonymousParticipantCount > 0 && (jsx("span", { className: "str-video__participant-list-header__title-anonymous", children: t('Anonymous', { count: anonymousParticipantCount }) }))] }), jsx(IconButton, { onClick: onClose, className: "str-video__participant-list-header__close-button", icon: "close" })] }));
1501
1508
  };
1502
1509
 
1503
- const CallParticipantListingItem = ({ participant, DisplayName = DefaultDisplayName, }) => {
1510
+ const CallParticipantListingItem = memo(function CallParticipantListingItem({ participant, DisplayName = DefaultDisplayName, }) {
1504
1511
  const isAudioOn = hasAudio(participant);
1505
1512
  const isVideoOn = hasVideo(participant);
1506
1513
  const isPinnedOn = isPinned(participant);
1507
1514
  const { t } = useI18n();
1508
1515
  return (jsxs("div", { className: "str-video__participant-listing-item", children: [jsx(Avatar, { name: participant.name, imageSrc: participant.image }), jsx(DisplayName, { participant: participant }), jsxs("div", { className: "str-video__participant-listing-item__media-indicator-group", children: [jsx(MediaIndicator, { title: isAudioOn ? t('Microphone on') : t('Microphone off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isAudioOn ? 'mic' : 'mic-off'}`) }), jsx(MediaIndicator, { title: isVideoOn ? t('Camera on') : t('Camera off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isVideoOn ? 'camera' : 'camera-off'}`) }), isPinnedOn && (jsx(MediaIndicator, { title: t('Pinned'), className: clsx('str-video__participant-listing-item__icon', 'str-video__participant-listing-item__icon-pinned') })), jsx(MenuToggle, { placement: "bottom-end", ToggleButton: ToggleButton$2, children: jsx(ParticipantViewContext.Provider, { value: { participant, trackType: 'none' }, children: jsx(ParticipantActionsContextMenu, {}) }) })] })] }));
1509
- };
1516
+ });
1517
+ CallParticipantListingItem.displayName = 'CallParticipantListingItem';
1510
1518
  const MediaIndicator = (props) => (jsx(WithTooltip, { ...props }));
1511
1519
  const DefaultDisplayName = ({ participant }) => {
1512
1520
  const connectedUser = useConnectedUser();
@@ -1538,18 +1546,19 @@ const EmptyParticipantSearchList = () => {
1538
1546
 
1539
1547
  const SearchInput = ({ exitSearch, isActive, ...rest }) => {
1540
1548
  const [inputElement, setInputElement] = useState(null);
1549
+ const onExitSearch = useEffectEvent(exitSearch);
1541
1550
  useEffect(() => {
1542
1551
  if (!inputElement)
1543
1552
  return;
1544
1553
  const handleKeyDown = (e) => {
1545
1554
  if (e.key.toLowerCase() === 'escape')
1546
- exitSearch();
1555
+ onExitSearch();
1547
1556
  };
1548
1557
  inputElement.addEventListener('keydown', handleKeyDown);
1549
1558
  return () => {
1550
1559
  inputElement.removeEventListener('keydown', handleKeyDown);
1551
1560
  };
1552
- }, [exitSearch, inputElement]);
1561
+ }, [inputElement]);
1553
1562
  return (jsxs("div", { className: clsx('str-video__search-input__container', {
1554
1563
  'str-video__search-input__container--active': isActive,
1555
1564
  }), children: [jsx("input", { placeholder: "Search", ...rest, ref: setInputElement }), isActive ? (jsx("button", { className: "str-video__search-input__clear-btn", onClick: exitSearch, children: jsx("span", { className: "str-video__search-input__icon--active" }) })) : (jsx("span", { className: "str-video__search-input__icon" }))] }));
@@ -1620,10 +1629,19 @@ const CallParticipantListContentHeader = ({ userListType, setUserListType, }) =>
1620
1629
  const ActiveUsersSearchResults = ({ searchQuery, activeUsersSearchFn: activeUsersSearchFnFromProps, debounceSearchInterval, }) => {
1621
1630
  const { useParticipants } = useCallStateHooks();
1622
1631
  const participants = useParticipants({ sortBy: name });
1632
+ // cache: each distinct name is normalized once
1633
+ const normalizedNameCache = useRef(null);
1623
1634
  const activeUsersSearchFn = useCallback(async (queryString) => {
1624
1635
  const normalizedQuery = normalizeString(queryString);
1625
- const queryRegExp = new RegExp(normalizedQuery, 'i');
1626
- return participants.filter((p) => normalizeString(p.name).match(queryRegExp));
1636
+ const cache = (normalizedNameCache.current ??= new Map());
1637
+ return participants.filter((p) => {
1638
+ let normalizedName = cache.get(p.name);
1639
+ if (normalizedName === undefined) {
1640
+ normalizedName = normalizeString(p.name);
1641
+ cache.set(p.name, normalizedName);
1642
+ }
1643
+ return normalizedName.includes(normalizedQuery);
1644
+ });
1627
1645
  }, [participants]);
1628
1646
  const { searchQueryInProgress, searchResults } = useSearch({
1629
1647
  searchFn: activeUsersSearchFnFromProps ?? activeUsersSearchFn,
@@ -1636,8 +1654,8 @@ const BlockedUsersSearchResults = ({ blockedUsersSearchFn: blockedUsersSearchFnF
1636
1654
  const { useCallBlockedUserIds } = useCallStateHooks();
1637
1655
  const blockedUsers = useCallBlockedUserIds();
1638
1656
  const blockedUsersSearchFn = useCallback(async (queryString) => {
1639
- const queryRegExp = new RegExp(queryString, 'i');
1640
- return blockedUsers.filter((userId) => userId.match(queryRegExp));
1657
+ const normalizedQuery = queryString.toLowerCase();
1658
+ return blockedUsers.filter((userId) => userId.toLowerCase().includes(normalizedQuery));
1641
1659
  }, [blockedUsers]);
1642
1660
  const { searchQueryInProgress, searchResults } = useSearch({
1643
1661
  searchFn: blockedUsersSearchFnFromProps ?? blockedUsersSearchFn,
@@ -1908,7 +1926,7 @@ const SpeechIndicator = () => {
1908
1926
  return (jsxs("span", { className: clsx('str-video__speech-indicator', isSpeaking && 'str-video__speech-indicator--speaking', isDominantSpeaker && 'str-video__speech-indicator--dominant'), children: [jsx("span", { className: "str-video__speech-indicator__bar" }), jsx("span", { className: "str-video__speech-indicator__bar" }), jsx("span", { className: "str-video__speech-indicator__bar" })] }));
1909
1927
  };
1910
1928
 
1911
- const ParticipantView = forwardRef(function ParticipantView({ participant, trackType = 'videoTrack', mirror, muteAudio, refs: { setVideoElement, setVideoPlaceholderElement } = {}, className, VideoPlaceholder, PictureInPicturePlaceholder, ParticipantViewUI = DefaultParticipantViewUI, }, ref) {
1929
+ const ParticipantView = memo(forwardRef(function ParticipantView({ participant, trackType = 'videoTrack', mirror, muteAudio, refs: { setVideoElement, setVideoPlaceholderElement } = {}, className, VideoPlaceholder, PictureInPicturePlaceholder, ParticipantViewUI = DefaultParticipantViewUI, }, ref) {
1912
1930
  const { isLocalParticipant, isSpeaking, isDominantSpeaker, sessionId } = participant;
1913
1931
  const hasAudioTrack = hasAudio(participant);
1914
1932
  const hasVideoTrack = hasVideo(participant);
@@ -1952,7 +1970,7 @@ const ParticipantView = forwardRef(function ParticipantView({ participant, track
1952
1970
  }, className: clsx('str-video__participant-view', isDominantSpeaker && 'str-video__participant-view--dominant-speaker', isSpeaking && 'str-video__participant-view--speaking', !hasVideoTrack && 'str-video__participant-view--no-video', !hasAudioTrack && 'str-video__participant-view--no-audio', className), children: jsxs(ParticipantViewContext.Provider, { value: participantViewContextValue, children: [!isLocalParticipant && !muteAudio && (jsxs(Fragment, { children: [hasAudioTrack && (jsx(Audio, { participant: participant, trackType: "audioTrack" })), hasScreenShareAudioTrack && (jsx(Audio, { participant: participant, trackType: "screenShareAudioTrack" }))] })), jsx(Video$1, { VideoPlaceholder: VideoPlaceholder, PictureInPicturePlaceholder: PictureInPicturePlaceholder, participant: participant, trackType: trackType, refs: videoRefs, enabled: isLocalParticipant ||
1953
1971
  trackType !== 'videoTrack' ||
1954
1972
  isParticipantVideoEnabled(participant.sessionId), mirror: mirror, autoPlay: true }), isComponentType(ParticipantViewUI) ? (jsx(ParticipantViewUI, {})) : (ParticipantViewUI)] }) }));
1955
- });
1973
+ }));
1956
1974
  ParticipantView.displayName = 'ParticipantView';
1957
1975
 
1958
1976
  // re-exporting the StreamCallProvider as StreamCall
@@ -2170,20 +2188,20 @@ function checkConditions(obj, conditions) {
2170
2188
  const eqOperatorValue = isDateString(eqOperator.$eq)
2171
2189
  ? new Date(eqOperator.$eq).getTime()
2172
2190
  : eqOperator.$eq;
2173
- match && (match = eqOperatorValue === value);
2191
+ match &&= eqOperatorValue === value;
2174
2192
  }
2175
2193
  else if (maybeOperator && '$neq' in operator) {
2176
2194
  const neqOperator = operator;
2177
- match && (match = neqOperator.$neq !== value);
2195
+ match &&= neqOperator.$neq !== value;
2178
2196
  }
2179
2197
  else if (maybeOperator && '$in' in operator) {
2180
2198
  const inOperator = operator;
2181
- match && (match = inOperator.$in.includes(value));
2199
+ match &&= inOperator.$in.includes(value);
2182
2200
  }
2183
2201
  else if (maybeOperator && '$contains' in operator) {
2184
2202
  if (Array.isArray(value)) {
2185
2203
  const containsOperator = operator;
2186
- match && (match = value.includes(containsOperator.$contains));
2204
+ match &&= value.includes(containsOperator.$contains);
2187
2205
  }
2188
2206
  else {
2189
2207
  match = false;
@@ -2194,35 +2212,35 @@ function checkConditions(obj, conditions) {
2194
2212
  const gtOperatorValue = isDateString(gtOperator.$gt)
2195
2213
  ? new Date(gtOperator.$gt).getTime()
2196
2214
  : gtOperator.$gt;
2197
- match && (match = value > gtOperatorValue);
2215
+ match &&= value > gtOperatorValue;
2198
2216
  }
2199
2217
  else if (maybeOperator && '$gte' in operator) {
2200
2218
  const gteOperator = operator;
2201
2219
  const gteOperatorValue = isDateString(gteOperator.$gte)
2202
2220
  ? new Date(gteOperator.$gte).getTime()
2203
2221
  : gteOperator.$gte;
2204
- match && (match = value >= gteOperatorValue);
2222
+ match &&= value >= gteOperatorValue;
2205
2223
  }
2206
2224
  else if (maybeOperator && '$lt' in operator) {
2207
2225
  const ltOperator = operator;
2208
2226
  const ltOperatorValue = isDateString(ltOperator.$lt)
2209
2227
  ? new Date(ltOperator.$lt).getTime()
2210
2228
  : ltOperator.$lt;
2211
- match && (match = value < ltOperatorValue);
2229
+ match &&= value < ltOperatorValue;
2212
2230
  }
2213
2231
  else if (maybeOperator && '$lte' in operator) {
2214
2232
  const lteOperator = operator;
2215
2233
  const lteOperatorValue = isDateString(lteOperator.$lte)
2216
2234
  ? new Date(lteOperator.$lte).getTime()
2217
2235
  : lteOperator.$lte;
2218
- match && (match = value <= lteOperatorValue);
2236
+ match &&= value <= lteOperatorValue;
2219
2237
  // } else if (maybeOperator && '$autocomplete' in operator) {
2220
2238
  // TODO: regexp solution maybe?
2221
2239
  // match &&= false;
2222
2240
  }
2223
2241
  else {
2224
2242
  const eqValue = operator;
2225
- match && (match = eqValue === value);
2243
+ match &&= eqValue === value;
2226
2244
  }
2227
2245
  if (!match) {
2228
2246
  return false;
@@ -3061,6 +3079,7 @@ const useWakeLock = () => {
3061
3079
  if (interrupted)
3062
3080
  return wls.release();
3063
3081
  wakeLockSentinel = wls;
3082
+ return undefined;
3064
3083
  })
3065
3084
  .catch((error) => console.debug(`Couldn't setup WakeLock due to: ${error}`));
3066
3085
  return () => {
@@ -3395,12 +3414,12 @@ const ViewerLobby = ({ onJoin }) => {
3395
3414
  }
3396
3415
  }, [canJoin, autoJoin, onJoin]);
3397
3416
  useEffect(() => {
3398
- if (!canJoinEarly) {
3399
- const handle = setInterval(() => {
3400
- setCanJoinEarly(checkCanJoinEarly(startsAt, joinAheadTimeSeconds));
3401
- }, 1000);
3402
- return () => clearInterval(handle);
3403
- }
3417
+ if (canJoinEarly)
3418
+ return;
3419
+ const handle = setInterval(() => {
3420
+ setCanJoinEarly(checkCanJoinEarly(startsAt, joinAheadTimeSeconds));
3421
+ }, 1000);
3422
+ return () => clearInterval(handle);
3404
3423
  }, [canJoinEarly, startsAt, joinAheadTimeSeconds]);
3405
3424
  useEffect(() => {
3406
3425
  if (!startsAt || startsAtPassed)