@stream-io/video-react-sdk 1.38.0 → 1.39.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +26 -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
@@ -7,7 +7,7 @@ var videoClient = require('@stream-io/video-client');
7
7
  var react$1 = require('@floating-ui/react');
8
8
  var clsx = require('clsx');
9
9
 
10
- const Audio = ({ participant, trackType = 'audioTrack', ...rest }) => {
10
+ const Audio = react.memo(function Audio({ participant, trackType = 'audioTrack', ...rest }) {
11
11
  const call = videoReactBindings.useCall();
12
12
  const [audioElement, setAudioElement] = react.useState(null);
13
13
  const { userId, sessionId } = participant;
@@ -20,7 +20,7 @@ const Audio = ({ participant, trackType = 'audioTrack', ...rest }) => {
20
20
  };
21
21
  }, [call, sessionId, audioElement, trackType]);
22
22
  return (jsxRuntime.jsx("audio", { autoPlay: true, ...rest, ref: setAudioElement, "data-user-id": userId, "data-session-id": sessionId, "data-track-type": trackType }));
23
- };
23
+ });
24
24
  Audio.displayName = 'Audio';
25
25
 
26
26
  const ParticipantsAudio = (props) => {
@@ -96,7 +96,7 @@ const useVerticalScrollPosition = (scrollElement, threshold = SCROLL_THRESHOLD)
96
96
  };
97
97
  const resizeObserver = new ResizeObserver(scrollHandler);
98
98
  resizeObserver.observe(scrollElement);
