@stream-io/video-react-sdk 1.6.6 → 1.7.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/dist/index.es.js CHANGED
@@ -393,11 +393,31 @@ const GenericMenuButtonItem = ({ children, ...rest }) => {
393
393
 
394
394
  const Icon = ({ className, icon }) => (jsx("span", { className: clsx('str-video__icon', icon && `str-video__icon--${icon}`, className) }));
395
395
 
396
+ function usePictureInPictureState(videoElement) {
397
+ const [isPiP, setIsPiP] = useState(document.pictureInPictureElement === videoElement);
398
+ if (!videoElement && isPiP)
399
+ setIsPiP(false);
400
+ useEffect(() => {
401
+ if (!videoElement)
402
+ return;
403
+ const handlePiP = () => {
404
+ setIsPiP(document.pictureInPictureElement === videoElement);
405
+ };
406
+ videoElement.addEventListener('enterpictureinpicture', handlePiP);
407
+ videoElement.addEventListener('leavepictureinpicture', handlePiP);
408
+ return () => {
409
+ videoElement.removeEventListener('enterpictureinpicture', handlePiP);
410
+ videoElement.removeEventListener('leavepictureinpicture', handlePiP);
411
+ };
412
+ }, [videoElement]);
413
+ return isPiP;
414
+ }
415
+
396
416
  const ParticipantActionsContextMenu = () => {
397
417
  const { participant, participantViewElement, videoElement } = useParticipantViewContext();
398
418
  const [fullscreenModeOn, setFullscreenModeOn] = useState(!!document.fullscreenElement);
399
- const [pictureInPictureElement, setPictureInPictureElement] = useState(document.pictureInPictureElement);
400
419
  const call = useCall();
420
+ const isPiP = usePictureInPictureState(videoElement ?? undefined);
401
421
  const { t } = useI18n();
402
422
  const { pin, sessionId, userId } = participant;
403
423
  const hasAudioTrack = hasAudio(participant);
@@ -466,21 +486,8 @@ const ParticipantActionsContextMenu = () => {
466
486
  document.removeEventListener('fullscreenchange', handleFullscreenChange);
467
487
  };
468
488
  }, []);
