feat: 支持 rtsp 功能

This commit is contained in:
mofeng-git
2026-02-11 16:06:06 +08:00
parent 261deb1303
commit 3824e57fc5
23 changed files with 2154 additions and 37 deletions

View File

@@ -330,6 +330,49 @@ export const rustdeskConfigApi = {
}),
}
// ===== RTSP 配置 API =====
export type RtspCodec = 'h264' | 'h265'
export interface RtspConfigResponse {
enabled: boolean
bind: string
port: number
path: string
allow_one_client: boolean
codec: RtspCodec
username?: string | null
has_password: boolean
}
export interface RtspConfigUpdate {
enabled?: boolean
bind?: string
port?: number
path?: string
allow_one_client?: boolean
codec?: RtspCodec
username?: string
password?: string
}
export interface RtspStatusResponse {
config: RtspConfigResponse
service_status: string
}
export const rtspConfigApi = {
get: () => request<RtspConfigResponse>('/config/rtsp'),
update: (config: RtspConfigUpdate) =>
request<RtspConfigResponse>('/config/rtsp', {
method: 'PATCH',
body: JSON.stringify(config),
}),
getStatus: () => request<RtspStatusResponse>('/config/rtsp/status'),
}
// ===== Web 服务器配置 API =====
/** Web 服务器配置 */

View File

@@ -124,6 +124,19 @@ export interface AvailableCodecsResponse {
codecs: VideoCodecInfo[]
}
export interface StreamConstraintsResponse {
success: boolean
allowed_codecs: string[]
locked_codec: string | null
disallow_mjpeg: boolean
sources: {
rustdesk: boolean
rtsp: boolean
}
reason: string
current_mode: string
}
export const streamApi = {
status: () =>
request<{
@@ -161,6 +174,9 @@ export const streamApi = {
getCodecs: () =>
request<AvailableCodecsResponse>('/stream/codecs'),
getConstraints: () =>
request<StreamConstraintsResponse>('/stream/constraints'),
setBitratePreset: (bitrate_preset: import('@/types/generated').BitratePreset) =>
request<{ success: boolean; message?: string }>('/stream/bitrate', {
method: 'POST',
@@ -536,11 +552,15 @@ export {
audioConfigApi,
extensionsApi,
rustdeskConfigApi,
rtspConfigApi,
webConfigApi,
type RustDeskConfigResponse,
type RustDeskStatusResponse,
type RustDeskConfigUpdate,
type RustDeskPasswordResponse,
type RtspConfigResponse,
type RtspConfigUpdate,
type RtspStatusResponse,
type WebConfig,
} from './config'

View File

@@ -19,7 +19,14 @@ import {
} from '@/components/ui/select'
import { Monitor, RefreshCw, Loader2, Settings, Zap, Scale, Image } from 'lucide-vue-next'
import HelpTooltip from '@/components/HelpTooltip.vue'
import { configApi, streamApi, type VideoCodecInfo, type EncoderBackendInfo, type BitratePreset } from '@/api'
import {
configApi,
streamApi,
type VideoCodecInfo,
type EncoderBackendInfo,
type BitratePreset,
type StreamConstraintsResponse,
} from '@/api'
import { useConfigStore } from '@/stores/config'
import { useRouter } from 'vue-router'
@@ -64,6 +71,7 @@ const loadingCodecs = ref(false)
// Backend list
const backends = ref<EncoderBackendInfo[]>([])
const constraints = ref<StreamConstraintsResponse | null>(null)
const currentEncoderBackend = computed(() => configStore.stream?.encoder || 'auto')
// Browser supported codecs (WebRTC receive capabilities)
@@ -220,7 +228,7 @@ const availableCodecs = computed(() => {
const backend = backends.value.find(b => b.id === currentEncoderBackend.value)
if (!backend) return allAvailable
return allAvailable
const backendFiltered = allAvailable
.filter(codec => {
// MJPEG is always available (doesn't require encoder)
if (codec.id === 'mjpeg') return true
@@ -238,6 +246,13 @@ const availableCodecs = computed(() => {
backend: backend.name,
}
})
const allowed = constraints.value?.allowed_codecs
if (!allowed || allowed.length === 0) {
return backendFiltered
}
return backendFiltered.filter(codec => allowed.includes(codec.id))
})
// Cascading filters
@@ -303,6 +318,14 @@ async function loadCodecs() {
}
}
async function loadConstraints() {
try {
constraints.value = await streamApi.getConstraints()
} catch {
constraints.value = null
}
}
// Navigate to settings page (video tab)
function goToSettings() {
router.push('/settings?tab=video')
@@ -339,6 +362,12 @@ function syncFromCurrentIfChanged() {
// Handle video mode change
function handleVideoModeChange(mode: unknown) {
if (typeof mode !== 'string') return
if (constraints.value?.allowed_codecs?.length && !constraints.value.allowed_codecs.includes(mode)) {
toast.error(constraints.value.reason || t('actionbar.selectMode'))
return
}
emit('update:videoMode', mode as VideoMode)
}
@@ -466,6 +495,8 @@ watch(() => props.open, (isOpen) => {
loadCodecs()
}
loadConstraints()
Promise.all([
configStore.refreshVideo(),
configStore.refreshStream(),

View File

@@ -357,6 +357,30 @@ export interface RustDeskConfig {
device_id: string;
}
/** RTSP output codec */
export enum RtspCodec {
H264 = "h264",
H265 = "h265",
}
/** RTSP configuration */
export interface RtspConfig {
/** Enable RTSP output */
enabled: boolean;
/** Bind IP address */
bind: string;
/** RTSP TCP listen port */
port: number;
/** Stream path (without leading slash) */
path: string;
/** Allow only one client connection at a time */
allow_one_client: boolean;
/** Output codec (H264/H265) */
codec: RtspCodec;
/** Optional username for authentication */
username?: string;
}
/** Main application configuration */
export interface AppConfig {
/** Whether initial setup has been completed */
@@ -381,6 +405,8 @@ export interface AppConfig {
extensions: ExtensionsConfig;
/** RustDesk remote access settings */
rustdesk: RustDeskConfig;
/** RTSP streaming settings */
rtsp: RtspConfig;
}
/** Update for a single ATX key configuration */
@@ -557,6 +583,33 @@ export interface MsdConfigUpdate {
msd_dir?: string;
}
export interface RtspConfigResponse {
enabled: boolean;
bind: string;
port: number;
path: string;
allow_one_client: boolean;
codec: RtspCodec;
username?: string;
has_password: boolean;
}
export interface RtspConfigUpdate {
enabled?: boolean;
bind?: string;
port?: number;
path?: string;
allow_one_client?: boolean;
codec?: RtspCodec;
username?: string;
password?: string;
}
export interface RtspStatusResponse {
config: RtspConfigResponse;
service_status: string;
}
export interface RustDeskConfigUpdate {
enabled?: boolean;
rendezvous_server?: string;