99
- scrollElement.addEventListener('scroll', scrollHandler);
99
+ scrollElement.addEventListener('scroll', scrollHandler, { passive: true });
100
100
  return () => {
101
101
  scrollElement.removeEventListener('scroll', scrollHandler);
102
102
  resizeObserver.disconnect();
@@ -124,7 +124,7 @@ const useHorizontalScrollPosition = (scrollElement, threshold = SCROLL_THRESHOLD
124
124
  };
125
125
  const resizeObserver = new ResizeObserver(scrollHandler);
126
126
  resizeObserver.observe(scrollElement);
127
- scrollElement.addEventListener('scroll', scrollHandler);
127
+ scrollElement.addEventListener('scroll', scrollHandler, { passive: true });
128
128
  return () => {
129
129
  scrollElement.removeEventListener('scroll', scrollHandler);
130
130
  resizeObserver.disconnect();
@@ -215,6 +215,7 @@ function useDeviceList(devices, selectedDeviceId) {
215
215
  * @param options - Options for customizing the drag-to-scroll behavior.
216
216
  */
217
217
  function useDragToScroll(element, options = {}) {
218
+ const { enabled, decay = 0.95, minVelocity = 0.5, dragThreshold = 5, } = options;
218
219
  const stateRef = react.useRef({
219
220
  isDragging: false,
220
221
  isPointerActive: false,
@@ -227,9 +228,8 @@ function useDragToScroll(element, options = {}) {
227
228
  startY: 0,
228
229
  });
229
230
  react.useEffect(() => {
230
- if (!element || !options.enabled)
231
+ if (!element || !enabled)
231
232
  return;
232
- const { decay = 0.95, minVelocity = 0.5, dragThreshold = 5 } = options;
233
233
  const state = stateRef.current;
234
234
  const stopMomentum = () => {
235
235
  if (state.rafId) {
@@ -313,7 +313,7 @@ function useDragToScroll(element, options = {}) {
313
313
  window.removeEventListener('pointercancel', onPointerUpOrCancel);
314
314
  stopMomentum();
315
315
  };
316
- }, [element, options]);
316
+ }, [element, enabled, decay, minVelocity, dragThreshold]);
317
317
  }
318
318
 
319
319
  var MenuVisualType;
@@ -543,9 +543,10 @@ const BaseVideo = react.forwardRef(function BaseVideo({ stream, ...rest }, ref)
543
543
  if (stream === videoElement.srcObject)
544
544
  return;
545
545
  videoElement.srcObject = stream;
546
+ let timeoutId;
546
547
  if (videoClient.Browsers.isSafari() || videoClient.Browsers.isFirefox()) {
547
548
  // Firefox and Safari have some timing issue
548
- setTimeout(() => {
549
+ timeoutId = setTimeout(() => {
549
550
  videoElement.srcObject = stream;
550
551
  videoElement.play().catch((e) => {
551
552
  console.error(`Failed to play stream`, e);
@@ -553,6 +554,7 @@ const BaseVideo = react.forwardRef(function BaseVideo({ stream, ...rest }, ref)
553
554
  }, 0);
554
555
  }
555
556
  return () => {
557
+ clearTimeout(timeoutId);
556
558
  videoElement.pause();
557
559
  videoElement.srcObject = null;
558
560
  };
@@ -589,6 +591,7 @@ const DefaultPictureInPicturePlaceholder = react.forwardRef(function DefaultPict
589
591
  return (jsxRuntime.jsx(BaseVideoPlaceholder, { ref: ref, ...props, children: t('Video is playing in a popup') }));
590
592
  });
591
593
 
594
+ const ABSOLUTE_POSITION_STYLE = { position: 'absolute' };
592
595
  const Video$1 = ({ enabled = true, mirror, trackType, participant, className, VideoPlaceholder = DefaultVideoPlaceholder, PictureInPicturePlaceholder = DefaultPictureInPicturePlaceholder, refs, ...rest }) => {
593
596
  const { sessionId, videoStream, screenShareStream, viewportVisibilityState, isLocalParticipant, userId, } = participant;
594
597
  const call = videoReactBindings.useCall();
@@ -661,7 +664,7 @@ const Video$1 = ({ enabled = true, mirror, trackType, participant, className, Vi
661
664
  }), "data-user-id": userId, "data-session-id": sessionId, ref: (element) => {
662
665
  setVideoElement(element);
663
666
  refs?.setVideoElement?.(element);
664
- } })), isPiP && PictureInPicturePlaceholder && (jsxRuntime.jsx(PictureInPicturePlaceholder, { style: { position: 'absolute' }, participant: participant })), (hasNoVideoOrInvisible || isVideoPaused) && VideoPlaceholder && (jsxRuntime.jsx(VideoPlaceholder, { style: { position: 'absolute' }, participant: participant, ref: refs?.setVideoPlaceholderElement }))] }));
667
+ } })), isPiP && PictureInPicturePlaceholder && (jsxRuntime.jsx(PictureInPicturePlaceholder, { style: ABSOLUTE_POSITION_STYLE, participant: participant })), (hasNoVideoOrInvisible || isVideoPaused) && VideoPlaceholder && (jsxRuntime.jsx(VideoPlaceholder, { style: ABSOLUTE_POSITION_STYLE, participant: participant, ref: refs?.setVideoPlaceholderElement }))] }));
665
668
  };
666
669
  Video$1.displayName = 'Video';
667
670
 
