Skip to content

Commit

Permalink
fix: audio and video muting
Browse files Browse the repository at this point in the history
  • Loading branch information
varshith15 committed Jan 30, 2025
1 parent 5897596 commit 06faf49
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 83 deletions.
20 changes: 11 additions & 9 deletions server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,17 @@ async def offer(request):

tracks = {"video": None, "audio": None}

# Prefer h264
transceiver = pc.addTransceiver("video")
caps = RTCRtpSender.getCapabilities("video")
prefs = list(filter(lambda x: x.name == "H264", caps.codecs))
transceiver.setCodecPreferences(prefs)

# Monkey patch max and min bitrate to ensure constant bitrate
h264.MAX_BITRATE = MAX_BITRATE
h264.MIN_BITRATE = MIN_BITRATE
# Only add video transceiver if video is present in the offer
if "m=video" in offer.sdp:
# Prefer h264
transceiver = pc.addTransceiver("video")
caps = RTCRtpSender.getCapabilities("video")
prefs = list(filter(lambda x: x.name == "H264", caps.codecs))
transceiver.setCodecPreferences(prefs)

# Monkey patch max and min bitrate to ensure constant bitrate
h264.MAX_BITRATE = MAX_BITRATE
h264.MIN_BITRATE = MIN_BITRATE

# Handle control channel from client
@pc.on("datachannel")
Expand Down
68 changes: 58 additions & 10 deletions ui/src/components/room.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,69 @@ interface MediaStreamPlayerProps {

function MediaStreamPlayer({ stream }: MediaStreamPlayerProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const [needsPlayButton, setNeedsPlayButton] = useState(false);

useEffect(() => {
if (videoRef.current && stream) {
videoRef.current.srcObject = stream;
}
if (!videoRef.current || !stream) return;

const video = videoRef.current;
video.srcObject = stream;
setNeedsPlayButton(false);

// Handle autoplay
const playStream = async () => {
try {
// Only attempt to play if the video element exists and has a valid srcObject
if (video && video.srcObject) {
await video.play();
setNeedsPlayButton(false);
}
} catch (error) {
// Log error but don't throw - this is likely due to browser autoplay policy
console.warn("Autoplay prevented:", error);
setNeedsPlayButton(true);
}
};
playStream();

return () => {
if (video) {
video.srcObject = null;
video.pause();
}
};
}, [stream]);

const handlePlayClick = async () => {
try {
if (videoRef.current) {
await videoRef.current.play();
setNeedsPlayButton(false);
}
} catch (error) {
console.warn("Manual play failed:", error);
}
};

return (
<video
ref={videoRef}
autoPlay
playsInline
// muted
className="w-full h-full"
/>
<div className="relative w-full h-full">
<video
ref={videoRef}
autoPlay
playsInline
className="w-full h-full"
/>
{needsPlayButton && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
<button
onClick={handlePlayClick}
className="px-4 py-2 bg-white text-black rounded-md hover:bg-gray-200 transition-colors"
>
Click to Play
</button>
</div>
)}
</div>
);
}

Expand Down
58 changes: 36 additions & 22 deletions ui/src/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,40 +149,56 @@ function ConfigForm({ config, onSubmit }: ConfigFormProps) {
await navigator.mediaDevices.getUserMedia({ video: true });

const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices
.filter((device) => device.kind === "videoinput")
.map((device) => ({
deviceId: device.deviceId,
label: device.label || `Camera ${device.deviceId.slice(0, 5)}...`,
}));
const videoDevices = [
{ deviceId: "none", label: "No Video" },
...devices
.filter((device) => device.kind === "videoinput")
.map((device) => ({
deviceId: device.deviceId,
label: device.label || `Camera ${device.deviceId.slice(0, 5)}...`,
}))
];

setVideoDevices(videoDevices);
if (videoDevices.length > 0) {
setSelectedDevice((curr) => curr || videoDevices[0].deviceId);
// Set default to first available camera if no selection yet
if (!selectedDevice && videoDevices.length > 1) {
setSelectedDevice(videoDevices[1].deviceId); // Index 1 because 0 is "No Video"
}
} catch (err) {
console.error("Failed to get video devices");
// If we can't access video devices, still provide the None option
const videoDevices = [{ deviceId: "none", label: "No Video" }];
setVideoDevices(videoDevices);
setSelectedDevice("none");
}
}, []);
}, [selectedDevice]);

const getAudioDevices = useCallback(async () => {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevices = devices
.filter((device) => device.kind === "audioinput")
.map((device) => ({
deviceId: device.deviceId,
label: device.label || `Microphone ${device.deviceId.slice(0, 5)}...`,
}));
const audioDevices = [
{ deviceId: "none", label: "No Audio" },
...devices
.filter((device) => device.kind === "audioinput")
.map((device) => ({
deviceId: device.deviceId,
label: device.label || `Microphone ${device.deviceId.slice(0, 5)}...`,
}))
];

setAudioDevices(audioDevices);
if (audioDevices.length > 0) {
setSelectedAudioDevice((curr) => curr || audioDevices[0].deviceId);
// Set default to first available microphone if no selection yet
if (!selectedAudioDevice && audioDevices.length > 1) {
setSelectedAudioDevice(audioDevices[1].deviceId); // Index 1 because 0 is "No Audio"
}
} catch (err) {
console.error("Failed to get audio devices");
// If we can't access audio devices, still provide the None option
const audioDevices = [{ deviceId: "none", label: "No Audio" }];
setAudioDevices(audioDevices);
setSelectedAudioDevice("none");
}
}, []);
}, [selectedAudioDevice]);

useEffect(() => {
getVideoDevices();
Expand Down Expand Up @@ -267,8 +283,7 @@ function ConfigForm({ config, onSubmit }: ConfigFormProps) {
<Label>Camera</Label>
<Select value={selectedDevice} onValueChange={setSelectedDevice}>
<Select.Trigger className="w-full mt-2">
{videoDevices.find((d) => d.deviceId === selectedDevice)?.label ||
"Select camera"}
{selectedDevice ? (videoDevices.find((d) => d.deviceId === selectedDevice)?.label || "None") : "None"}
</Select.Trigger>
<Select.Content>
{videoDevices.map((device) => (
Expand All @@ -284,8 +299,7 @@ function ConfigForm({ config, onSubmit }: ConfigFormProps) {
<Label>Microphone</Label>
<Select value={selectedAudioDevice} onValueChange={setSelectedAudioDevice}>
<Select.Trigger className="w-full mt-2">
{audioDevices.find((d) => d.deviceId === selectedAudioDevice)?.label ||
"Select microphone"}
{selectedAudioDevice ? (audioDevices.find((d) => d.deviceId === selectedAudioDevice)?.label || "None") : "None"}
</Select.Trigger>
<Select.Content>
{audioDevices.map((device) => (
Expand Down
Loading

0 comments on commit 06faf49

Please sign in to comment.