feat(webrtc): render remote audio tracks and allow camera access
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useVoiceStore } from '../stores/voice.ts';
|
||||
import { Track } from 'livekit-client';
|
||||
import type { Participant, TrackPublication, Room } from 'livekit-client';
|
||||
|
||||
function RemoteAudioTrack({ participant, room }: { participant: Participant; room: Room }) {
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = audioRef.current;
|
||||
if (!el) return;
|
||||
|
||||
let pub: TrackPublication | undefined;
|
||||
|
||||
const attachTrack = () => {
|
||||
pub = participant.getTrackPublication(Track.Source.Microphone);
|
||||
if (pub?.track && el) {
|
||||
pub.track.attach(el);
|
||||
el.play().catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
const detachTrack = () => {
|
||||
if (pub?.track && el) {
|
||||
pub.track.detach(el);
|
||||
}
|
||||
};
|
||||
|
||||
attachTrack();
|
||||
|
||||
const handlePublished = (publication: TrackPublication) => {
|
||||
if (publication.source === Track.Source.Microphone && publication.track) {
|
||||
publication.track.attach(el);
|
||||
el.play().catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
room.on('trackPublished' as any, handlePublished);
|
||||
|
||||
// If a track is subscribed later
|
||||
const handleSubscribed = (track: Track, publication: TrackPublication, trackParticipant: Participant) => {
|
||||
if (trackParticipant.identity === participant.identity && publication.source === Track.Source.Microphone) {
|
||||
track.attach(el);
|
||||
el.play().catch(() => {});
|
||||
}
|
||||
};
|
||||
room.on('trackSubscribed' as any, handleSubscribed);
|
||||
|
||||
return () => {
|
||||
detachTrack();
|
||||
room.off('trackPublished' as any, handlePublished);
|
||||
room.off('trackSubscribed' as any, handleSubscribed);
|
||||
};
|
||||
}, [participant, room]);
|
||||
|
||||
return <audio ref={audioRef} autoPlay />;
|
||||
}
|
||||
|
||||
export function AudioRenderers() {
|
||||
const room = useVoiceStore((s) => s._room);
|
||||
|
||||
if (!room) return null;
|
||||
|
||||
const remoteParticipants = Array.from(room.remoteParticipants.values());
|
||||
|
||||
return (
|
||||
<div className="hidden">
|
||||
{remoteParticipants.map((p) => (
|
||||
<RemoteAudioTrack key={p.identity} participant={p} room={room} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useVoiceStore } from '../stores/voice.ts';
|
||||
import { useAuthStore } from '../stores/auth.ts';
|
||||
import { VoiceControls } from './VoiceControls.tsx';
|
||||
import { VideoGrid } from './VideoGrid.tsx';
|
||||
import { AudioRenderers } from './AudioRenderers.tsx';
|
||||
|
||||
export function VoicePanel() {
|
||||
const isConnected = useVoiceStore((state) => state.isConnected);
|
||||
@@ -122,6 +123,7 @@ export function VoicePanel() {
|
||||
|
||||
{/* Video grid */}
|
||||
<VideoGrid />
|
||||
<AudioRenderers />
|
||||
|
||||
{/* Controls */}
|
||||
<div className="px-3 py-1.5">
|
||||
|
||||
Reference in New Issue
Block a user