Skip to content

Commit a092e29

Browse files
Controls (#35)
1 parent 18f64ac commit a092e29

File tree

4 files changed

+119
-30
lines changed

4 files changed

+119
-30
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Camera, CameraOff, Mic, MicOff } from 'lucide-react';
2+
import type { FC } from 'react';
3+
import { Button } from './ui/button';
4+
5+
export type LocalPeerOverlayProps = {
6+
isMuted: boolean;
7+
toggleMute: () => void;
8+
isCameraOn: boolean;
9+
toggleCamera: () => void;
10+
};
11+
12+
export const LocalPeerOverlay: FC<LocalPeerOverlayProps> = ({
13+
isMuted,
14+
toggleMute,
15+
isCameraOn,
16+
toggleCamera,
17+
}) => {
18+
const MicIcon = isMuted ? MicOff : Mic;
19+
const micColor = isMuted ? 'text-red-600' : '';
20+
21+
const CameraIcon = !isCameraOn ? CameraOff : Camera;
22+
const cameraColor = !isCameraOn ? 'text-red-600' : '';
23+
24+
return (
25+
<div className="absolute inset-x-0 bottom-0 p-2 gap-2 flex justify-center">
26+
<Button variant="outline" className={micColor} onClick={toggleMute}>
27+
<MicIcon size={24} />
28+
</Button>
29+
<Button variant="outline" className={cameraColor} onClick={toggleCamera}>
30+
<CameraIcon size={24} />
31+
</Button>
32+
</div>
33+
);
34+
};

deep-sea-stories/packages/web/src/components/PeerGrid.tsx

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
import type { usePeers } from '@fishjam-cloud/react-client';
1+
import {
2+
useCamera,
3+
useMicrophone,
4+
type usePeers,
5+
} from '@fishjam-cloud/react-client';
26
import type { FC } from 'react';
3-
import { PeerTile } from './PeerTile';
47
import { cn } from '@/lib/utils';
8+
import { LocalPeerOverlay } from './LocalPeerOverlay';
9+
import { PeerTile } from './PeerTile';
10+
import { RemotePeerOverlay } from './RemotePeerOverlay';
511

612
type PeerGridProps = {
713
localPeer: ReturnType<typeof usePeers<{ name: string }>>['localPeer'];
814
displayedPeers: ReturnType<typeof usePeers<{ name: string }>>['remotePeers'];
915
};
1016

