From 3e76684891b21a3be549a89caf2b2afa07f260ac Mon Sep 17 00:00:00 2001 From: Megghy Date: Sat, 25 Oct 2025 21:54:14 +0800 Subject: [PATCH] refactor: consolidate RTC client implementation and improve connection handling --- src/api/api-models.ts | 2 + src/components.d.ts | 6 - src/data/RTCClient.ts | 197 ++++++++---------- src/store/useRTC.ts | 10 +- .../components/SongRequestSettings.vue | 26 +++ 5 files changed, 126 insertions(+), 115 deletions(-) diff --git a/src/api/api-models.ts b/src/api/api-models.ts index cb0bb2f..2f9d9d1 100644 --- a/src/api/api-models.ts +++ b/src/api/api-models.ts @@ -171,6 +171,8 @@ export interface Setting_LiveRequest { zongduCooldownSecond: number tiduCooldownSecond: number jianzhangCooldownSecond: number + enableWebCooldown: boolean + webCooldownSecond: number allowGift: boolean giftNames?: string[] diff --git a/src/components.d.ts b/src/components.d.ts index b368e71..aa98134 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -19,17 +19,11 @@ declare module 'vue' { LabelItem: typeof import('./components/LabelItem.vue')['default'] LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default'] MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.vue')['default'] - NAlert: typeof import('naive-ui')['NAlert'] - NEllipsis: typeof import('naive-ui')['NEllipsis'] - NEmpty: typeof import('naive-ui')['NEmpty'] NFlex: typeof import('naive-ui')['NFlex'] NFormItemGi: typeof import('naive-ui')['NFormItemGi'] NGridItem: typeof import('naive-ui')['NGridItem'] - NIcon: typeof import('naive-ui')['NIcon'] NScrollbar: typeof import('naive-ui')['NScrollbar'] NTag: typeof import('naive-ui')['NTag'] - NText: typeof import('naive-ui')['NText'] - NTime: typeof import('naive-ui')['NTime'] PointGoodsItem: typeof import('./components/manage/PointGoodsItem.vue')['default'] PointHistoryCard: typeof import('./components/manage/PointHistoryCard.vue')['default'] PointOrderCard: typeof import('./components/manage/PointOrderCard.vue')['default'] diff --git a/src/data/RTCClient.ts b/src/data/RTCClient.ts index 7f983ba..89a8f49 100644 --- a/src/data/RTCClient.ts +++ b/src/data/RTCClient.ts @@ -1,6 +1,6 @@ -import type { DataConnection } from 'peerjs' -import Peer from 'peerjs' +import { useAccount } from '@/api/account' import { useVTsuruHub } from '@/store/useVTsuruHub' +import Peer, { DataConnection } from 'peerjs' export interface ComponentsEventHubModel { IsMaster: boolean @@ -34,9 +34,6 @@ export abstract class BaseRTCClient { abstract type: 'master' | 'slave' - protected abstract connectRTC(): void - protected abstract processData(conn: DataConnection, data: RTCData): void - public on(eventName: string, listener: (args: any) => void) { eventName = eventName.toLowerCase() if (!this.events[eventName]) { @@ -46,7 +43,6 @@ export abstract class BaseRTCClient { this.send('VTsuru.RTCEvent.On', eventName) } - public off(eventName: string, listener: (args: any) => void) { if (this.events[eventName]) { const index = this.events[eventName].indexOf(listener) @@ -57,35 +53,92 @@ export abstract class BaseRTCClient { this.send('VTsuru.RTCEvent.Off', eventName) } - public send(eventName: string, data: any) { + const payload = { Key: eventName, Data: data } this.connections.forEach((item) => { if (item && item.open) { - item.send({ - Key: eventName, - Data: data, - }) + item.send(payload) } }) } + protected connectRTC() { + //console.log('[Components-Event] 正在连接到 PeerJS 服务器...') + this.peer = new Peer({ + host: 'peer.suki.club', + port: 443, + key: 'vtsuru', + secure: true, + config: { + iceServers: [ + { urls: ['stun:turn.suki.club'] }, + { + urls: ['turn:turn.suki.club'], + username: this.user, + credential: this.pass + } + ] + } + //debug: 3 + }) + + this.peer?.on('open', async (id) => { + console.log('[Components-Event] 已连接到 PeerJS 服务器: ' + id) + this.vhub?.send('SetRTCToken', id, this.type == 'master') + }) + this.peer?.on('error', (err) => { + console.error(err) + }) + this.peer?.on('close', () => { + console.log('[Components-Event] PeerJS 连接已关闭') + }) + this.peer?.on('disconnected', () => { + console.log('[Components-Event] PeerJS 连接已断开') + this.peer?.reconnect() + }) + } + public processData(conn: DataConnection, data: RTCData) { + //console.log(data) + if (data.Key == 'Heartbeat') { + // 心跳 + return + } else if (data.Key == 'VTsuru.RTCEvent.On') { + // 添加事件 + this.handledEvents[conn.peer].push(data.Data) + } else if (data.Key == 'VTsuru.RTCEvent.Off') { + // 移除事件 + const i = this.handledEvents[conn.peer].indexOf(data.Data) + if (i > -1) { + this.handledEvents[conn.peer].splice(i, 1) + } + } else { + const key = data.Key.toLowerCase() + if (this.events[key]) { + this.events[key].forEach((item) => item(data.Data)) + } + } + } + public async getAllRTC() { + return ( + (await this.vhub.invoke('GetOnlineRTC')) || [] + ) + } protected onConnectionClose(id: string) { - this.connections = this.connections.filter(item => item && item.peer != id) + this.connections = this.connections.filter((item) => item.peer != id) delete this.handledEvents[id] console.log( - `[Components-Event] <${this.connections.length}> ${this.type == 'master' ? 'Slave' : 'Master'} 下线: ${ - id}`, + `[Components-Event] <${this.connections.length}> ${this.type == 'master' ? 'Slave' : 'Master'} 下线: ` + + id ) } public async Init() { if (!this.isInited) { this.isInited = true - await this.vhub.on('RTCOffline', (...args: unknown[]) => { - const id = args[0] as string + await this.vhub.on('RTCOffline', (id: string) => this.onConnectionClose(id) - }) + ) this.connectRTC() } return this @@ -96,28 +149,21 @@ export class SlaveRTCClient extends BaseRTCClient { constructor(user: string, pass: string) { super(user, pass) } - type: 'slave' = 'slave' as const - - protected async getAllRTC(): Promise { - return await this.vhub.invoke('GetOnlineRTC') || [] - } - public async connectToAllMaster() { const masters = (await this.getAllRTC()).filter( - (item: ComponentsEventHubModel) => - item.IsMaster - && item.Token != this.peer.id - && !this.connections.some(conn => conn.peer == item.Token), + (item) => + item.IsMaster && + item.Token != this.peer!.id && + !this.connections.some((conn) => conn.peer == item.Token) ) - masters.forEach((item: ComponentsEventHubModel) => { + masters.forEach((item) => { this.connectToMaster(item.Token) - // console.log('[Components-Event] 正在连接到现有 Master: ' + item.Token) + //console.log('[Components-Event] 正在连接到现有 Master: ' + item.Token) }) } - public connectToMaster(id: string) { - if (this.connections.some(conn => conn.peer == id)) return + if (this.connections.some((conn) => conn.peer == id)) return const c = this.peer?.connect(id) c?.on('open', () => { this.connections.push(c) @@ -125,53 +171,17 @@ export class SlaveRTCClient extends BaseRTCClient { this.handledEvents[id] = [] console.log( - `[Components-Event] <${this.connections.length}> ==> Master 连接已建立: ${ - id}`, + `[Components-Event] <${this.connections.length}> ==> Master 连接已建立: ` + + id ) }) - c?.on('error', err => console.error(err)) - c?.on('data', data => this.processData(c, data as RTCData)) + c?.on('error', (err) => console.error(err)) + c?.on('data', (data) => this.processData(c, data as RTCData)) c?.on('close', () => this.onConnectionClose(c.peer)) } - - protected connectRTC(): void { - this.peer = new Peer() - this.peer.on('open', (id) => { - console.log('[Components-Event] Slave Peer ID:', id) - this.vhub.send('RegisterRTC', false, id) - }) - this.peer.on('error', err => console.error('[Components-Event] Slave Peer Error:', err)) - } - - protected processData(conn: DataConnection, data: RTCData): void { - if (data.Key === 'VTsuru.RTCEvent.On') { - if (!this.handledEvents[conn.peer]) { - this.handledEvents[conn.peer] = [] - } - if (!this.handledEvents[conn.peer].includes(data.Data)) { - this.handledEvents[conn.peer].push(data.Data) - } - } else if (data.Key === 'VTsuru.RTCEvent.Off') { - if (this.handledEvents[conn.peer]) { - const index = this.handledEvents[conn.peer].indexOf(data.Data) - if (index > -1) { - this.handledEvents[conn.peer].splice(index, 1) - } - } - } else { - const eventName = data.Key.toLowerCase() - if (this.events[eventName]) { - this.events[eventName].forEach(listener => listener(data.Data)) - } - } - } - public async Init() { await super.Init() - this.vhub?.on('MasterOnline', (...args: unknown[]) => { - const data = args[0] as string - this.connectToMaster(data) - }) + this.vhub?.on('MasterOnline', (data: string) => this.connectToMaster(data)) setTimeout(() => { this.connectToAllMaster() }, 500) @@ -186,55 +196,26 @@ export class MasterRTCClient extends BaseRTCClient { constructor(user: string, pass: string) { super(user, pass) } - type: 'master' = 'master' as const - protected connectRTC(): void { + public connectRTC() { + super.connectRTC() this.peer?.on('connection', (conn) => { conn.on('open', () => { this.connections.push(conn) this.handledEvents[conn.peer] = [] console.log( - `[Components-Event] <${this.connections.length}> Slave 上线: ${ - conn.peer}`, + `[Components-Event] <${this.connections.length}> Slave 上线: ` + + conn.peer ) }) - conn.on('data', d => this.processData(conn, d as RTCData)) - conn.on('error', err => console.error(err)) + conn.on('data', (d) => this.processData(conn, d as RTCData)) + conn.on('error', (err) => console.error(err)) conn.on('close', () => this.onConnectionClose(conn.peer)) }) - - this.peer = new Peer() - this.peer.on('open', (id) => { - console.log('[Components-Event] Master Peer ID:', id) - this.vhub.send('RegisterRTC', true, id) - }) - this.peer.on('error', err => console.error('[Components-Event] Master Peer Error:', err)) } - protected processData(conn: DataConnection, data: RTCData): void { - if (data.Key === 'VTsuru.RTCEvent.On') { - if (!this.handledEvents[conn.peer]) { - this.handledEvents[conn.peer] = [] - } - if (!this.handledEvents[conn.peer].includes(data.Data)) { - this.handledEvents[conn.peer].push(data.Data) - } - } else if (data.Key === 'VTsuru.RTCEvent.Off') { - if (this.handledEvents[conn.peer]) { - const index = this.handledEvents[conn.peer].indexOf(data.Data) - if (index > -1) { - this.handledEvents[conn.peer].splice(index, 1) - } - } - } else { - if (this.handledEvents[conn.peer]?.includes(data.Key.toLowerCase())) { - conn.send(data) - } - } - } - - public async Init() { + public Init() { return super.Init() } } diff --git a/src/store/useRTC.ts b/src/store/useRTC.ts index d4d2cd8..c55dbca 100644 --- a/src/store/useRTC.ts +++ b/src/store/useRTC.ts @@ -4,7 +4,7 @@ import type { import { acceptHMRUpdate, defineStore } from 'pinia' import { ref } from 'vue' import { useRoute } from 'vue-router' -import { cookie, useAccount } from '@/api/account' +import { cookie, useAccount, GetSelfAccount } from '@/api/account' import { MasterRTCClient, SlaveRTCClient, @@ -42,6 +42,14 @@ export const useWebRTC = defineStore('WebRTC', () => { console.log('[RTC] 未登录, 跳过RTC初始化') return } + // 当无 Cookie 但 url 上带 token 时,主动拉取账号信息,避免一直等待 + if (!cookie.value.cookie && route.query.token) { + try { + await GetSelfAccount(route.query.token as string) + } catch (e) { + console.error('[RTC] 获取账号信息失败:', e) + } + } while (!accountInfo.value.id) { await new Promise(resolve => setTimeout(resolve, 500)) } diff --git a/src/views/open_live/components/SongRequestSettings.vue b/src/views/open_live/components/SongRequestSettings.vue index c5852e5..8e3472d 100644 --- a/src/views/open_live/components/SongRequestSettings.vue +++ b/src/views/open_live/components/SongRequestSettings.vue @@ -38,6 +38,8 @@ const defaultSettings = { zongduCooldownSecond: 300, tiduCooldownSecond: 600, jianzhangCooldownSecond: 900, + enableWebCooldown: true, + webCooldownSecond: 600, isReverse: false, } as Setting_LiveRequest @@ -323,6 +325,13 @@ async function updateSettings() { > 启用点播冷却 + + 启用网页点播冷却 + 普通弹幕 @@ -383,6 +392,23 @@ async function updateSettings() { + + 网页点播 + + + 确定 + + OBS