Merge pull request #648 from vector-im/dbkr/tiles_for_everyone
Show tiles for members we're trying to connect to
This commit is contained in:
commit
b8af9a0733
12 changed files with 88 additions and 62 deletions
|
@ -2,6 +2,7 @@
|
||||||
"{{count}} people connected|one": "{{count}} person connected",
|
"{{count}} people connected|one": "{{count}} person connected",
|
||||||
"{{count}} people connected|other": "{{count}} people connected",
|
"{{count}} people connected|other": "{{count}} people connected",
|
||||||
"{{displayName}}, your call is now ended": "{{displayName}}, your call is now ended",
|
"{{displayName}}, your call is now ended": "{{displayName}}, your call is now ended",
|
||||||
|
"{{name}} (Connecting...)": "{{name}} (Connecting...)",
|
||||||
"{{name}} is presenting": "{{name}} is presenting",
|
"{{name}} is presenting": "{{name}} is presenting",
|
||||||
"{{name}} is talking…": "{{name}} is talking…",
|
"{{name}} is talking…": "{{name}} is talking…",
|
||||||
"{{names}}, {{name}}": "{{names}}, {{name}}",
|
"{{names}}, {{name}}": "{{names}}, {{name}}",
|
||||||
|
|
|
@ -76,7 +76,6 @@ export function GroupCallView({
|
||||||
toggleScreensharing,
|
toggleScreensharing,
|
||||||
requestingScreenshare,
|
requestingScreenshare,
|
||||||
isScreensharing,
|
isScreensharing,
|
||||||
localScreenshareFeed,
|
|
||||||
screenshareFeeds,
|
screenshareFeeds,
|
||||||
participants,
|
participants,
|
||||||
unencryptedEventsFromUsers,
|
unencryptedEventsFromUsers,
|
||||||
|
@ -221,6 +220,7 @@ export function GroupCallView({
|
||||||
client={client}
|
client={client}
|
||||||
roomName={groupCall.room.name}
|
roomName={groupCall.room.name}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
|
participants={participants}
|
||||||
microphoneMuted={microphoneMuted}
|
microphoneMuted={microphoneMuted}
|
||||||
localVideoMuted={localVideoMuted}
|
localVideoMuted={localVideoMuted}
|
||||||
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
||||||
|
@ -230,7 +230,6 @@ export function GroupCallView({
|
||||||
onLeave={onLeave}
|
onLeave={onLeave}
|
||||||
toggleScreensharing={toggleScreensharing}
|
toggleScreensharing={toggleScreensharing}
|
||||||
isScreensharing={isScreensharing}
|
isScreensharing={isScreensharing}
|
||||||
localScreenshareFeed={localScreenshareFeed}
|
|
||||||
screenshareFeeds={screenshareFeeds}
|
screenshareFeeds={screenshareFeeds}
|
||||||
roomIdOrAlias={roomIdOrAlias}
|
roomIdOrAlias={roomIdOrAlias}
|
||||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||||
|
|
|
@ -70,6 +70,7 @@ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||||
interface Props {
|
interface Props {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
groupCall: GroupCall;
|
groupCall: GroupCall;
|
||||||
|
participants: RoomMember[];
|
||||||
roomName: string;
|
roomName: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
microphoneMuted: boolean;
|
microphoneMuted: boolean;
|
||||||
|
@ -82,14 +83,16 @@ interface Props {
|
||||||
onLeave: () => void;
|
onLeave: () => void;
|
||||||
isScreensharing: boolean;
|
isScreensharing: boolean;
|
||||||
screenshareFeeds: CallFeed[];
|
screenshareFeeds: CallFeed[];
|
||||||
localScreenshareFeed: CallFeed;
|
|
||||||
roomIdOrAlias: string;
|
roomIdOrAlias: string;
|
||||||
unencryptedEventsFromUsers: Set<string>;
|
unencryptedEventsFromUsers: Set<string>;
|
||||||
hideHeader: boolean;
|
hideHeader: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Participant {
|
// Represents something that should get a tile on the layout,
|
||||||
|
// ie. a user's video feed or a screen share feed.
|
||||||
|
export interface TileDescriptor {
|
||||||
id: string;
|
id: string;
|
||||||
|
member: RoomMember;
|
||||||
focused: boolean;
|
focused: boolean;
|
||||||
presenter: boolean;
|
presenter: boolean;
|
||||||
callFeed?: CallFeed;
|
callFeed?: CallFeed;
|
||||||
|
@ -99,6 +102,7 @@ export interface Participant {
|
||||||
export function InCallView({
|
export function InCallView({
|
||||||
client,
|
client,
|
||||||
groupCall,
|
groupCall,
|
||||||
|
participants,
|
||||||
roomName,
|
roomName,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
microphoneMuted,
|
microphoneMuted,
|
||||||
|
@ -111,7 +115,6 @@ export function InCallView({
|
||||||
toggleScreensharing,
|
toggleScreensharing,
|
||||||
isScreensharing,
|
isScreensharing,
|
||||||
screenshareFeeds,
|
screenshareFeeds,
|
||||||
localScreenshareFeed,
|
|
||||||
roomIdOrAlias,
|
roomIdOrAlias,
|
||||||
unencryptedEventsFromUsers,
|
unencryptedEventsFromUsers,
|
||||||
hideHeader,
|
hideHeader,
|
||||||
|
@ -185,39 +188,48 @@ export function InCallView({
|
||||||
}, [setLayout]);
|
}, [setLayout]);
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
const participants: Participant[] = [];
|
const tileDescriptors: TileDescriptor[] = [];
|
||||||
|
|
||||||
for (const callFeed of userMediaFeeds) {
|
// one tile for each participants, to start with (we want a tile for everyone we
|
||||||
participants.push({
|
// think should be in the call, even if we don't have a media feed for them yet)
|
||||||
id: callFeed.stream.id,
|
for (const p of participants) {
|
||||||
callFeed,
|
const userMediaFeed = userMediaFeeds.find((f) => f.userId === p.userId);
|
||||||
focused:
|
|
||||||
screenshareFeeds.length === 0 && callFeed.userId === activeSpeaker,
|
// NB. this assumes that the same user can't join more than once from multiple
|
||||||
isLocal: callFeed.isLocal(),
|
// devices, but the participants are just RoomMembers, so this assumption is baked
|
||||||
|
// into GroupCall itself.
|
||||||
|
tileDescriptors.push({
|
||||||
|
id: p.userId,
|
||||||
|
member: p,
|
||||||
|
callFeed: userMediaFeed,
|
||||||
|
focused: screenshareFeeds.length === 0 && p.userId === activeSpeaker,
|
||||||
|
isLocal: p.userId === client.getUserId(),
|
||||||
presenter: false,
|
presenter: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const callFeed of screenshareFeeds) {
|
// add the screenshares too
|
||||||
const userMediaItem = participants.find(
|
for (const screenshareFeed of screenshareFeeds) {
|
||||||
(item) => item.callFeed.userId === callFeed.userId
|
const userMediaItem = tileDescriptors.find(
|
||||||
|
(item) => item.member.userId === screenshareFeed.userId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (userMediaItem) {
|
if (userMediaItem) {
|
||||||
userMediaItem.presenter = true;
|
userMediaItem.presenter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
participants.push({
|
tileDescriptors.push({
|
||||||
id: callFeed.stream.id,
|
id: screenshareFeed.stream.id,
|
||||||
callFeed,
|
member: userMediaItem?.member,
|
||||||
|
callFeed: screenshareFeed,
|
||||||
focused: true,
|
focused: true,
|
||||||
isLocal: callFeed.isLocal(),
|
isLocal: screenshareFeed.isLocal(),
|
||||||
presenter: false,
|
presenter: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return participants;
|
return tileDescriptors;
|
||||||
}, [userMediaFeeds, activeSpeaker, screenshareFeeds]);
|
}, [client, participants, userMediaFeeds, activeSpeaker, screenshareFeeds]);
|
||||||
|
|
||||||
// The maximised participant: either the participant that the user has
|
// The maximised participant: either the participant that the user has
|
||||||
// manually put in fullscreen, or the focused (active) participant if the
|
// manually put in fullscreen, or the focused (active) participant if the
|
||||||
|
@ -281,7 +293,13 @@ export function InCallView({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VideoGrid items={items} layout={layout} disableAnimations={isSafari}>
|
<VideoGrid items={items} layout={layout} disableAnimations={isSafari}>
|
||||||
{({ item, ...rest }: { item: Participant; [x: string]: unknown }) => (
|
{({
|
||||||
|
item,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
item: TileDescriptor;
|
||||||
|
[x: string]: unknown;
|
||||||
|
}) => (
|
||||||
<VideoTileContainer
|
<VideoTileContainer
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
|
|
|
@ -51,7 +51,6 @@ export interface UseGroupCallReturnType {
|
||||||
requestingScreenshare: boolean;
|
requestingScreenshare: boolean;
|
||||||
isScreensharing: boolean;
|
isScreensharing: boolean;
|
||||||
screenshareFeeds: CallFeed[];
|
screenshareFeeds: CallFeed[];
|
||||||
localScreenshareFeed: CallFeed;
|
|
||||||
localDesktopCapturerSourceId: string;
|
localDesktopCapturerSourceId: string;
|
||||||
participants: RoomMember[];
|
participants: RoomMember[];
|
||||||
hasLocalParticipant: boolean;
|
hasLocalParticipant: boolean;
|
||||||
|
@ -68,7 +67,6 @@ interface State {
|
||||||
microphoneMuted: boolean;
|
microphoneMuted: boolean;
|
||||||
localVideoMuted: boolean;
|
localVideoMuted: boolean;
|
||||||
screenshareFeeds: CallFeed[];
|
screenshareFeeds: CallFeed[];
|
||||||
localScreenshareFeed: CallFeed;
|
|
||||||
localDesktopCapturerSourceId: string;
|
localDesktopCapturerSourceId: string;
|
||||||
isScreensharing: boolean;
|
isScreensharing: boolean;
|
||||||
requestingScreenshare: boolean;
|
requestingScreenshare: boolean;
|
||||||
|
@ -89,7 +87,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||||
localVideoMuted,
|
localVideoMuted,
|
||||||
isScreensharing,
|
isScreensharing,
|
||||||
screenshareFeeds,
|
screenshareFeeds,
|
||||||
localScreenshareFeed,
|
|
||||||
localDesktopCapturerSourceId,
|
localDesktopCapturerSourceId,
|
||||||
participants,
|
participants,
|
||||||
hasLocalParticipant,
|
hasLocalParticipant,
|
||||||
|
@ -107,7 +104,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||||
localVideoMuted: false,
|
localVideoMuted: false,
|
||||||
isScreensharing: false,
|
isScreensharing: false,
|
||||||
screenshareFeeds: [],
|
screenshareFeeds: [],
|
||||||
localScreenshareFeed: null,
|
|
||||||
localDesktopCapturerSourceId: null,
|
localDesktopCapturerSourceId: null,
|
||||||
requestingScreenshare: false,
|
requestingScreenshare: false,
|
||||||
participants: [],
|
participants: [],
|
||||||
|
@ -135,7 +131,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||||
microphoneMuted: groupCall.isMicrophoneMuted(),
|
microphoneMuted: groupCall.isMicrophoneMuted(),
|
||||||
localVideoMuted: groupCall.isLocalVideoMuted(),
|
localVideoMuted: groupCall.isLocalVideoMuted(),
|
||||||
isScreensharing: groupCall.isScreensharing(),
|
isScreensharing: groupCall.isScreensharing(),
|
||||||
localScreenshareFeed: groupCall.localScreenshareFeed,
|
|
||||||
localDesktopCapturerSourceId: groupCall.localDesktopCapturerSourceId,
|
localDesktopCapturerSourceId: groupCall.localDesktopCapturerSourceId,
|
||||||
screenshareFeeds: [...groupCall.screenshareFeeds],
|
screenshareFeeds: [...groupCall.screenshareFeeds],
|
||||||
participants: [...groupCall.participants],
|
participants: [...groupCall.participants],
|
||||||
|
@ -172,12 +167,11 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||||
|
|
||||||
function onLocalScreenshareStateChanged(
|
function onLocalScreenshareStateChanged(
|
||||||
isScreensharing: boolean,
|
isScreensharing: boolean,
|
||||||
localScreenshareFeed: CallFeed,
|
_localScreenshareFeed: CallFeed,
|
||||||
localDesktopCapturerSourceId: string
|
localDesktopCapturerSourceId: string
|
||||||
): void {
|
): void {
|
||||||
updateState({
|
updateState({
|
||||||
isScreensharing,
|
isScreensharing,
|
||||||
localScreenshareFeed,
|
|
||||||
localDesktopCapturerSourceId,
|
localDesktopCapturerSourceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -228,7 +222,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||||
microphoneMuted: groupCall.isMicrophoneMuted(),
|
microphoneMuted: groupCall.isMicrophoneMuted(),
|
||||||
localVideoMuted: groupCall.isLocalVideoMuted(),
|
localVideoMuted: groupCall.isLocalVideoMuted(),
|
||||||
isScreensharing: groupCall.isScreensharing(),
|
isScreensharing: groupCall.isScreensharing(),
|
||||||
localScreenshareFeed: groupCall.localScreenshareFeed,
|
|
||||||
localDesktopCapturerSourceId: groupCall.localDesktopCapturerSourceId,
|
localDesktopCapturerSourceId: groupCall.localDesktopCapturerSourceId,
|
||||||
screenshareFeeds: [...groupCall.screenshareFeeds],
|
screenshareFeeds: [...groupCall.screenshareFeeds],
|
||||||
participants: [...groupCall.participants],
|
participants: [...groupCall.participants],
|
||||||
|
@ -412,7 +405,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||||
requestingScreenshare,
|
requestingScreenshare,
|
||||||
isScreensharing,
|
isScreensharing,
|
||||||
screenshareFeeds,
|
screenshareFeeds,
|
||||||
localScreenshareFeed,
|
|
||||||
localDesktopCapturerSourceId,
|
localDesktopCapturerSourceId,
|
||||||
participants,
|
participants,
|
||||||
hasLocalParticipant,
|
hasLocalParticipant,
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { FC, useEffect, useRef } from "react";
|
import React, { FC, useEffect, useRef } from "react";
|
||||||
|
|
||||||
import { Participant } from "../room/InCallView";
|
import { TileDescriptor } from "../room/InCallView";
|
||||||
import { useCallFeed } from "./useCallFeed";
|
import { useCallFeed } from "./useCallFeed";
|
||||||
import { useMediaStreamTrackCount } from "./useMediaStream";
|
import { useMediaStreamTrackCount } from "./useMediaStream";
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import { useMediaStreamTrackCount } from "./useMediaStream";
|
||||||
// only way to a hook on an array
|
// only way to a hook on an array
|
||||||
|
|
||||||
interface AudioForParticipantProps {
|
interface AudioForParticipantProps {
|
||||||
item: Participant;
|
item: TileDescriptor;
|
||||||
audioContext: AudioContext;
|
audioContext: AudioContext;
|
||||||
audioDestination: AudioNode;
|
audioDestination: AudioNode;
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ export const AudioForParticipant: FC<AudioForParticipantProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AudioContainerProps {
|
interface AudioContainerProps {
|
||||||
items: Participant[];
|
items: TileDescriptor[];
|
||||||
audioContext: AudioContext;
|
audioContext: AudioContext;
|
||||||
audioDestination: AudioNode;
|
audioDestination: AudioNode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,12 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { RoomMember } from "matrix-js-sdk";
|
||||||
|
|
||||||
import { VideoGrid, useVideoGridLayout } from "./VideoGrid";
|
import { VideoGrid, useVideoGridLayout } from "./VideoGrid";
|
||||||
import { VideoTile } from "./VideoTile";
|
import { VideoTile } from "./VideoTile";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { Participant } from "../room/InCallView";
|
import { TileDescriptor } from "../room/InCallView";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "VideoGrid",
|
title: "VideoGrid",
|
||||||
|
@ -33,10 +34,11 @@ export const ParticipantsTest = () => {
|
||||||
const { layout, setLayout } = useVideoGridLayout(false);
|
const { layout, setLayout } = useVideoGridLayout(false);
|
||||||
const [participantCount, setParticipantCount] = useState(1);
|
const [participantCount, setParticipantCount] = useState(1);
|
||||||
|
|
||||||
const items: Participant[] = useMemo(
|
const items: TileDescriptor[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
new Array(participantCount).fill(undefined).map((_, i) => ({
|
new Array(participantCount).fill(undefined).map((_, i) => ({
|
||||||
id: (i + 1).toString(),
|
id: (i + 1).toString(),
|
||||||
|
member: new RoomMember("!fake:room.id", `@user${i}:fake.dummy`),
|
||||||
focused: false,
|
focused: false,
|
||||||
presenter: false,
|
presenter: false,
|
||||||
})),
|
})),
|
||||||
|
@ -77,6 +79,7 @@ export const ParticipantsTest = () => {
|
||||||
key={item.id}
|
key={item.id}
|
||||||
name={`User ${item.id}`}
|
name={`User ${item.id}`}
|
||||||
disableSpeakingIndicator={items.length < 3}
|
disableSpeakingIndicator={items.length < 3}
|
||||||
|
hasFeed={true}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { ReactDOMAttributes } from "@use-gesture/react/dist/declarations/src/typ
|
||||||
|
|
||||||
import styles from "./VideoGrid.module.css";
|
import styles from "./VideoGrid.module.css";
|
||||||
import { Layout } from "../room/GridLayoutMenu";
|
import { Layout } from "../room/GridLayoutMenu";
|
||||||
import { Participant } from "../room/InCallView";
|
import { TileDescriptor } from "../room/InCallView";
|
||||||
|
|
||||||
interface TilePosition {
|
interface TilePosition {
|
||||||
x: number;
|
x: number;
|
||||||
|
@ -36,7 +36,7 @@ interface TilePosition {
|
||||||
interface Tile {
|
interface Tile {
|
||||||
key: Key;
|
key: Key;
|
||||||
order: number;
|
order: number;
|
||||||
item: Participant;
|
item: TileDescriptor;
|
||||||
remove: boolean;
|
remove: boolean;
|
||||||
focused: boolean;
|
focused: boolean;
|
||||||
presenter: boolean;
|
presenter: boolean;
|
||||||
|
@ -693,12 +693,12 @@ interface ChildrenProperties extends ReactDOMAttributes {
|
||||||
};
|
};
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
item: Participant;
|
item: TileDescriptor;
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VideoGridProps {
|
interface VideoGridProps {
|
||||||
items: Participant[];
|
items: TileDescriptor[];
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
disableAnimations?: boolean;
|
disableAnimations?: boolean;
|
||||||
children: (props: ChildrenProperties) => React.ReactNode;
|
children: (props: ChildrenProperties) => React.ReactNode;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { AudioButton, FullscreenButton } from "../button/Button";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string;
|
name: string;
|
||||||
|
hasFeed: Boolean;
|
||||||
speaking?: boolean;
|
speaking?: boolean;
|
||||||
audioMuted?: boolean;
|
audioMuted?: boolean;
|
||||||
videoMuted?: boolean;
|
videoMuted?: boolean;
|
||||||
|
@ -47,6 +48,7 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
|
hasFeed,
|
||||||
speaking,
|
speaking,
|
||||||
audioMuted,
|
audioMuted,
|
||||||
videoMuted,
|
videoMuted,
|
||||||
|
@ -90,6 +92,8 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const caption = hasFeed ? name : t("{{name}} (Connecting...)", { name });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<animated.div
|
<animated.div
|
||||||
className={classNames(styles.videoTile, className, {
|
className={classNames(styles.videoTile, className, {
|
||||||
|
@ -120,7 +124,7 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
||||||
<div className={classNames(styles.infoBubble, styles.memberName)}>
|
<div className={classNames(styles.infoBubble, styles.memberName)}>
|
||||||
{audioMuted && !videoMuted && <MicMutedIcon />}
|
{audioMuted && !videoMuted && <MicMutedIcon />}
|
||||||
{videoMuted && <VideoMutedIcon />}
|
{videoMuted && <VideoMutedIcon />}
|
||||||
<span title={name}>{name}</span>
|
<span title={caption}>{caption}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<video ref={mediaRef} playsInline disablePictureInPicture />
|
<video ref={mediaRef} playsInline disablePictureInPicture />
|
||||||
|
|
|
@ -25,10 +25,10 @@ import { useRoomMemberName } from "./useRoomMemberName";
|
||||||
import { VideoTile } from "./VideoTile";
|
import { VideoTile } from "./VideoTile";
|
||||||
import { VideoTileSettingsModal } from "./VideoTileSettingsModal";
|
import { VideoTileSettingsModal } from "./VideoTileSettingsModal";
|
||||||
import { useModalTriggerState } from "../Modal";
|
import { useModalTriggerState } from "../Modal";
|
||||||
import { Participant } from "../room/InCallView";
|
import { TileDescriptor } from "../room/InCallView";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: Participant;
|
item: TileDescriptor;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
getAvatar: (
|
getAvatar: (
|
||||||
|
@ -41,7 +41,7 @@ interface Props {
|
||||||
disableSpeakingIndicator: boolean;
|
disableSpeakingIndicator: boolean;
|
||||||
maximised: boolean;
|
maximised: boolean;
|
||||||
fullscreen: boolean;
|
fullscreen: boolean;
|
||||||
onFullscreen: (item: Participant) => void;
|
onFullscreen: (item: TileDescriptor) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoTileContainer({
|
export function VideoTileContainer({
|
||||||
|
@ -65,9 +65,8 @@ export function VideoTileContainer({
|
||||||
speaking,
|
speaking,
|
||||||
stream,
|
stream,
|
||||||
purpose,
|
purpose,
|
||||||
member,
|
|
||||||
} = useCallFeed(item.callFeed);
|
} = useCallFeed(item.callFeed);
|
||||||
const { rawDisplayName } = useRoomMemberName(member);
|
const { rawDisplayName } = useRoomMemberName(item.member);
|
||||||
const [tileRef, mediaRef] = useSpatialMediaStream(
|
const [tileRef, mediaRef] = useSpatialMediaStream(
|
||||||
stream,
|
stream,
|
||||||
audioContext,
|
audioContext,
|
||||||
|
@ -99,9 +98,10 @@ export function VideoTileContainer({
|
||||||
videoMuted={videoMuted}
|
videoMuted={videoMuted}
|
||||||
screenshare={purpose === SDPStreamMetadataPurpose.Screenshare}
|
screenshare={purpose === SDPStreamMetadataPurpose.Screenshare}
|
||||||
name={rawDisplayName}
|
name={rawDisplayName}
|
||||||
|
hasFeed={Boolean(item.callFeed)}
|
||||||
ref={tileRef}
|
ref={tileRef}
|
||||||
mediaRef={mediaRef}
|
mediaRef={mediaRef}
|
||||||
avatar={getAvatar && getAvatar(member, width, height)}
|
avatar={getAvatar && getAvatar(item.member, width, height)}
|
||||||
onOptionsPress={onOptionsPress}
|
onOptionsPress={onOptionsPress}
|
||||||
localVolume={localVolume}
|
localVolume={localVolume}
|
||||||
maximised={maximised}
|
maximised={maximised}
|
||||||
|
|
|
@ -16,11 +16,10 @@ limitations under the License.
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed";
|
import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
|
||||||
import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
|
import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
|
||||||
|
|
||||||
interface CallFeedState {
|
interface CallFeedState {
|
||||||
member: RoomMember;
|
callFeed: CallFeed;
|
||||||
isLocal: boolean;
|
isLocal: boolean;
|
||||||
speaking: boolean;
|
speaking: boolean;
|
||||||
videoMuted: boolean;
|
videoMuted: boolean;
|
||||||
|
@ -32,7 +31,7 @@ interface CallFeedState {
|
||||||
}
|
}
|
||||||
function getCallFeedState(callFeed: CallFeed): CallFeedState {
|
function getCallFeedState(callFeed: CallFeed): CallFeedState {
|
||||||
return {
|
return {
|
||||||
member: callFeed ? callFeed.getMember() : null,
|
callFeed,
|
||||||
isLocal: callFeed ? callFeed.isLocal() : false,
|
isLocal: callFeed ? callFeed.isLocal() : false,
|
||||||
speaking: callFeed ? callFeed.isSpeaking() : false,
|
speaking: callFeed ? callFeed.isSpeaking() : false,
|
||||||
videoMuted: callFeed ? callFeed.isVideoMuted() : true,
|
videoMuted: callFeed ? callFeed.isVideoMuted() : true,
|
||||||
|
|
|
@ -17,27 +17,27 @@ limitations under the License.
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Participant } from "../room/InCallView";
|
import { TileDescriptor } from "../room/InCallView";
|
||||||
import { useEventTarget } from "../useEvents";
|
import { useEventTarget } from "../useEvents";
|
||||||
import { useCallFeed } from "./useCallFeed";
|
import { useCallFeed } from "./useCallFeed";
|
||||||
|
|
||||||
export function useFullscreen(ref: React.RefObject<HTMLElement>): {
|
export function useFullscreen(ref: React.RefObject<HTMLElement>): {
|
||||||
toggleFullscreen: (participant: Participant) => void;
|
toggleFullscreen: (participant: TileDescriptor) => void;
|
||||||
fullscreenParticipant: Participant | null;
|
fullscreenParticipant: TileDescriptor | null;
|
||||||
} {
|
} {
|
||||||
const [fullscreenParticipant, setFullscreenParticipant] =
|
const [fullscreenParticipant, setFullscreenParticipant] =
|
||||||
useState<Participant | null>(null);
|
useState<TileDescriptor | null>(null);
|
||||||
const { disposed } = useCallFeed(fullscreenParticipant?.callFeed);
|
const { disposed } = useCallFeed(fullscreenParticipant?.callFeed);
|
||||||
|
|
||||||
const toggleFullscreen = useCallback(
|
const toggleFullscreen = useCallback(
|
||||||
(participant: Participant) => {
|
(tileDes: TileDescriptor) => {
|
||||||
if (fullscreenParticipant) {
|
if (fullscreenParticipant) {
|
||||||
document.exitFullscreen();
|
document.exitFullscreen();
|
||||||
setFullscreenParticipant(null);
|
setFullscreenParticipant(null);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
ref.current.requestFullscreen();
|
ref.current.requestFullscreen();
|
||||||
setFullscreenParticipant(participant);
|
setFullscreenParticipant(tileDes);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Failed to fullscreen:", error);
|
console.warn("Failed to fullscreen:", error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,23 +33,33 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMediaStreamTrackCount = (
|
export const useMediaStreamTrackCount = (
|
||||||
stream: MediaStream
|
stream: MediaStream | null
|
||||||
): [number, number] => {
|
): [number, number] => {
|
||||||
|
const latestAudioTrackCount = stream ? stream.getAudioTracks().length : 0;
|
||||||
|
const latestVideoTrackCount = stream ? stream.getVideoTracks().length : 0;
|
||||||
|
|
||||||
const [audioTrackCount, setAudioTrackCount] = useState(
|
const [audioTrackCount, setAudioTrackCount] = useState(
|
||||||
stream.getAudioTracks().length
|
stream ? stream.getAudioTracks().length : 0
|
||||||
);
|
);
|
||||||
const [videoTrackCount, setVideoTrackCount] = useState(
|
const [videoTrackCount, setVideoTrackCount] = useState(
|
||||||
stream.getVideoTracks().length
|
stream ? stream.getVideoTracks().length : 0
|
||||||
);
|
);
|
||||||
|
|
||||||
const tracksChanged = useCallback(() => {
|
const tracksChanged = useCallback(() => {
|
||||||
setAudioTrackCount(stream.getAudioTracks().length);
|
setAudioTrackCount(stream ? stream.getAudioTracks().length : 0);
|
||||||
setVideoTrackCount(stream.getVideoTracks().length);
|
setVideoTrackCount(stream ? stream.getVideoTracks().length : 0);
|
||||||
}, [stream]);
|
}, [stream]);
|
||||||
|
|
||||||
useEventTarget(stream, "addtrack", tracksChanged);
|
useEventTarget(stream, "addtrack", tracksChanged);
|
||||||
useEventTarget(stream, "removetrack", tracksChanged);
|
useEventTarget(stream, "removetrack", tracksChanged);
|
||||||
|
|
||||||
|
if (
|
||||||
|
latestAudioTrackCount !== audioTrackCount ||
|
||||||
|
latestVideoTrackCount !== videoTrackCount
|
||||||
|
) {
|
||||||
|
tracksChanged();
|
||||||
|
}
|
||||||
|
|
||||||
return [audioTrackCount, videoTrackCount];
|
return [audioTrackCount, videoTrackCount];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue