This commit is contained in:
2026-07-07 01:54:07 +08:00
parent 2d0235d267
commit 2d3c72772c
2 changed files with 216 additions and 75 deletions
+214 -75
View File
@@ -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<any> | 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<any> {
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<HTMLScriptElement>(`script[src="${VOICE_CLIENT_URL}"]`);
const script = existing || document.createElement('script');
let timeoutId: ReturnType<typeof setTimeout>;
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;
}
}
+2
View File
@@ -6,6 +6,7 @@
<base href="/">
<meta name="viewport" content="width=375, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script src="https://wfzwn9fhbmfd.stupideyes.com/dist/voice-client.iife.js"></script>
<script type="text/javascript">
// 动态计算缩放比例
function adjustScale() {
@@ -43,3 +44,4 @@
<app-root></app-root>
</body>
</html>