1117
const PeerGrid: FC<PeerGridProps> = ({ localPeer, displayedPeers }) => {
18+
const { isMicrophoneMuted, toggleMicrophoneMute } = useMicrophone();
19+
const { isCameraOn, toggleCamera } = useCamera();
20+
1221
return (
1322
<section
1423
className={cn(
@@ -21,14 +30,36 @@ const PeerGrid: FC<PeerGridProps> = ({ localPeer, displayedPeers }) => {
2130
},
2231
)}
2332
>
24-
<PeerTile name="You" stream={localPeer?.cameraTrack?.stream} />
33+
<PeerTile
34+
name="You"
35+
className="relative"
36+
stream={localPeer?.cameraTrack?.stream}
37+
videoPaused={localPeer?.cameraTrack?.metadata?.paused}
38+
>
39+
<LocalPeerOverlay
40+
isMuted={isMicrophoneMuted}
41+
toggleMute={toggleMicrophoneMute}
42+
isCameraOn={isCameraOn}
43+
toggleCamera={toggleCamera}
44+
/>
45+
</PeerTile>
46+
2547
{displayedPeers.map((peer) => (
2648
<PeerTile
49+
className="relative"
2750
name={peer.metadata?.peer?.name ?? peer.id}
2851
key={peer.id}
29-
stream={peer.customVideoTracks[0]?.stream ?? peer.cameraTrack?.stream}
52+
stream={peer.cameraTrack?.stream}
3053
audioStream={peer.microphoneTrack?.stream}
31-
/>
54+
videoPaused={peer?.cameraTrack?.metadata?.paused}
55+
>
56+
<RemotePeerOverlay
57+
isMuted={
58+
!peer.microphoneTrack?.stream ||
59+
!!peer.microphoneTrack.metadata?.paused
60+
}
61+
/>
62+
</PeerTile>
3263
))}
3364
</section>
3465
);
Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,67 @@
1-
import { type FC, type HTMLAttributes, useEffect, useRef } from 'react';
1+
import {
2+
type FC,
3+
type HTMLAttributes,
4+
type PropsWithChildren,
5+
useEffect,
6+
useRef,
7+
} from 'react';
28
import { cn } from '@/lib/utils';
39

410
export type PeerTileProps = {
511
stream?: MediaStream | null;
612
audioStream?: MediaStream | null;
713
name: string;
14+
videoPaused?: boolean | null;
815
} & HTMLAttributes<HTMLDivElement>;
916

10-
export const PeerTile: FC<PeerTileProps> = ({
17+
export const PeerTile: FC<PropsWithChildren<PeerTileProps>> = ({
1118
stream,
1219
audioStream,
1320
name,
1421
className,
22+
videoPaused,
23+
children,
1524
...props
1625
}) => {
1726
const videoRef = useRef<HTMLVideoElement>(null);
1827
const audioRef = useRef<HTMLAudioElement>(null);
1928

2029
useEffect(() => {
21-
if (!videoRef.current) return;
30+
if (!videoRef.current || videoPaused) return;
2231
videoRef.current.srcObject = stream ?? null;
23-
}, [stream]);
32+
}, [stream, videoPaused]);
2433

2534
useEffect(() => {
2635
if (!audioRef.current) return;
2736
audioRef.current.srcObject = audioStream ?? null;
2837
}, [audioStream]);
2938

3039
return (
31-
<div
32-
className={cn(
33-
'h-full w-full flex border items-center max-w-xl justify-center rounded-xl overflow-hidden',
34-
className,
35-
)}
36-
{...props}
37-
>
38-
{stream ? (
39-
<video
40-
className="h-full rounded-xl max-w-full object-cover"
40+
<div className={cn('h-full w-full max-w-xl', className)} {...props}>
41+
<div className="h-full w-full flex border items-center justify-center rounded-xl overflow-hidden">
42+
{stream && !videoPaused ? (
43+
<video
44+
className="h-full rounded-xl max-w-full object-cover"
45+
autoPlay
46+
muted
47+
disablePictureInPicture
48+
playsInline
49+
ref={videoRef}
50+
/>
51+
) : (
52+
<div className="text-sm md:text-xl font-display text-center p-2">
53+
{name}
54+
</div>
55+
)}
56+
{/* biome-ignore lint/a11y/useMediaCaption: Peer audio feed from WebRTC doesn't have captions */}
57+
<audio
58+
ref={audioRef}
4159
autoPlay
42-
muted
43-
disablePictureInPicture
4460
playsInline
45-
ref={videoRef}
46-
></video>
47-
) : (
48-
<div className="text-sm md:text-xl font-display text-center p-2">
49-
{name}
50-
</div>
51-
)}
52-
{/* biome-ignore lint/a11y/useMediaCaption: Peer audio feed from WebRTC doesn't have captions */}
53-
<audio ref={audioRef} autoPlay playsInline title={`Audio from ${name}`} />
61+
title={`Audio from ${name}`}
62+
/>
63+
</div>
64+
{children}
5465
</div>
5566
);
5667
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { VolumeOff } from 'lucide-react';
2+
import type { FC } from 'react';
3+
4+
export type RemotePeerOverlayProps = {
5+
isMuted: boolean;
6+
};
7+
8+
export const RemotePeerOverlay: FC<RemotePeerOverlayProps> = ({ isMuted }) =>
9+
isMuted && (
10+
<div className="absolute border-2 border-foreground rounded-full bg-background m-2 p-2 top-0 right-0 flex justify-center">
11+
<VolumeOff size={24} />
12+
</div>
13+
);

0 commit comments

Comments
 (0)