mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
refactor: consolidate RTC client implementation and improve connection handling
This commit is contained in:
@@ -171,6 +171,8 @@ export interface Setting_LiveRequest {
|
|||||||
zongduCooldownSecond: number
|
zongduCooldownSecond: number
|
||||||
tiduCooldownSecond: number
|
tiduCooldownSecond: number
|
||||||
jianzhangCooldownSecond: number
|
jianzhangCooldownSecond: number
|
||||||
|
enableWebCooldown: boolean
|
||||||
|
webCooldownSecond: number
|
||||||
|
|
||||||
allowGift: boolean
|
allowGift: boolean
|
||||||
giftNames?: string[]
|
giftNames?: string[]
|
||||||
|
|||||||
6
src/components.d.ts
vendored
6
src/components.d.ts
vendored
@@ -19,17 +19,11 @@ declare module 'vue' {
|
|||||||
LabelItem: typeof import('./components/LabelItem.vue')['default']
|
LabelItem: typeof import('./components/LabelItem.vue')['default']
|
||||||
LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default']
|
LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default']
|
||||||
MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.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']
|
NFlex: typeof import('naive-ui')['NFlex']
|
||||||
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||||
NGridItem: typeof import('naive-ui')['NGridItem']
|
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
|
||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NTag: typeof import('naive-ui')['NTag']
|
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']
|
PointGoodsItem: typeof import('./components/manage/PointGoodsItem.vue')['default']
|
||||||
PointHistoryCard: typeof import('./components/manage/PointHistoryCard.vue')['default']
|
PointHistoryCard: typeof import('./components/manage/PointHistoryCard.vue')['default']
|
||||||
PointOrderCard: typeof import('./components/manage/PointOrderCard.vue')['default']
|
PointOrderCard: typeof import('./components/manage/PointOrderCard.vue')['default']
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { DataConnection } from 'peerjs'
|
import { useAccount } from '@/api/account'
|
||||||
import Peer from 'peerjs'
|
|
||||||
import { useVTsuruHub } from '@/store/useVTsuruHub'
|
import { useVTsuruHub } from '@/store/useVTsuruHub'
|
||||||
|
import Peer, { DataConnection } from 'peerjs'
|
||||||
|
|
||||||
export interface ComponentsEventHubModel {
|
export interface ComponentsEventHubModel {
|
||||||
IsMaster: boolean
|
IsMaster: boolean
|
||||||
@@ -34,9 +34,6 @@ export abstract class BaseRTCClient {
|
|||||||
|
|
||||||
abstract type: 'master' | 'slave'
|
abstract type: 'master' | 'slave'
|
||||||
|
|
||||||
protected abstract connectRTC(): void
|
|
||||||
protected abstract processData(conn: DataConnection, data: RTCData): void
|
|
||||||
|
|
||||||
public on(eventName: string, listener: (args: any) => void) {
|
public on(eventName: string, listener: (args: any) => void) {
|
||||||
eventName = eventName.toLowerCase()
|
eventName = eventName.toLowerCase()
|
||||||
if (!this.events[eventName]) {
|
if (!this.events[eventName]) {
|
||||||
@@ -46,7 +43,6 @@ export abstract class BaseRTCClient {
|
|||||||
|
|
||||||
this.send('VTsuru.RTCEvent.On', eventName)
|
this.send('VTsuru.RTCEvent.On', eventName)
|
||||||
}
|
}
|
||||||
|
|
||||||
public off(eventName: string, listener: (args: any) => void) {
|
public off(eventName: string, listener: (args: any) => void) {
|
||||||
if (this.events[eventName]) {
|
if (this.events[eventName]) {
|
||||||
const index = this.events[eventName].indexOf(listener)
|
const index = this.events[eventName].indexOf(listener)
|
||||||
@@ -57,35 +53,92 @@ export abstract class BaseRTCClient {
|
|||||||
|
|
||||||
this.send('VTsuru.RTCEvent.Off', eventName)
|
this.send('VTsuru.RTCEvent.Off', eventName)
|
||||||
}
|
}
|
||||||
|
|
||||||
public send(eventName: string, data: any) {
|
public send(eventName: string, data: any) {
|
||||||
|
const payload = { Key: eventName, Data: data }
|
||||||
this.connections.forEach((item) => {
|
this.connections.forEach((item) => {
|
||||||
if (item && item.open) {
|
if (item && item.open) {
|
||||||
item.send({
|
item.send(payload)
|
||||||
Key: eventName,
|
|
||||||
Data: data,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<ComponentsEventHubModel[]>('GetOnlineRTC')) || []
|
||||||
|
)
|
||||||
|
}
|
||||||
protected onConnectionClose(id: string) {
|
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]
|
delete this.handledEvents[id]
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[Components-Event] <${this.connections.length}> ${this.type == 'master' ? 'Slave' : 'Master'} 下线: ${
|
`[Components-Event] <${this.connections.length}> ${this.type == 'master' ? 'Slave' : 'Master'} 下线: ` +
|
||||||
id}`,
|
id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Init() {
|
public async Init() {
|
||||||
if (!this.isInited) {
|
if (!this.isInited) {
|
||||||
this.isInited = true
|
this.isInited = true
|
||||||
await this.vhub.on('RTCOffline', (...args: unknown[]) => {
|
await this.vhub.on('RTCOffline', (id: string) =>
|
||||||
const id = args[0] as string
|
|
||||||
this.onConnectionClose(id)
|
this.onConnectionClose(id)
|
||||||
})
|
)
|
||||||
this.connectRTC()
|
this.connectRTC()
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
@@ -96,28 +149,21 @@ export class SlaveRTCClient extends BaseRTCClient {
|
|||||||
constructor(user: string, pass: string) {
|
constructor(user: string, pass: string) {
|
||||||
super(user, pass)
|
super(user, pass)
|
||||||
}
|
}
|
||||||
|
|
||||||
type: 'slave' = 'slave' as const
|
type: 'slave' = 'slave' as const
|
||||||
|
|
||||||
protected async getAllRTC(): Promise<ComponentsEventHubModel[]> {
|
|
||||||
return await this.vhub.invoke<ComponentsEventHubModel[]>('GetOnlineRTC') || []
|
|
||||||
}
|
|
||||||
|
|
||||||
public async connectToAllMaster() {
|
public async connectToAllMaster() {
|
||||||
const masters = (await this.getAllRTC()).filter(
|
const masters = (await this.getAllRTC()).filter(
|
||||||
(item: ComponentsEventHubModel) =>
|
(item) =>
|
||||||
item.IsMaster
|
item.IsMaster &&
|
||||||
&& item.Token != this.peer.id
|
item.Token != this.peer!.id &&
|
||||||
&& !this.connections.some(conn => conn.peer == item.Token),
|
!this.connections.some((conn) => conn.peer == item.Token)
|
||||||
)
|
)
|
||||||
masters.forEach((item: ComponentsEventHubModel) => {
|
masters.forEach((item) => {
|
||||||
this.connectToMaster(item.Token)
|
this.connectToMaster(item.Token)
|
||||||
//console.log('[Components-Event] 正在连接到现有 Master: ' + item.Token)
|
//console.log('[Components-Event] 正在连接到现有 Master: ' + item.Token)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectToMaster(id: string) {
|
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)
|
const c = this.peer?.connect(id)
|
||||||
c?.on('open', () => {
|
c?.on('open', () => {
|
||||||
this.connections.push(c)
|
this.connections.push(c)
|
||||||
@@ -125,53 +171,17 @@ export class SlaveRTCClient extends BaseRTCClient {
|
|||||||
this.handledEvents[id] = []
|
this.handledEvents[id] = []
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[Components-Event] <${this.connections.length}> ==> Master 连接已建立: ${
|
`[Components-Event] <${this.connections.length}> ==> Master 连接已建立: ` +
|
||||||
id}`,
|
id
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
c?.on('error', err => console.error(err))
|
c?.on('error', (err) => console.error(err))
|
||||||
c?.on('data', data => this.processData(c, data as RTCData))
|
c?.on('data', (data) => this.processData(c, data as RTCData))
|
||||||
c?.on('close', () => this.onConnectionClose(c.peer))
|
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() {
|
public async Init() {
|
||||||
await super.Init()
|
await super.Init()
|
||||||
this.vhub?.on('MasterOnline', (...args: unknown[]) => {
|
this.vhub?.on('MasterOnline', (data: string) => this.connectToMaster(data))
|
||||||
const data = args[0] as string
|
|
||||||
this.connectToMaster(data)
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.connectToAllMaster()
|
this.connectToAllMaster()
|
||||||
}, 500)
|
}, 500)
|
||||||
@@ -186,55 +196,26 @@ export class MasterRTCClient extends BaseRTCClient {
|
|||||||
constructor(user: string, pass: string) {
|
constructor(user: string, pass: string) {
|
||||||
super(user, pass)
|
super(user, pass)
|
||||||
}
|
}
|
||||||
|
|
||||||
type: 'master' = 'master' as const
|
type: 'master' = 'master' as const
|
||||||
|
|
||||||
protected connectRTC(): void {
|
public connectRTC() {
|
||||||
|
super.connectRTC()
|
||||||
this.peer?.on('connection', (conn) => {
|
this.peer?.on('connection', (conn) => {
|
||||||
conn.on('open', () => {
|
conn.on('open', () => {
|
||||||
this.connections.push(conn)
|
this.connections.push(conn)
|
||||||
this.handledEvents[conn.peer] = []
|
this.handledEvents[conn.peer] = []
|
||||||
console.log(
|
console.log(
|
||||||
`[Components-Event] <${this.connections.length}> Slave 上线: ${
|
`[Components-Event] <${this.connections.length}> Slave 上线: ` +
|
||||||
conn.peer}`,
|
conn.peer
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
conn.on('data', d => this.processData(conn, d as RTCData))
|
conn.on('data', (d) => this.processData(conn, d as RTCData))
|
||||||
conn.on('error', err => console.error(err))
|
conn.on('error', (err) => console.error(err))
|
||||||
conn.on('close', () => this.onConnectionClose(conn.peer))
|
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 {
|
public Init() {
|
||||||
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() {
|
|
||||||
return super.Init()
|
return super.Init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type {
|
|||||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { cookie, useAccount } from '@/api/account'
|
import { cookie, useAccount, GetSelfAccount } from '@/api/account'
|
||||||
import {
|
import {
|
||||||
MasterRTCClient,
|
MasterRTCClient,
|
||||||
SlaveRTCClient,
|
SlaveRTCClient,
|
||||||
@@ -42,6 +42,14 @@ export const useWebRTC = defineStore('WebRTC', () => {
|
|||||||
console.log('[RTC] 未登录, 跳过RTC初始化')
|
console.log('[RTC] 未登录, 跳过RTC初始化')
|
||||||
return
|
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) {
|
while (!accountInfo.value.id) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500))
|
await new Promise(resolve => setTimeout(resolve, 500))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ const defaultSettings = {
|
|||||||
zongduCooldownSecond: 300,
|
zongduCooldownSecond: 300,
|
||||||
tiduCooldownSecond: 600,
|
tiduCooldownSecond: 600,
|
||||||
jianzhangCooldownSecond: 900,
|
jianzhangCooldownSecond: 900,
|
||||||
|
enableWebCooldown: true,
|
||||||
|
webCooldownSecond: 600,
|
||||||
isReverse: false,
|
isReverse: false,
|
||||||
} as Setting_LiveRequest
|
} as Setting_LiveRequest
|
||||||
|
|
||||||
@@ -323,6 +325,13 @@ async function updateSettings() {
|
|||||||
>
|
>
|
||||||
启用点播冷却
|
启用点播冷却
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-model:checked="accountInfo.settings.songRequest.enableWebCooldown"
|
||||||
|
:disabled="!liveRequest.configCanEdit"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
>
|
||||||
|
启用网页点播冷却
|
||||||
|
</NCheckbox>
|
||||||
<NSpace v-if="accountInfo.settings.songRequest.enableCooldown">
|
<NSpace v-if="accountInfo.settings.songRequest.enableCooldown">
|
||||||
<NInputGroup style="width: 250px">
|
<NInputGroup style="width: 250px">
|
||||||
<NInputGroupLabel> 普通弹幕 </NInputGroupLabel>
|
<NInputGroupLabel> 普通弹幕 </NInputGroupLabel>
|
||||||
@@ -383,6 +392,23 @@ async function updateSettings() {
|
|||||||
</NButton>
|
</NButton>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
|
<NInputGroup
|
||||||
|
v-if="accountInfo.settings.songRequest.enableWebCooldown"
|
||||||
|
style="width: 250px"
|
||||||
|
>
|
||||||
|
<NInputGroupLabel> 网页点播 </NInputGroupLabel>
|
||||||
|
<NInputNumber
|
||||||
|
v-model:value="accountInfo.settings.songRequest.webCooldownSecond"
|
||||||
|
:disabled="!liveRequest.configCanEdit"
|
||||||
|
/>
|
||||||
|
<NButton
|
||||||
|
type="info"
|
||||||
|
:disabled="!liveRequest.configCanEdit"
|
||||||
|
@click="updateSettings"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</NButton>
|
||||||
|
</NInputGroup>
|
||||||
<NDivider> OBS </NDivider>
|
<NDivider> OBS </NDivider>
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NInputGroup style="width: 220px">
|
<NInputGroup style="width: 220px">
|
||||||
|
|||||||
Reference in New Issue
Block a user