469
- useEffect(() => {
470
- if (!videoElement)
471
- return;
472
- const handlePiP = () => {
473
- setPictureInPictureElement(document.pictureInPictureElement);
474
- };
475
- videoElement.addEventListener('enterpictureinpicture', handlePiP);
476
- videoElement.addEventListener('leavepictureinpicture', handlePiP);
477
- return () => {
478
- videoElement.removeEventListener('enterpictureinpicture', handlePiP);
479
- videoElement.removeEventListener('leavepictureinpicture', handlePiP);
480
- };
481
- }, [videoElement]);
482
489
  const togglePictureInPicture = () => {
483
- if (videoElement && pictureInPictureElement !== videoElement) {
490
+ if (videoElement && !isPiP) {
484
491
  return videoElement
485
492
  .requestPictureInPicture()
486
493
  .catch(console.error);
@@ -491,9 +498,7 @@ const ParticipantActionsContextMenu = () => {
491
498
  return (jsxs(GenericMenu, { onItemClick: close, children: [jsxs(GenericMenuButtonItem, { onClick: toggleParticipantPin, disabled: pin && !pin.isLocalPin, children: [jsx(Icon, { icon: "pin" }), pin ? t('Unpin') : t('Pin')] }), jsxs(Restricted, { requiredGrants: [OwnCapability.PIN_FOR_EVERYONE], children: [jsxs(GenericMenuButtonItem, { onClick: pinForEveryone, disabled: pin && !pin.isLocalPin, children: [jsx(Icon, { icon: "pin" }), t('Pin for everyone')] }), jsxs(GenericMenuButtonItem, { onClick: unpinForEveryone, disabled: !pin || pin.isLocalPin, children: [jsx(Icon, { icon: "pin" }), t('Unpin for everyone')] })] }), jsx(Restricted, { requiredGrants: [OwnCapability.BLOCK_USERS], children: jsxs(GenericMenuButtonItem, { onClick: blockUser, children: [jsx(Icon, { icon: "not-allowed" }), t('Block')] }) }), jsxs(Restricted, { requiredGrants: [OwnCapability.MUTE_USERS], children: [hasVideoTrack && (jsxs(GenericMenuButtonItem, { onClick: muteVideo, children: [jsx(Icon, { icon: "camera-off-outline" }), t('Turn off video')] })), hasScreenShareTrack && (jsxs(GenericMenuButtonItem, { onClick: muteScreenShare, children: [jsx(Icon, { icon: "screen-share-off" }), t('Turn off screen share')] })), hasAudioTrack && (jsxs(GenericMenuButtonItem, { onClick: muteAudio, children: [jsx(Icon, { icon: "no-audio" }), t('Mute audio')] })), hasScreenShareAudioTrack && (jsxs(GenericMenuButtonItem, { onClick: muteScreenShareAudio, children: [jsx(Icon, { icon: "no-audio" }), t('Mute screen share audio')] }))] }), participantViewElement && (jsx(GenericMenuButtonItem, { onClick: toggleFullscreenMode, children: t('{{ direction }} fullscreen', {
492
499
  direction: fullscreenModeOn ? t('Leave') : t('Enter'),
493
500
  }) })), videoElement && document.pictureInPictureEnabled && (jsx(GenericMenuButtonItem, { onClick: togglePictureInPicture, children: t('{{ direction }} picture-in-picture', {
494
- direction: pictureInPictureElement === videoElement
495
- ? t('Leave')
496
- : t('Enter'),
501
+ direction: isPiP ? t('Leave') : t('Enter'),
497
502
  }) })), jsxs(Restricted, { requiredGrants: [OwnCapability.UPDATE_CALL_PERMISSIONS], children: [jsx(GenericMenuButtonItem, { onClick: grantPermission(OwnCapability.SEND_AUDIO), children: t('Allow audio') }), jsx(GenericMenuButtonItem, { onClick: grantPermission(OwnCapability.SEND_VIDEO), children: t('Allow video') }), jsx(GenericMenuButtonItem, { onClick: grantPermission(OwnCapability.SCREENSHARE), children: t('Allow screen sharing') }), jsx(GenericMenuButtonItem, { onClick: revokePermission(OwnCapability.SEND_AUDIO), children: t('Disable audio') }), jsx(GenericMenuButtonItem, { onClick: revokePermission(OwnCapability.SEND_VIDEO), children: t('Disable video') }), jsx(GenericMenuButtonItem, { onClick: revokePermission(OwnCapability.SCREENSHARE), children: t('Disable screen sharing') })] })] }));
498
503
  };
499
504
 
@@ -548,12 +553,11 @@ const BaseVideo = forwardRef(function BaseVideo({ stream, ...rest }, ref) {
548
553
  } }));
549
554
  });
550
555
 
551
- const DefaultVideoPlaceholder = forwardRef(function DefaultVideoPlaceholder({ participant, style }, ref) {
552
- const { t } = useI18n();
556
+ const BaseVideoPlaceholder = forwardRef(function DefaultVideoPlaceholder({ participant, style, children }, ref) {
553
557
  const [error, setError] = useState(false);
554
558
  const name = participant.name || participant.userId;
555
559
  return (jsxs("div", { className: "str-video__video-placeholder", style: style, ref: ref, children: [(!participant.image || error) &&
556
- (name ? (jsx(InitialsFallback, { name: name })) : (jsx("div", { className: "str-video__video-placeholder__no-video-label", children: t('Video is disabled') }))), participant.image && !error && (jsx("img", { onError: () => setError(true), alt: "video-placeholder", className: "str-video__video-placeholder__avatar", src: participant.image }))] }));
560
+ (name ? (jsx(InitialsFallback, { name: name })) : (jsx("div", { className: "str-video__video-placeholder__no-video-label", children: children }))), participant.image && !error && (jsx("img", { onError: () => setError(true), alt: name, className: "str-video__video-placeholder__avatar", src: participant.image }))] }));
557
561
  });
