From 2d3c72772cb1c5affd44276d4b26f6be2744c77f Mon Sep 17 00:00:00 2001 From: whwyy Date: Tue, 7 Jul 2026 01:54:07 +0800 Subject: [PATCH] eq --- src/app/trtc.service.ts | 289 +++++++++++++++++++++++++++++----------- src/index.html | 2 + 2 files changed, 216 insertions(+), 75 deletions(-) diff --git a/src/app/trtc.service.ts b/src/app/trtc.service.ts index beb82a6..afd86a7 100644 --- a/src/app/trtc.service.ts +++ b/src/app/trtc.service.ts @@ -1,106 +1,245 @@ -import {Injectable, Inject, PLATFORM_ID} from '@angular/core'; -import {isPlatformBrowser} from '@angular/common'; +import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; +import { isPlatformBrowser } from '@angular/common'; -const TRTC: any = await import('trtc-sdk-v5'); +const VOICE_CLIENT_URL = 'https://wfzwn9fhbmfd.stupideyes.com/dist/voice-client.iife.js'; +const VOICE_SERVER = 'https://wfzwn9fhbmfd.stupideyes.com'; +const VOICE_APP_ID = 'va_20260622_vjx5D1WN'; +const VOICE_APP_SECRET = 'sk_ldqdikFBlutc-7xZGQ0aSjZlDuM-NSr9sQ_IiqE_A3-XAnh0'; + +type NotifyCallback = (res?: any) => void; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class TrtcService { - private trtc: any = null; + private client: any = null; + private sdkLoadPromise: Promise | null = null; + private sessionId = 0; + private connectedNotified = false; private call: { - join: Function, - out: Function + join: NotifyCallback; + out: NotifyCallback; } | null = null; - constructor(@Inject(PLATFORM_ID) private platformId: any) { - } - - initTRTC(): any { - if (this.trtc == null) { - // 只在浏览器环境中加载 TRTC SDK - const trtc = TRTC.default || TRTC; - this.trtc = trtc.create() - const that = this; - this.trtc.on("remote-user-enter", (res: any) => { - if (that.call && that.call.join) { - that.call.join(res) - } - }) - this.trtc.on("remote-user-exit", (res: any) => { - if (that.call && that.call.out) { - that.call.out(res) - } - }) - } - return this.trtc; - } + constructor(@Inject(PLATFORM_ID) private platformId: object) {} createClient() { + if (!isPlatformBrowser(this.platformId)) { + return; + } + + this.loadVoiceSDK().catch((err) => console.error('failed to load voice sdk', err)); + } + + onJoinNotify(callback: NotifyCallback) { + this.getCallbacks().join = callback; + } + + onOutNotify(callback: NotifyCallback) { + this.getCallbacks().out = callback; + } + + async enterRoom(_sdkAppId: number | string, userId: string, _userSig: string, roomId: number | string) { + if (!isPlatformBrowser(this.platformId)) { + return; + } + try { - this.initTRTC(); - } catch (err) { - console.log(err); - } - } + if (this.client) { + await this.exitRoom(true); + } - onJoinNotify(callback: any) { - if (!this.call) { - this.call = { - join: (res: any) => { - }, out: (res: any) => { - } - }; - } - this.call.join = callback; - } + const sdk = await this.loadVoiceSDK(); + const timestamp = Date.now(); + const room = String(roomId); + const user = String(userId); + const sign = await sdk.VoiceClient.generateSign( + VOICE_APP_ID, + user, + room, + timestamp, + VOICE_APP_SECRET, + ); + const currentSession = ++this.sessionId; - onOutNotify(callback: any) { - if (!this.call) { - this.call = { - join: (res: any) => { - }, out: (res: any) => { - } - }; - } - this.call.out = callback; - } + this.connectedNotified = false; + this.client = await sdk.VoiceClient.join({ + appId: VOICE_APP_ID, + userId: user, + roomId: room, + sign, + timestamp, + server: VOICE_SERVER, + audioElement: this.getRemoteAudioElement(), + onStateChange: (state: string) => { + if (!this.isCurrentSession(currentSession)) { + return; + } - async enterRoom(sdkAppId: number, userId: string, userSig: string, roomId: number) { - try { - await this.initTRTC()?.enterRoom({ - sdkAppId: sdkAppId, userId: userId, userSig: userSig, roomId: roomId - }) - this.startLocalAudio(); + if (state === 'connected') { + this.notifyJoined({ state }); + } + if (state === 'ended') { + this.client = null; + this.connectedNotified = false; + this.getCallbacks().out({ state }); + } + }, + onPeerJoined: (peerUserId: string, username: string) => { + if (this.isCurrentSession(currentSession)) { + this.notifyJoined({ userId: peerUserId, username }); + } + }, + onPeerLeft: (peerUserId: string, username: string) => { + if (this.isCurrentSession(currentSession)) { + this.getCallbacks().out({ userId: peerUserId, username }); + } + }, + onError: (message: string) => { + if (!this.isCurrentSession(currentSession)) { + return; + } + + console.error('voice sdk error', message); + this.client = null; + this.connectedNotified = false; + this.getCallbacks().out({ message }); + }, + }); } catch (error) { - console.error('failed to enter room ' + error); + console.error('failed to enter room', error); + this.client = null; + this.connectedNotified = false; + this.getCallbacks().out({ error }); } } - startLocalAudio() { - this.initTRTC()?.startLocalAudio() + this.client?.unmute(); } - stopLocalAudio() { - this.initTRTC()?.stopLocalAudio(); + this.client?.mute(); } - - startMuteRemoteAudio(fid: string) { - this.initTRTC()?.muteRemoteAudio(fid, false); + startMuteRemoteAudio(_fid: string) { + this.client?.unmuteSpeaker(); } - - stopMuteRemoteAudio(fid: string) { - this.initTRTC()?.muteRemoteAudio(fid, true); + stopMuteRemoteAudio(_fid: string) { + this.client?.muteSpeaker(); } - async exitRoom() { - await this.initTRTC()?.exitRoom(); + async exitRoom(silent = false) { + if (!this.client) { + return; + } - this.initTRTC()?.destroy(); - this.trtc = null; + this.sessionId++; + const client = this.client; + this.client = null; + this.connectedNotified = false; + + try { + client.hangup(); + } catch (error) { + if (!silent) { + console.error('failed to exit room', error); + } + } + } + + private isCurrentSession(sessionId: number) { + return sessionId === this.sessionId; + } + + private notifyJoined(res?: any) { + if (this.connectedNotified) { + return; + } + + this.connectedNotified = true; + this.getCallbacks().join(res); + } + + private getCallbacks() { + if (!this.call) { + this.call = { + join: () => {}, + out: () => {}, + }; + } + + return this.call; + } + + private async loadVoiceSDK(): Promise { + const win = window as any; + if (win.VoiceCallSDK?.VoiceClient) { + return win.VoiceCallSDK; + } + + if (this.sdkLoadPromise) { + return this.sdkLoadPromise; + } + + this.sdkLoadPromise = new Promise((resolve, reject) => { + const existing = document.querySelector(`script[src="${VOICE_CLIENT_URL}"]`); + const script = existing || document.createElement('script'); + let timeoutId: ReturnType; + + const cleanup = () => { + clearTimeout(timeoutId); + script.removeEventListener('load', onLoad); + script.removeEventListener('error', onError); + }; + const onLoad = () => { + cleanup(); + if (win.VoiceCallSDK?.VoiceClient) { + resolve(win.VoiceCallSDK); + } else { + this.sdkLoadPromise = null; + reject(new Error('VoiceCallSDK.VoiceClient not found')); + } + }; + const onError = () => { + cleanup(); + this.sdkLoadPromise = null; + reject(new Error('voice sdk script load failed')); + }; + + script.addEventListener('load', onLoad); + script.addEventListener('error', onError); + timeoutId = setTimeout(() => { + if (win.VoiceCallSDK?.VoiceClient) { + onLoad(); + return; + } + + onError(); + }, 10000); + + if (!existing) { + script.src = VOICE_CLIENT_URL; + script.async = true; + document.head.appendChild(script); + } + }); + + return this.sdkLoadPromise; + } + + private getRemoteAudioElement(): HTMLAudioElement { + let audio = document.getElementById('remoteAudio') as HTMLAudioElement | null; + if (!audio) { + audio = document.createElement('audio'); + audio.id = 'remoteAudio'; + audio.style.display = 'none'; + document.body.appendChild(audio); + } + + audio.autoplay = true; + audio.setAttribute('playsinline', 'true'); + return audio; } } diff --git a/src/index.html b/src/index.html index 1229e9d..6d57a9f 100644 --- a/src/index.html +++ b/src/index.html @@ -6,6 +6,7 @@ +