@@ -1020,16 +1023,20 @@ const ScreenShareButton = (props) => {
1020
1023
  const AudioVolumeIndicator = () => {
1021
1024
  const { useMicrophoneState } = videoReactBindings.useCallStateHooks();
1022
1025
  const { isEnabled, mediaStream } = useMicrophoneState();
1023
- const [audioLevel, setAudioLevel] = react.useState(0);
1026
+ const barRef = react.useRef(null);
1024
1027
  react.useEffect(() => {
1025
1028
  if (!isEnabled || !mediaStream)
1026
1029
  return;
1027
- const disposeSoundDetector = videoClient.createSoundDetector(mediaStream, ({ audioLevel: al }) => setAudioLevel(al), { detectionFrequencyInMs: 80, destroyStreamOnStop: false });
1030
+ const disposeSoundDetector = videoClient.createSoundDetector(mediaStream, ({ audioLevel: al }) => {
1031
+ if (barRef.current) {
1032
+ barRef.current.style.transform = `scaleX(${al / 100})`;
1033
+ }
1034
+ }, { detectionFrequencyInMs: 80, destroyStreamOnStop: false });
1028
1035
  return () => {
1029
1036
  disposeSoundDetector().catch(console.error);
1030
1037
  };
1031
1038
  }, [isEnabled, mediaStream]);
1032
- return (jsxRuntime.jsxs("div", { className: "str-video__audio-volume-indicator", children: [jsxRuntime.jsx(Icon, { icon: isEnabled ? 'mic' : 'mic-off' }), jsxRuntime.jsx("div", { className: "str-video__audio-volume-indicator__bar", children: jsxRuntime.jsx("div", { className: "str-video__audio-volume-indicator__bar-value", style: { transform: `scaleX(${audioLevel / 100})` } }) })] }));
1039
+ return (jsxRuntime.jsxs("div", { className: "str-video__audio-volume-indicator", children: [jsxRuntime.jsx(Icon, { icon: isEnabled ? 'mic' : 'mic-off' }), jsxRuntime.jsx("div", { className: "str-video__audio-volume-indicator__bar", children: jsxRuntime.jsx("div", { ref: barRef, className: "str-video__audio-volume-indicator__bar-value" }) })] }));
1033
1040
  };
1034
1041
 
1035
1042
  const LEVEL_BARS = 5;
@@ -1203,7 +1210,7 @@ const SpeakerTest = (props) => {
1203
1210
  const audioElementRef = react.useRef(null);
1204
1211
  const [isPlaying, setIsPlaying] = react.useState(false);
1205
1212
  const { t } = videoReactBindings.useI18n();
1206
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.38.0"}/assets/piano.mp3`, } = props;
1213
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.39.0"}/assets/piano.mp3`, } = props;
1207
1214
  // Update audio output device when selection changes
1208
1215
  react.useEffect(() => {
1209
1216
  const audio = audioElementRef.current;
@@ -1502,13 +1509,14 @@ const CallParticipantListHeader = ({ onClose, }) => {
1502
1509
  return (jsxRuntime.jsxs("div", { className: "str-video__participant-list-header", children: [jsxRuntime.jsxs("div", { className: "str-video__participant-list-header__title", children: [t('Participants'), ' ', jsxRuntime.jsxs("span", { className: "str-video__participant-list-header__title-count", children: ["[", participants.length, "]"] }), anonymousParticipantCount > 0 && (jsxRuntime.jsx("span", { className: "str-video__participant-list-header__title-anonymous", children: t('Anonymous', { count: anonymousParticipantCount }) }))] }), jsxRuntime.jsx(IconButton, { onClick: onClose, className: "str-video__participant-list-header__close-button", icon: "close" })] }));
1503
1510
  };
1504
1511
 
1505
- const CallParticipantListingItem = ({ participant, DisplayName = DefaultDisplayName, }) => {
1512
+ const CallParticipantListingItem = react.memo(function CallParticipantListingItem({ participant, DisplayName = DefaultDisplayName, }) {
1506
1513
  const isAudioOn = videoClient.hasAudio(participant);
1507
1514
  const isVideoOn = videoClient.hasVideo(participant);
1508
1515
  const isPinnedOn = videoClient.isPinned(participant);
1509
1516
  const { t } = videoReactBindings.useI18n();
1510
1517
  return (jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item", children: [jsxRuntime.jsx(Avatar, { name: participant.name, imageSrc: participant.image }), jsxRuntime.jsx(DisplayName, { participant: participant }), jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item__media-indicator-group", children: [jsxRuntime.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'}`) }), jsxRuntime.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 && (jsxRuntime.jsx(MediaIndicator, { title: t('Pinned'), className: clsx('str-video__participant-listing-item__icon', 'str-video__participant-listing-item__icon-pinned') })), jsxRuntime.jsx(MenuToggle, { placement: "bottom-end", ToggleButton: ToggleButton$2, children: jsxRuntime.jsx(ParticipantViewContext.Provider, { value: { participant, trackType: 'none' }, children: jsxRuntime.jsx(ParticipantActionsContextMenu, {}) }) })] })] }));
1511
- };
1518
+ });
1519
+ CallParticipantListingItem.displayName = 'CallParticipantListingItem';
1512
1520
  const MediaIndicator = (props) => (jsxRuntime.jsx(WithTooltip, { ...props }));
1513
1521
  const DefaultDisplayName = ({ participant }) => {
1514
1522
  const connectedUser = videoReactBindings.useConnectedUser();
@@ -1540,18 +1548,19 @@ const EmptyParticipantSearchList = () => {
1540
1548
 
1541
1549
  const SearchInput = ({ exitSearch, isActive, ...rest }) => {
1542
1550
  const [inputElement, setInputElement] = react.useState(null);
1551
+ const onExitSearch = videoReactBindings.useEffectEvent(exitSearch);
1543
1552
  react.useEffect(() => {
1544
1553
  if (!inputElement)
1545
1554
  return;
1546
1555
  const handleKeyDown = (e) => {
1547
1556
  if (e.key.toLowerCase() === 'escape')
1548
- exitSearch();
1557
+ onExitSearch();
1549
1558
  };
1550
1559
  inputElement.addEventListener('keydown', handleKeyDown);
1551
1560
  return () => {
1552
1561
  inputElement.removeEventListener('keydown', handleKeyDown);
1553
1562
  };
1554
- }, [exitSearch, inputElement]);
1563
+ }, [inputElement]);
1555
1564
  return (jsxRuntime.jsxs("div", { className: clsx('str-video__search-input__container', {
1556
1565
  'str-video__search-input__container--active': isActive,
1557
1566
  }), children: [jsxRuntime.jsx("input", { placeholder: "Search", ...rest, ref: setInputElement }), isActive ? (jsxRuntime.jsx("button", { className: "str-video__search-input__clear-btn", onClick: exitSearch, children: jsxRuntime.jsx("span", { className: "str-video__search-input__icon--active" }) })) : (jsxRuntime.jsx("span", { className: "str-video__search-input__icon" }))] }));
@@ -1622,10 +1631,19 @@ const CallParticipantListContentHeader = ({ userListType, setUserListType, }) =>
1622
1631
  const ActiveUsersSearchResults = ({ searchQuery, activeUsersSearchFn: activeUsersSearchFnFromProps, debounceSearchInterval, }) => {
1623
1632
  const { useParticipants } = videoReactBindings.useCallStateHooks();
1624
1633
  const participants = useParticipants({ sortBy: videoClient.name });
1634
+ // cache: each distinct name is normalized once
1635
+ const normalizedNameCache = react.useRef(null);
1625
1636
  const activeUsersSearchFn = react.useCallback(async (queryString) => {
1626
1637
  const normalizedQuery = normalizeString(queryString);
1627
- const queryRegExp = new RegExp(normalizedQuery, 'i');
1628
- return participants.filter((p) => normalizeString(p.name).match(queryRegExp));
1638
+ const cache = (normalizedNameCache.current ??= new Map());
1639
+ return participants.filter((p) => {
1640
+ let normalizedName = cache.get(p.name);
1641
+ if (normalizedName === undefined) {
1642
+ normalizedName = normalizeString(p.name);
1643
+ cache.set(p.name, normalizedName);
1644
+ }
1645
+ return normalizedName.includes(normalizedQuery);
1646
+ });
1629
1647
  }, [participants]);
1630
1648
  const { searchQueryInProgress, searchResults } = useSearch({
1631
1649
  searchFn: activeUsersSearchFnFromProps ?? activeUsersSearchFn,
@@ -1638,8 +1656,8 @@ const BlockedUsersSearchResults = ({ blockedUsersSearchFn: blockedUsersSearchFnF
1638
1656
  const { useCallBlockedUserIds } = videoReactBindings.useCallStateHooks();
1639
1657
  const blockedUsers = useCallBlockedUserIds();
1640
1658
  const blockedUsersSearchFn = react.useCallback(async (queryString) => {
1641
- const queryRegExp = new RegExp(queryString, 'i');
1642
- return blockedUsers.filter((userId) => userId.match(queryRegExp));
1659
+ const normalizedQuery = queryString.toLowerCase();
1660
+ return blockedUsers.filter((userId) => userId.toLowerCase().includes(normalizedQuery));
1643
1661
  }, [blockedUsers]);
1644
1662
  const { searchQueryInProgress, searchResults } = useSearch({
1645
1663
  searchFn: blockedUsersSearchFnFromProps ?? blockedUsersSearchFn,
@@ -1910,7 +1928,7 @@ const SpeechIndicator = () => {
1910
1928
  return (jsxRuntime.jsxs("span", { className: clsx('str-video__speech-indicator', isSpeaking && 'str-video__speech-indicator--speaking', isDominantSpeaker && 'str-video__speech-indicator--dominant'), children: [jsxRuntime.jsx("span", { className: "str-video__speech-indicator__bar" }), jsxRuntime.jsx("span", { className: "str-video__speech-indicator__bar" }), jsxRuntime.jsx("span", { className: "str-video__speech-indicator__bar" })] }));
1911
1929
  };
1912
1930
 
1913
- const ParticipantView = react.forwardRef(function ParticipantView({ participant, trackType = 'videoTrack', mirror, muteAudio, refs: { setVideoElement, setVideoPlaceholderElement } = {}, className, VideoPlaceholder, PictureInPicturePlaceholder, ParticipantViewUI = DefaultParticipantViewUI, }, ref) {
1931
+ const ParticipantView = react.memo(react.forwardRef(function ParticipantView({ participant, trackType = 'videoTrack', mirror, muteAudio, refs: { setVideoElement, setVideoPlaceholderElement } = {}, className, VideoPlaceholder, PictureInPicturePlaceholder, ParticipantViewUI = DefaultParticipantViewUI, }, ref) {
1914
1932
  const { isLocalParticipant, isSpeaking, isDominantSpeaker, sessionId } = participant;
1915
1933
  const hasAudioTrack = videoClient.hasAudio(participant);
1916
1934
  const hasVideoTrack = videoClient.hasVideo(participant);
@@ -1954,7 +1972,7 @@ const ParticipantView = react.forwardRef(function ParticipantView({ participant,
1954
1972
  }, 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: jsxRuntime.jsxs(ParticipantViewContext.Provider, { value: participantViewContextValue, children: [!isLocalParticipant && !muteAudio && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasAudioTrack && (jsxRuntime.jsx(Audio, { participant: participant, trackType: "audioTrack" })), hasScreenShareAudioTrack && (jsxRuntime.jsx(Audio, { participant: participant, trackType: "screenShareAudioTrack" }))] })), jsxRuntime.jsx(Video$1, { VideoPlaceholder: VideoPlaceholder, PictureInPicturePlaceholder: PictureInPicturePlaceholder, participant: participant, trackType: trackType, refs: videoRefs, enabled: isLocalParticipant ||
1955
1973
  trackType !== 'videoTrack' ||
1956
1974
  isParticipantVideoEnabled(participant.sessionId), mirror: mirror, autoPlay: true }), isComponentType(ParticipantViewUI) ? (jsxRuntime.jsx(ParticipantViewUI, {})) : (ParticipantViewUI)] }) }));
1957
- });
1975
+ }));
1958
1976
  ParticipantView.displayName = 'ParticipantView';
1959
1977
 
1960
1978
  // re-exporting the StreamCallProvider as StreamCall
@@ -2172,20 +2190,20 @@ function checkConditions(obj, conditions) {
2172
2190
  const eqOperatorValue = isDateString(eqOperator.$eq)
2173
2191
  ? new Date(eqOperator.$eq).getTime()
2174
2192
  : eqOperator.$eq;
2175
- match && (match = eqOperatorValue === value);
2193
+ match &&= eqOperatorValue === value;
2176
2194
  }
2177
2195
  else if (maybeOperator && '$neq' in operator) {
2178
2196
  const neqOperator = operator;
2179
- match && (match = neqOperator.$neq !== value);
2197
+ match &&= neqOperator.$neq !== value;
2180
2198
  }
2181
2199
  else if (maybeOperator && '$in' in operator) {
2182
2200
  const inOperator = operator;
2183
- match && (match = inOperator.$in.includes(value));
2201
+ match &&= inOperator.$in.includes(value);
2184
2202
  }
2185
2203
  else if (maybeOperator && '$contains' in operator) {
2186
2204
  if (Array.isArray(value)) {
2187
2205
  const containsOperator = operator;
2188
- match && (match = value.includes(containsOperator.$contains));
2206
+ match &&= value.includes(containsOperator.$contains);
2189
2207
  }
2190
2208
  else {
2191
2209
  match = false;
@@ -2196,35 +2214,35 @@ function checkConditions(obj, conditions) {
2196
2214
  const gtOperatorValue = isDateString(gtOperator.$gt)
2197
2215
  ? new Date(gtOperator.$gt).getTime()
2198
2216
  : gtOperator.$gt;
2199
- match && (match = value > gtOperatorValue);
2217
+ match &&= value > gtOperatorValue;
2200
2218
  }
2201
2219
  else if (maybeOperator && '$gte' in operator) {
2202
2220
  const gteOperator = operator;
2203
2221
  const gteOperatorValue = isDateString(gteOperator.$gte)
2204
2222
  ? new Date(gteOperator.$gte).getTime()
2205
2223
  : gteOperator.$gte;
2206
- match && (match = value >= gteOperatorValue);
2224
+ match &&= value >= gteOperatorValue;
2207
2225
  }
2208
2226
  else if (maybeOperator && '$lt' in operator) {
2209
2227
  const ltOperator = operator;
2210
2228
  const ltOperatorValue = isDateString(ltOperator.$lt)
2211
2229
  ? new Date(ltOperator.$lt).getTime()
2212
2230
  : ltOperator.$lt;
2213
- match && (match = value < ltOperatorValue);
2231
+ match &&= value < ltOperatorValue;
2214
2232
  }
2215
2233
  else if (maybeOperator && '$lte' in operator) {
2216
2234
  const lteOperator = operator;
2217
2235
  const lteOperatorValue = isDateString(lteOperator.$lte)
2218
2236
  ? new Date(lteOperator.$lte).getTime()
2219
2237
  : lteOperator.$lte;
2220
- match && (match = value <= lteOperatorValue);
2238
+ match &&= value <= lteOperatorValue;
2221
2239
  // } else if (maybeOperator && '$autocomplete' in operator) {
2222
2240
  // TODO: regexp solution maybe?
2223
2241
  // match &&= false;
2224
2242
  }
2225
2243
  else {
2226
2244
  const eqValue = operator;
2227
- match && (match = eqValue === value);
2245
+ match &&= eqValue === value;
2228
2246
  }
2229
2247
  if (!match) {
2230
2248
  return false;
@@ -3063,6 +3081,7 @@ const useWakeLock = () => {
3063
3081
  if (interrupted)
3064
3082
  return wls.release();
3065
3083
  wakeLockSentinel = wls;
3084
+ return undefined;
3066
3085
  })
3067
3086
  .catch((error) => console.debug(`Couldn't setup WakeLock due to: ${error}`));
3068
3087
  return () => {
@@ -3397,12 +3416,12 @@ const ViewerLobby = ({ onJoin }) => {
3397
3416
  }
3398
3417
  }, [canJoin, autoJoin, onJoin]);
3399
3418
  react.useEffect(() => {
3400
- if (!canJoinEarly) {
3401
- const handle = setInterval(() => {
3402
- setCanJoinEarly(checkCanJoinEarly(startsAt, joinAheadTimeSeconds));
3403
- }, 1000);
3404
- return () => clearInterval(handle);
3405
- }
3419
+ if (canJoinEarly)
3420
+ return;
3421
+ const handle = setInterval(() => {
3422
+ setCanJoinEarly(checkCanJoinEarly(startsAt, joinAheadTimeSeconds));
3423
+ }, 1000);
3424
+ return () => clearInterval(handle);
3406
3425
  }, [canJoinEarly, startsAt, joinAheadTimeSeconds]);
3407
3426
  react.useEffect(() => {
3408
3427
  if (!startsAt || startsAtPassed)