558
562
  const InitialsFallback = (props) => {
559
563
  const { name } = props;
@@ -565,13 +569,24 @@ const InitialsFallback = (props) => {
565
569
  return (jsx("div", { className: "str-video__video-placeholder__initials-fallback", children: initials }));
566
570
  };
567
571
 
568
- const Video$1 = ({ enabled = true, mirror, trackType, participant, className, VideoPlaceholder = DefaultVideoPlaceholder, refs, ...rest }) => {
572
+ const DefaultVideoPlaceholder = forwardRef(function DefaultVideoPlaceholder(props, ref) {
573
+ const { t } = useI18n();
574
+ return (jsx(BaseVideoPlaceholder, { ref: ref, ...props, children: t('Video is disabled') }));
575
+ });
576
+
577
+ const DefaultPictureInPicturePlaceholder = forwardRef(function DefaultPictureInPicturePlaceholder(props, ref) {
578
+ const { t } = useI18n();
579
+ return (jsx(BaseVideoPlaceholder, { ref: ref, ...props, children: t('Video is playing in a popup') }));
580
+ });
581
+
582
+ const Video$1 = ({ enabled = true, mirror, trackType, participant, className, VideoPlaceholder = DefaultVideoPlaceholder, PictureInPicturePlaceholder = DefaultPictureInPicturePlaceholder, refs, ...rest }) => {
569
583
  const { sessionId, videoStream, screenShareStream, viewportVisibilityState, isLocalParticipant, userId, } = participant;
570
584
  const call = useCall();
571
585
  const [videoElement, setVideoElement] = useState(null);
572
586
  // start with true, will flip once the video starts playing
573
587
  const [isVideoPaused, setIsVideoPaused] = useState(true);
574
588
  const [isWideMode, setIsWideMode] = useState(true);
589
+ const isPiP = usePictureInPictureState(videoElement ?? undefined);
575
590
  const stream = trackType === 'videoTrack'
576
591
  ? videoStream
577
592
  : trackType === 'screenShareTrack'
@@ -633,7 +648,7 @@ const Video$1 = ({ enabled = true, mirror, trackType, participant, className, Vi
633
648
  }), "data-user-id": userId, "data-session-id": sessionId, ref: (element) => {
634
649
  setVideoElement(element);
635
650
  refs?.setVideoElement?.(element);
636
- } })), (hasNoVideoOrInvisible || isVideoPaused) && VideoPlaceholder && (jsx(VideoPlaceholder, { style: { position: 'absolute' }, participant: participant, ref: refs?.setVideoPlaceholderElement }))] }));
651
+ } })), isPiP && (jsx(DefaultPictureInPicturePlaceholder, { style: { position: 'absolute' }, participant: participant })), (hasNoVideoOrInvisible || isVideoPaused) && VideoPlaceholder && (jsx(VideoPlaceholder, { style: { position: 'absolute' }, participant: participant, ref: refs?.setVideoPlaceholderElement }))] }));
637
652
  };
638
653
  Video$1.displayName = 'Video';
639
654
 
@@ -2083,7 +2098,7 @@ const SpeechIndicator = () => {
2083
2098
  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" })] }));
2084
2099
  };
2085
2100
 
2086
- const ParticipantView = forwardRef(function ParticipantView({ participant, trackType = 'videoTrack', mirror, muteAudio, refs: { setVideoElement, setVideoPlaceholderElement } = {}, className, VideoPlaceholder, ParticipantViewUI = DefaultParticipantViewUI, }, ref) {
2101
+ const ParticipantView = forwardRef(function ParticipantView({ participant, trackType = 'videoTrack', mirror, muteAudio, refs: { setVideoElement, setVideoPlaceholderElement } = {}, className, VideoPlaceholder, PictureInPicturePlaceholder, ParticipantViewUI = DefaultParticipantViewUI, }, ref) {
2087
2102
  const { isLocalParticipant, isSpeaking, isDominantSpeaker, sessionId } = participant;
2088
2103
  const hasAudioTrack = hasAudio(participant);
2089
2104
  const hasVideoTrack = hasVideo(participant);
@@ -2125,7 +2140,7 @@ const ParticipantView = forwardRef(function ParticipantView({ participant, track
2125
2140
  return (jsx("div", { "data-testid": "participant-view", ref: (element) => {
2126
2141
  applyElementToRef(ref, element);
2127
2142
  setTrackedElement(element);
2128
- }, 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, participant: participant, trackType: trackType, refs: videoRefs, enabled: isLocalParticipant ||
2143
+ }, 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 ||
2129
2144
  trackType !== 'videoTrack' ||
2130
2145
  isParticipantVideoEnabled(participant.sessionId), mirror: mirror, autoPlay: true }), isComponentType(ParticipantViewUI) ? (jsx(ParticipantViewUI, {})) : (ParticipantViewUI)] }) }));
2131
2146
  });
