feat(webrtc): render remote audio tracks and allow camera access
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
{
|
||||
auto_https off
|
||||
https_port 8443
|
||||
}
|
||||
|
||||
:80 {
|
||||
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
FROM golang:1.24-alpine AS go-builder
|
||||
FROM golang:1.26.4-alpine AS go-builder
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY cmd/ cmd/
|
||||
COPY internal/ internal/
|
||||
COPY docs/ docs/
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/dumpster-server ./cmd/server
|
||||
|
||||
FROM node:22-alpine AS web-builder
|
||||
|
||||
+6
-2
@@ -94,25 +94,29 @@ services:
|
||||
image: coturn/coturn:latest
|
||||
container_name: dumpster-coturn
|
||||
command:
|
||||
- "-n"
|
||||
- "--log-file=stdout"
|
||||
- "--min-port=50101"
|
||||
- "--max-port=50200"
|
||||
- "--realm=${DUMPSTER_HOST:-localhost}"
|
||||
- "--user=${LIVEKIT_API_KEY:-devkey}:${LIVEKIT_API_SECRET:-secret}"
|
||||
- "--cert=/etc/coturn/certs/turn.crt"
|
||||
- "--pkey=/etc/coturn/certs/turn.key"
|
||||
- "--tls-listening-port=5349"
|
||||
ports:
|
||||
- "3478:3478/tcp"
|
||||
- "3478:3478/udp"
|
||||
- "5349:5349/tcp"
|
||||
- "5349:5349/udp"
|
||||
- "50101-50200:50101-50200/udp"
|
||||
volumes:
|
||||
- ./certs:/etc/coturn/certs:ro
|
||||
|
||||
caddy:
|
||||
image: caddy:2
|
||||
container_name: dumpster-caddy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8443:8443"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- web_assets:/srv/web:ro
|
||||
|
||||
@@ -13,7 +13,7 @@ func SecurityHeaders(next http.Handler) http.Handler {
|
||||
w.Header().Set("Content-Security-Policy",
|
||||
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "+
|
||||
"img-src 'self' https: data:; connect-src 'self' wss: ws:; font-src 'self';")
|
||||
w.Header().Set("Permissions-Policy", "camera=(), microphone=(self), geolocation=()")
|
||||
w.Header().Set("Permissions-Policy", "camera=(self), microphone=(self), geolocation=()")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
Generated
-39
@@ -1003,9 +1003,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1020,9 +1017,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1037,9 +1031,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1054,9 +1045,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1071,9 +1059,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1088,9 +1073,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1105,9 +1087,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1122,9 +1101,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1139,9 +1115,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1156,9 +1129,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1173,9 +1143,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1190,9 +1157,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1207,9 +1171,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/BotManager.tsx","./src/components/CalendarView.tsx","./src/components/ChannelList.tsx","./src/components/ChannelSettingsModal.tsx","./src/components/ChatArea.tsx","./src/components/CommandDropdown.tsx","./src/components/CommandManager.tsx","./src/components/ContextMenu.tsx","./src/components/ConversationList.tsx","./src/components/CreateChannelModal.tsx","./src/components/CreateServerModal.tsx","./src/components/DMChat.tsx","./src/components/DocsView.tsx","./src/components/EmojiPicker.tsx","./src/components/FeatureRequestsPanel.tsx","./src/components/ForgotPasswordPage.tsx","./src/components/ForumView.tsx","./src/components/GiphyPicker.tsx","./src/components/InstallPrompt.tsx","./src/components/InviteModal.tsx","./src/components/JoinServer.tsx","./src/components/JoinServerModal.tsx","./src/components/Layout.tsx","./src/components/ListView.tsx","./src/components/LoginForm.tsx","./src/components/MemberContextMenu.tsx","./src/components/MemberList.tsx","./src/components/MemberRoleAssign.tsx","./src/components/MentionDropdown.tsx","./src/components/MentionPopup.tsx","./src/components/MessageSearch.tsx","./src/components/MobileDrawer.tsx","./src/components/MobileNav.tsx","./src/components/NewConversationModal.tsx","./src/components/PinnedMessages.tsx","./src/components/Poll.tsx","./src/components/ReactionBar.tsx","./src/components/ReplyBar.tsx","./src/components/ResetPasswordPage.tsx","./src/components/RoleManager.tsx","./src/components/ServerBar.tsx","./src/components/ServerSettingsModal.tsx","./src/components/SlashCommandPopup.tsx","./src/components/ThemeToggle.tsx","./src/components/ThreadListPanel.tsx","./src/components/ThreadPanel.tsx","./src/components/TypingIndicator.tsx","./src/components/UserProfileModal.tsx","./src/components/UserSettings.tsx","./src/components/VideoGrid.tsx","./src/components/VoiceChannel.tsx","./src/components/VoiceControls.tsx","./src/components/VoicePanel.tsx","./src/lib/api.ts","./src/lib/kaomojiData.ts","./src/lib/slashCommands.ts","./src/lib/usePermissions.ts","./src/stores/auth.ts","./src/stores/bot.ts","./src/stores/channel.ts","./src/stores/conversation.ts","./src/stores/featureRequest.ts","./src/stores/layout.ts","./src/stores/member.ts","./src/stores/message.ts","./src/stores/moderation.ts","./src/stores/notificationSettings.ts","./src/stores/permissions.ts","./src/stores/presence.ts","./src/stores/push.ts","./src/stores/readStates.ts","./src/stores/role.ts","./src/stores/server.ts","./src/stores/thread.ts","./src/stores/typing.ts","./src/stores/voice.ts","./src/stores/ws.ts"],"version":"5.9.3"}
|
||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/AudioRenderers.tsx","./src/components/BotManager.tsx","./src/components/CalendarView.tsx","./src/components/ChannelList.tsx","./src/components/ChannelSettingsModal.tsx","./src/components/ChatArea.tsx","./src/components/CommandDropdown.tsx","./src/components/CommandManager.tsx","./src/components/ConnectionStatus.tsx","./src/components/ContextMenu.tsx","./src/components/ConversationList.tsx","./src/components/CreateChannelModal.tsx","./src/components/CreateServerModal.tsx","./src/components/DMChat.tsx","./src/components/DocsView.tsx","./src/components/EmojiPicker.tsx","./src/components/FeatureRequestsPanel.tsx","./src/components/ForgotPasswordPage.tsx","./src/components/FormatToolbar.tsx","./src/components/ForumView.tsx","./src/components/GiphyPicker.tsx","./src/components/InstallBanner.tsx","./src/components/InstallPrompt.tsx","./src/components/InviteModal.tsx","./src/components/JoinServer.tsx","./src/components/JoinServerModal.tsx","./src/components/Layout.tsx","./src/components/ListView.tsx","./src/components/LoginForm.tsx","./src/components/MemberContextMenu.tsx","./src/components/MemberList.tsx","./src/components/MemberRoleAssign.tsx","./src/components/MentionDropdown.tsx","./src/components/MentionPopup.tsx","./src/components/MessageSearch.tsx","./src/components/MobileDrawer.tsx","./src/components/MobileNav.tsx","./src/components/NewConversationModal.tsx","./src/components/NotificationPrompt.tsx","./src/components/PinnedMessages.tsx","./src/components/Poll.tsx","./src/components/ReactionBar.tsx","./src/components/ReplyBar.tsx","./src/components/ResetPasswordPage.tsx","./src/components/RoleManager.tsx","./src/components/ServerBar.tsx","./src/components/ServerSettingsModal.tsx","./src/components/SlashCommandPopup.tsx","./src/components/ThemeToggle.tsx","./src/components/ThreadListPanel.tsx","./src/components/ThreadPanel.tsx","./src/components/TypingIndicator.tsx","./src/components/UserProfileModal.tsx","./src/components/UserSettings.tsx","./src/components/VideoGrid.tsx","./src/components/VoiceChannel.tsx","./src/components/VoiceControls.tsx","./src/components/VoicePanel.tsx","./src/lib/api.ts","./src/lib/kaomojiData.ts","./src/lib/slashCommands.ts","./src/lib/usePermissions.ts","./src/stores/auth.ts","./src/stores/bot.ts","./src/stores/channel.ts","./src/stores/conversation.ts","./src/stores/featureRequest.ts","./src/stores/layout.ts","./src/stores/member.ts","./src/stores/message.ts","./src/stores/moderation.ts","./src/stores/notificationSettings.ts","./src/stores/permissions.ts","./src/stores/presence.ts","./src/stores/push.ts","./src/stores/readStates.ts","./src/stores/role.ts","./src/stores/server.ts","./src/stores/thread.ts","./src/stores/typing.ts","./src/stores/voice.ts","./src/stores/ws.ts"],"version":"5.9.3"}
|
||||
@@ -1 +1 @@
|
||||
{"root":["./vite.config.ts"],"errors":true,"version":"5.9.3"}
|
||||
{"root":["./vite.config.ts"],"version":"5.9.3"}
|
||||
Reference in New Issue
Block a user