feat(webrtc): render remote audio tracks and allow camera access
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
auto_https off
|
auto_https off
|
||||||
|
https_port 8443
|
||||||
}
|
}
|
||||||
|
|
||||||
:80 {
|
: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
|
WORKDIR /app
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
COPY cmd/ cmd/
|
COPY cmd/ cmd/
|
||||||
COPY internal/ internal/
|
COPY internal/ internal/
|
||||||
|
COPY docs/ docs/
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/dumpster-server ./cmd/server
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/dumpster-server ./cmd/server
|
||||||
|
|
||||||
FROM node:22-alpine AS web-builder
|
FROM node:22-alpine AS web-builder
|
||||||
|
|||||||
+6
-2
@@ -94,25 +94,29 @@ services:
|
|||||||
image: coturn/coturn:latest
|
image: coturn/coturn:latest
|
||||||
container_name: dumpster-coturn
|
container_name: dumpster-coturn
|
||||||
command:
|
command:
|
||||||
- "-n"
|
|
||||||
- "--log-file=stdout"
|
- "--log-file=stdout"
|
||||||
- "--min-port=50101"
|
- "--min-port=50101"
|
||||||
- "--max-port=50200"
|
- "--max-port=50200"
|
||||||
- "--realm=${DUMPSTER_HOST:-localhost}"
|
- "--realm=${DUMPSTER_HOST:-localhost}"
|
||||||
- "--user=${LIVEKIT_API_KEY:-devkey}:${LIVEKIT_API_SECRET:-secret}"
|
- "--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:
|
ports:
|
||||||
- "3478:3478/tcp"
|
- "3478:3478/tcp"
|
||||||
- "3478:3478/udp"
|
- "3478:3478/udp"
|
||||||
- "5349:5349/tcp"
|
- "5349:5349/tcp"
|
||||||
- "5349:5349/udp"
|
- "5349:5349/udp"
|
||||||
- "50101-50200:50101-50200/udp"
|
- "50101-50200:50101-50200/udp"
|
||||||
|
volumes:
|
||||||
|
- ./certs:/etc/coturn/certs:ro
|
||||||
|
|
||||||
caddy:
|
caddy:
|
||||||
image: caddy:2
|
image: caddy:2
|
||||||
container_name: dumpster-caddy
|
container_name: dumpster-caddy
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "8443:8443"
|
||||||
volumes:
|
volumes:
|
||||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
- web_assets:/srv/web:ro
|
- web_assets:/srv/web:ro
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func SecurityHeaders(next http.Handler) http.Handler {
|
|||||||
w.Header().Set("Content-Security-Policy",
|
w.Header().Set("Content-Security-Policy",
|
||||||
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "+
|
"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';")
|
"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)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
-39
@@ -1003,9 +1003,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1020,9 +1017,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1037,9 +1031,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1054,9 +1045,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1071,9 +1059,6 @@
|
|||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1088,9 +1073,6 @@
|
|||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1105,9 +1087,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1122,9 +1101,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1139,9 +1115,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1156,9 +1129,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1173,9 +1143,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1190,9 +1157,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1207,9 +1171,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"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 { useAuthStore } from '../stores/auth.ts';
|
||||||
import { VoiceControls } from './VoiceControls.tsx';
|
import { VoiceControls } from './VoiceControls.tsx';
|
||||||
import { VideoGrid } from './VideoGrid.tsx';
|
import { VideoGrid } from './VideoGrid.tsx';
|
||||||
|
import { AudioRenderers } from './AudioRenderers.tsx';
|
||||||
|
|
||||||
export function VoicePanel() {
|
export function VoicePanel() {
|
||||||
const isConnected = useVoiceStore((state) => state.isConnected);
|
const isConnected = useVoiceStore((state) => state.isConnected);
|
||||||
@@ -122,6 +123,7 @@ export function VoicePanel() {
|
|||||||
|
|
||||||
{/* Video grid */}
|
{/* Video grid */}
|
||||||
<VideoGrid />
|
<VideoGrid />
|
||||||
|
<AudioRenderers />
|
||||||
|
|
||||||
{/* Controls */}
|
{/* Controls */}
|
||||||
<div className="px-3 py-1.5">
|
<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