@@ -2378,12 +2393,12 @@ const formatDuration = (durationInMs) => {
2378
2393
  };
2379
2394
 
2380
2395
  const GROUP_SIZE = 16;
2381
- const PaginatedGridLayoutGroup = ({ group, mirror, VideoPlaceholder, ParticipantViewUI, }) => {
2396
+ const PaginatedGridLayoutGroup = ({ group, mirror, VideoPlaceholder, PictureInPicturePlaceholder, ParticipantViewUI, }) => {
2382
2397
  return (jsx("div", { className: clsx('str-video__paginated-grid-layout__group', {
2383
2398
  'str-video__paginated-grid-layout--one': group.length === 1,
2384
2399
  'str-video__paginated-grid-layout--two-four': group.length >= 2 && group.length <= 4,
2385
2400
  'str-video__paginated-grid-layout--five-nine': group.length >= 5 && group.length <= 9,
2386
- }), children: group.map((participant) => (jsx(ParticipantView, { participant: participant, muteAudio: true, mirror: mirror, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI }, participant.sessionId))) }));
2401
+ }), children: group.map((participant) => (jsx(ParticipantView, { participant: participant, muteAudio: true, mirror: mirror, VideoPlaceholder: VideoPlaceholder, PictureInPicturePlaceholder: PictureInPicturePlaceholder, ParticipantViewUI: ParticipantViewUI }, participant.sessionId))) }));
2387
2402
  };
2388
2403
  const PaginatedGridLayout = (props) => {
2389
2404
  const { groupSize = (props.groupSize || 0) > 0
@@ -2475,7 +2490,7 @@ hostElement, limit) => {
2475
2490
  };
2476
2491
 
2477
2492
  const DefaultParticipantViewUIBar = () => (jsx(DefaultParticipantViewUI, { menuPlacement: "top-end" }));
2478
- const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, ParticipantViewUISpotlight = DefaultParticipantViewUI, VideoPlaceholder, participantsBarPosition = 'bottom', participantsBarLimit, mirrorLocalParticipantVideo = true, excludeLocalParticipant = false, pageArrowsVisible = true, }) => {
2493
+ const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, ParticipantViewUISpotlight = DefaultParticipantViewUI, VideoPlaceholder, PictureInPicturePlaceholder, participantsBarPosition = 'bottom', participantsBarLimit, mirrorLocalParticipantVideo = true, excludeLocalParticipant = false, pageArrowsVisible = true, }) => {
2479
2494
  const call = useCall();
2480
2495
  const { useParticipants, useRemoteParticipants } = useCallStateHooks();
2481
2496
  const allParticipants = useParticipants();
@@ -2515,7 +2530,7 @@ const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, Par
2515
2530
  const renderParticipantsBar = participantsBarPosition &&
2516
2531
  (participantsWithAppliedLimit.length > 0 || isSpeakerScreenSharing);
2517
2532
  return (jsxs("div", { className: "str-video__speaker-layout__wrapper", children: [jsx(ParticipantsAudio, { participants: remoteParticipants }), jsxs("div", { className: clsx('str-video__speaker-layout', participantsBarPosition &&
2518
- `str-video__speaker-layout--variant-${participantsBarPosition}`), children: [jsx("div", { className: "str-video__speaker-layout__spotlight", children: participantInSpotlight && (jsx(ParticipantView, { participant: participantInSpotlight, muteAudio: true, mirror: mirror, trackType: isSpeakerScreenSharing ? 'screenShareTrack' : 'videoTrack', ParticipantViewUI: ParticipantViewUISpotlight, VideoPlaceholder: VideoPlaceholder })) }), renderParticipantsBar && (jsxs("div", { ref: setButtonsWrapperElement, className: "str-video__speaker-layout__participants-bar-buttons-wrapper", children: [jsx("div", { className: "str-video__speaker-layout__participants-bar-wrapper", ref: setParticipantsBarWrapperElement, children: jsxs("div", { ref: setParticipantsBarElement, className: "str-video__speaker-layout__participants-bar", children: [isSpeakerScreenSharing && (jsx("div", { className: "str-video__speaker-layout__participant-tile", children: jsx(ParticipantView, { participant: participantInSpotlight, ParticipantViewUI: ParticipantViewUIBar, VideoPlaceholder: VideoPlaceholder, mirror: mirror, muteAudio: true }) }, participantInSpotlight.sessionId)), participantsWithAppliedLimit.map((participant) => (jsx("div", { className: "str-video__speaker-layout__participant-tile", children: jsx(ParticipantView, { participant: participant, ParticipantViewUI: ParticipantViewUIBar, VideoPlaceholder: VideoPlaceholder, mirror: mirror, muteAudio: true }) }, participant.sessionId)))] }) }), pageArrowsVisible && isVertical && (jsx(VerticalScrollButtons, { scrollWrapper: participantsBarWrapperElement })), pageArrowsVisible && isHorizontal && (jsx(HorizontalScrollButtons, { scrollWrapper: participantsBarWrapperElement }))] }))] })] }));
2533
+ `str-video__speaker-layout--variant-${participantsBarPosition}`), children: [jsx("div", { className: "str-video__speaker-layout__spotlight", children: participantInSpotlight && (jsx(ParticipantView, { participant: participantInSpotlight, muteAudio: true, mirror: mirror, trackType: isSpeakerScreenSharing ? 'screenShareTrack' : 'videoTrack', ParticipantViewUI: ParticipantViewUISpotlight, VideoPlaceholder: VideoPlaceholder, PictureInPicturePlaceholder: PictureInPicturePlaceholder })) }), renderParticipantsBar && (jsxs("div", { ref: setButtonsWrapperElement, className: "str-video__speaker-layout__participants-bar-buttons-wrapper", children: [jsx("div", { className: "str-video__speaker-layout__participants-bar-wrapper", ref: setParticipantsBarWrapperElement, children: jsxs("div", { ref: setParticipantsBarElement, className: "str-video__speaker-layout__participants-bar", children: [isSpeakerScreenSharing && (jsx("div", { className: "str-video__speaker-layout__participant-tile", children: jsx(ParticipantView, { participant: participantInSpotlight, ParticipantViewUI: ParticipantViewUIBar, VideoPlaceholder: VideoPlaceholder, PictureInPicturePlaceholder: PictureInPicturePlaceholder, mirror: mirror, muteAudio: true }) }, participantInSpotlight.sessionId)), participantsWithAppliedLimit.map((participant) => (jsx("div", { className: "str-video__speaker-layout__participant-tile", children: jsx(ParticipantView, { participant: participant, ParticipantViewUI: ParticipantViewUIBar, VideoPlaceholder: VideoPlaceholder, PictureInPicturePlaceholder: PictureInPicturePlaceholder, mirror: mirror, muteAudio: true }) }, participant.sessionId)))] }) }), pageArrowsVisible && isVertical && (jsx(VerticalScrollButtons, { scrollWrapper: participantsBarWrapperElement })), pageArrowsVisible && isHorizontal && (jsx(HorizontalScrollButtons, { scrollWrapper: participantsBarWrapperElement }))] }))] })] }));
2519
2534
  };
2520
2535
  SpeakerLayout.displayName = 'SpeakerLayout';
2521
2536
  const HorizontalScrollButtons = ({ scrollWrapper, }) => {
@@ -2561,7 +2576,7 @@ const LivestreamPlayer = (props) => {
2561
2576
  return (jsx(StreamCall, { call: call, children: jsx(LivestreamLayout, { ...layoutProps }) }));
2562
2577
  };
2563
2578
 
2564
- const [major, minor, patch] = ("1.6.6").split('.');
2579
+ const [major, minor, patch] = ("1.7.0").split('.');
2565
2580
  setSdkInfo({
2566
2581
  type: SfuModels.SdkType.REACT,
2567
2582
  major,