|
|
@@ -0,0 +1,639 @@
|
|
|
+// import { Message as $msg } from '@arco-design/web-vue';
|
|
|
+import { getToken } from '@/api/system';
|
|
|
+import E2EEWorker from 'livekit-client/e2ee-worker?worker';
|
|
|
+import {
|
|
|
+ ConnectionQuality,
|
|
|
+ ConnectionState,
|
|
|
+ DisconnectReason,
|
|
|
+ ExternalE2EEKeyProvider,
|
|
|
+ LocalAudioTrack,
|
|
|
+ LocalParticipant,
|
|
|
+ LogLevel,
|
|
|
+ MediaDeviceFailure,
|
|
|
+ Participant,
|
|
|
+ ParticipantEvent,
|
|
|
+ RemoteParticipant,
|
|
|
+ RemoteTrackPublication,
|
|
|
+ RemoteVideoTrack,
|
|
|
+ Room,
|
|
|
+ RoomEvent,
|
|
|
+ ScreenSharePresets,
|
|
|
+ Track,
|
|
|
+ TrackPublication,
|
|
|
+ VideoPresets,
|
|
|
+ // VideoQuality,
|
|
|
+ createAudioAnalyser,
|
|
|
+ setLogLevel,
|
|
|
+ supportsAV1,
|
|
|
+ supportsVP9,
|
|
|
+} from 'livekit-client';
|
|
|
+import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
|
|
+
|
|
|
+// 指纹
|
|
|
+const getFingerprint = async () => {
|
|
|
+ const fpPromise = FingerprintJS.load();
|
|
|
+ const fp = await fpPromise;
|
|
|
+ const result = await fp.get();
|
|
|
+ // const components = {
|
|
|
+ // languages: result.components.languages,
|
|
|
+ // colorDepth: result.components.colorDepth,
|
|
|
+ // deviceMemory: result.components.deviceMemory,
|
|
|
+ // hardwareConcurrency: result.components.hardwareConcurrency,
|
|
|
+ // timezone: result.components.timezone,
|
|
|
+ // platform: result.components.platform,
|
|
|
+ // vendor: result.components.vendor,
|
|
|
+ // vendorFlavors: result.components.vendorFlavors,
|
|
|
+ // cookiesEnabled: result.components.cookiesEnabled,
|
|
|
+ // colorGamut: result.components.colorGamut,
|
|
|
+ // hdr: result.components.hdr,
|
|
|
+ // videoCard: result.components.videoCard,
|
|
|
+ // };
|
|
|
+ return result.visitorId;
|
|
|
+};
|
|
|
+
|
|
|
+const state = {
|
|
|
+ isFrontFacing: false,
|
|
|
+ encoder: new TextEncoder(),
|
|
|
+ decoder: new TextDecoder(),
|
|
|
+ defaultDevices: new Map(),
|
|
|
+ bitrateInterval,
|
|
|
+ e2eeKeyProvider: new ExternalE2EEKeyProvider(),
|
|
|
+};
|
|
|
+
|
|
|
+let currentRoom;
|
|
|
+
|
|
|
+let startTime;
|
|
|
+let timer;
|
|
|
+
|
|
|
+function appendLog(...args) {
|
|
|
+ let logger = '';
|
|
|
+ for (let i = 0; i < arguments.length; i += 1) {
|
|
|
+ if (typeof args[i] === 'object') {
|
|
|
+ logger += `${JSON && JSON.stringify ? JSON.stringify(args[i], undefined, 2) : args[i]
|
|
|
+ } `;
|
|
|
+ } else {
|
|
|
+ logger += `${args[i]} `;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(logger);
|
|
|
+}
|
|
|
+
|
|
|
+function getParticipantsAreaElement() {
|
|
|
+ return (
|
|
|
+ window.documentPictureInPicture?.window?.document.querySelector(
|
|
|
+ '#participants-area'
|
|
|
+ ) || document.querySelector('#participants-area')
|
|
|
+ );
|
|
|
+}
|
|
|
+// updates participant UI
|
|
|
+function renderParticipant(participant, remove = false) {
|
|
|
+ const container = getParticipantsAreaElement();
|
|
|
+ if (!container) return;
|
|
|
+ const { identity } = participant;
|
|
|
+ let div = container.querySelector(`#participant-${identity}`);
|
|
|
+ if (!div && !remove) {
|
|
|
+ div = document.createElement('div');
|
|
|
+ div.id = `participant-${identity}`;
|
|
|
+ div.className = 'participant';
|
|
|
+ div.innerHTML = `
|
|
|
+ <video id="video-${identity}"></video>
|
|
|
+ <audio id="audio-${identity}"></audio>
|
|
|
+ <div class="info-bar">
|
|
|
+ <div id="name-${identity}" class="name">
|
|
|
+ </div>
|
|
|
+ <div style="text-align: center;">
|
|
|
+ <span id="codec-${identity}" class="codec">
|
|
|
+ </span>
|
|
|
+ <span id="size-${identity}" class="size">
|
|
|
+ </span>
|
|
|
+ <span id="bitrate-${identity}" class="bitrate">
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="right">
|
|
|
+ <span id="signal-${identity}"></span>
|
|
|
+ <span id="mic-${identity}" class="mic-on"></span>
|
|
|
+ <span id="e2ee-${identity}" class="e2ee-on"></span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ${participant instanceof RemoteParticipant
|
|
|
+ ? `<div class="volume-control">
|
|
|
+ <input id="volume-${identity}" type="range" min="0" max="1" step="0.1" value="1" orient="vertical" />
|
|
|
+ </div>`
|
|
|
+ : `<progress id="local-volume" max="1" value="0" />`
|
|
|
+ }
|
|
|
+
|
|
|
+ `;
|
|
|
+ container.appendChild(div);
|
|
|
+
|
|
|
+ const sizeElm = container.querySelector(`#size-${identity}`);
|
|
|
+ const videoElm = (
|
|
|
+ container.querySelector(`#video-${identity}`)
|
|
|
+ );
|
|
|
+ // videoElm.onresize = () => {
|
|
|
+ // updateVideoSize(videoElm!, sizeElm!);
|
|
|
+ // };
|
|
|
+ }
|
|
|
+ const videoElm = (
|
|
|
+ container.querySelector(`#video-${identity}`)
|
|
|
+ );
|
|
|
+ const audioELm = (
|
|
|
+ container.querySelector(`#audio-${identity}`)
|
|
|
+ );
|
|
|
+ if (remove) {
|
|
|
+ div?.remove();
|
|
|
+ if (videoElm) {
|
|
|
+ videoElm.srcObject = null;
|
|
|
+ videoElm.src = '';
|
|
|
+ }
|
|
|
+ if (audioELm) {
|
|
|
+ audioELm.srcObject = null;
|
|
|
+ audioELm.src = '';
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // update properties
|
|
|
+ const nameElement = container.querySelector(`#name-${identity}`);
|
|
|
+ if (nameElement) {
|
|
|
+ nameElement.innerHTML = participant.identity;
|
|
|
+ if (participant instanceof LocalParticipant) {
|
|
|
+ nameElement.innerHTML += ' (you)';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const micElm = container.querySelector(`#mic-${identity}`);
|
|
|
+ const signalElm = container.querySelector(`#signal-${identity}`);
|
|
|
+ const cameraPub = participant.getTrackPublication(Track.Source.Camera);
|
|
|
+ const micPub = participant.getTrackPublication(Track.Source.Microphone);
|
|
|
+ if (participant.isSpeaking) {
|
|
|
+ div.classList.add('speaking');
|
|
|
+ } else {
|
|
|
+ div.classList.remove('speaking');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (participant instanceof RemoteParticipant) {
|
|
|
+ const volumeSlider = (
|
|
|
+ container.querySelector(`#volume-${identity}`)
|
|
|
+ );
|
|
|
+ volumeSlider.addEventListener('input', (ev) => {
|
|
|
+ participant.setVolume(
|
|
|
+ Number.parseFloat(ev.target.value)
|
|
|
+ );
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ const cameraEnabled =
|
|
|
+ cameraPub && cameraPub.isSubscribed && !cameraPub.isMuted;
|
|
|
+ if (cameraEnabled) {
|
|
|
+ if (participant instanceof LocalParticipant) {
|
|
|
+ // flip
|
|
|
+ videoElm.style.transform = 'scale(-1, 1)';
|
|
|
+ } else if (!cameraPub?.videoTrack?.attachedElements.includes(videoElm)) {
|
|
|
+ const renderStartTime = Date.now();
|
|
|
+ // measure time to render
|
|
|
+ videoElm.onloadeddata = () => {
|
|
|
+ const elapsed = Date.now() - renderStartTime;
|
|
|
+ let fromJoin = 0;
|
|
|
+ if (
|
|
|
+ participant.joinedAt &&
|
|
|
+ participant.joinedAt.getTime() < startTime
|
|
|
+ ) {
|
|
|
+ fromJoin = Date.now() - startTime;
|
|
|
+ }
|
|
|
+ appendLog(
|
|
|
+ `RemoteVideoTrack ${cameraPub?.trackSid} (${videoElm.videoWidth}x${videoElm.videoHeight}) rendered in ${elapsed}ms`,
|
|
|
+ fromJoin > 0 ? `, ${fromJoin}ms from start` : ''
|
|
|
+ );
|
|
|
+ };
|
|
|
+ }
|
|
|
+ cameraPub?.videoTrack?.attach(videoElm);
|
|
|
+ } else {
|
|
|
+ // clear information display
|
|
|
+ const sizeElement = container.querySelector(`#size-${identity}`);
|
|
|
+ if (sizeElement) {
|
|
|
+ sizeElement.innerHTML = '';
|
|
|
+ }
|
|
|
+ if (cameraPub?.videoTrack) {
|
|
|
+ // detach manually whenever possible
|
|
|
+ cameraPub.videoTrack?.detach(videoElm);
|
|
|
+ } else {
|
|
|
+ videoElm.src = '';
|
|
|
+ videoElm.srcObject = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const micEnabled = micPub && micPub.isSubscribed && !micPub.isMuted;
|
|
|
+ if (micEnabled) {
|
|
|
+ if (!(participant instanceof LocalParticipant)) {
|
|
|
+ // don't attach local audio
|
|
|
+ audioELm.onloadeddata = () => {
|
|
|
+ if (
|
|
|
+ participant.joinedAt &&
|
|
|
+ participant.joinedAt.getTime() < startTime
|
|
|
+ ) {
|
|
|
+ const fromJoin = Date.now() - startTime;
|
|
|
+ appendLog(
|
|
|
+ `RemoteAudioTrack ${micPub?.trackSid} played ${fromJoin}ms from start`
|
|
|
+ );
|
|
|
+ }
|
|
|
+ };
|
|
|
+ micPub?.audioTrack?.attach(audioELm);
|
|
|
+ }
|
|
|
+ micElm.className = 'mic-on';
|
|
|
+ micElm.innerHTML = '<i class="fas fa-microphone"></i>';
|
|
|
+ } else {
|
|
|
+ micElm.className = 'mic-off';
|
|
|
+ micElm.innerHTML = '<i class="fas fa-microphone-slash"></i>';
|
|
|
+ }
|
|
|
+
|
|
|
+ const e2eeElm = container.querySelector(`#e2ee-${identity}`);
|
|
|
+ if (participant.isEncrypted) {
|
|
|
+ e2eeElm.className = 'e2ee-on';
|
|
|
+ e2eeElm.innerHTML = '<i class="fas fa-lock"></i>';
|
|
|
+ } else {
|
|
|
+ e2eeElm.className = 'e2ee-off';
|
|
|
+ e2eeElm.innerHTML = '<i class="fas fa-unlock"></i>';
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (participant.connectionQuality) {
|
|
|
+ case ConnectionQuality.Excellent:
|
|
|
+ case ConnectionQuality.Good:
|
|
|
+ case ConnectionQuality.Poor:
|
|
|
+ signalElm.className = `connection-${participant.connectionQuality}`;
|
|
|
+ signalElm.innerHTML = '<i class="fas fa-circle"></i>';
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ signalElm.innerHTML = '';
|
|
|
+ // do nothing
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function participantConnected(participant) {
|
|
|
+ appendLog(
|
|
|
+ 'participant',
|
|
|
+ participant.identity,
|
|
|
+ 'connected',
|
|
|
+ participant.metadata
|
|
|
+ );
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log('tracks', participant.trackPublications);
|
|
|
+ participant
|
|
|
+ .on(ParticipantEvent.TrackMuted, (pub) => {
|
|
|
+ appendLog('track was muted', pub.trackSid, participant.identity);
|
|
|
+ renderParticipant(participant);
|
|
|
+ })
|
|
|
+ .on(ParticipantEvent.TrackUnmuted, (pub) => {
|
|
|
+ appendLog('track was unmuted', pub.trackSid, participant.identity);
|
|
|
+ renderParticipant(participant);
|
|
|
+ })
|
|
|
+ .on(ParticipantEvent.IsSpeakingChanged, () => {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(participant);
|
|
|
+ renderParticipant(participant);
|
|
|
+ })
|
|
|
+ .on(ParticipantEvent.ConnectionQualityChanged, () => {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(participant);
|
|
|
+ renderParticipant(participant);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function participantDisconnected(participant) {
|
|
|
+ appendLog('participant', participant.sid, 'disconnected');
|
|
|
+
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(participant);
|
|
|
+}
|
|
|
+
|
|
|
+function handleRoomDisconnect(reason) {
|
|
|
+ if (!currentRoom) return;
|
|
|
+ appendLog('disconnected from room', { reason });
|
|
|
+ currentRoom = undefined;
|
|
|
+ window.currentRoom = undefined;
|
|
|
+}
|
|
|
+
|
|
|
+const elementMapping = {
|
|
|
+ // 'video-input': 'videoinput',
|
|
|
+ 'audio-input': 'audioinput',
|
|
|
+ 'audio-output': 'audiooutput',
|
|
|
+};
|
|
|
+
|
|
|
+async function handleDevicesChanged() {
|
|
|
+ Promise.all(
|
|
|
+ Object.keys(elementMapping).map(async (id) => {
|
|
|
+ const kind = elementMapping[id];
|
|
|
+ if (!kind) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const devices = await Room.getLocalDevices(kind);
|
|
|
+ // eslint-disable-next-line no-restricted-syntax
|
|
|
+ for (const device of devices) {
|
|
|
+ if (device.deviceId === state.defaultDevices.get(kind)) {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(device.label);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function renderScreenShare(room) {
|
|
|
+ // const div = $('screenshare-area')!;
|
|
|
+ if (room.state !== ConnectionState.Connected) {
|
|
|
+ // div.style.display = 'none';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let participant;
|
|
|
+ let screenSharePub =
|
|
|
+ room.localParticipant.getTrackPublication(Track.Source.ScreenShare);
|
|
|
+ let screenShareAudioPub;
|
|
|
+ if (!screenSharePub) {
|
|
|
+ room.remoteParticipants.forEach((p) => {
|
|
|
+ if (screenSharePub) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ participant = p;
|
|
|
+ const pub = p.getTrackPublication(Track.Source.ScreenShare);
|
|
|
+ if (pub?.isSubscribed) {
|
|
|
+ screenSharePub = pub;
|
|
|
+ }
|
|
|
+ const audioPub = p.getTrackPublication(Track.Source.ScreenShareAudio);
|
|
|
+ if (audioPub?.isSubscribed) {
|
|
|
+ screenShareAudioPub = audioPub;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ participant = room.localParticipant;
|
|
|
+ }
|
|
|
+
|
|
|
+ // if (screenSharePub && participant) {
|
|
|
+ // // div.style.display = 'block';
|
|
|
+ // // const videoElm = $('screenshare-video');
|
|
|
+ // screenSharePub.videoTrack?.attach(videoElm);
|
|
|
+ // if (screenShareAudioPub) {
|
|
|
+ // screenShareAudioPub.audioTrack?.attach(videoElm);
|
|
|
+ // }
|
|
|
+ // videoElm.onresize = () => {
|
|
|
+ // updateVideoSize(videoElm, <HTMLSpanElement>$('screenshare-resolution'));
|
|
|
+ // };
|
|
|
+ // const infoElm = $('screenshare-info')!;
|
|
|
+ // infoElm.innerHTML = `Screenshare from ${participant.identity}`;
|
|
|
+ // } else {
|
|
|
+ // div.style.display = 'none';
|
|
|
+ // }
|
|
|
+}
|
|
|
+
|
|
|
+const connectToRoom = async (
|
|
|
+ url,
|
|
|
+ token,
|
|
|
+ roomOptions,
|
|
|
+ connectOptions,
|
|
|
+ shouldPublish
|
|
|
+) => {
|
|
|
+ const room = new Room(roomOptions);
|
|
|
+
|
|
|
+ startTime = Date.now();
|
|
|
+ await room.prepareConnection(url, token);
|
|
|
+ const prewarmTime = Date.now() - startTime;
|
|
|
+ appendLog(`prewarmed connection in ${prewarmTime}ms`);
|
|
|
+
|
|
|
+ room
|
|
|
+ .on(RoomEvent.ParticipantConnected, participantConnected)
|
|
|
+ .on(RoomEvent.ParticipantDisconnected, participantDisconnected)
|
|
|
+ .on(RoomEvent.Disconnected, handleRoomDisconnect)
|
|
|
+ .on(RoomEvent.Reconnecting, () => appendLog('Reconnecting to room'))
|
|
|
+ .on(RoomEvent.Reconnected, async () => {
|
|
|
+ appendLog(
|
|
|
+ 'Successfully reconnected. server',
|
|
|
+ await room.engine.getConnectedServerAddress()
|
|
|
+ );
|
|
|
+ })
|
|
|
+ .on(RoomEvent.LocalTrackPublished, (pub) => {
|
|
|
+ const track = pub.track;
|
|
|
+
|
|
|
+ if (track instanceof LocalAudioTrack) {
|
|
|
+ const { calculateVolume } = createAudioAnalyser(track);
|
|
|
+ // setInterval(() => {
|
|
|
+ // $('local-volume')?.setAttribute(
|
|
|
+ // 'value',
|
|
|
+ // calculateVolume().toFixed(4)
|
|
|
+ // );
|
|
|
+ // }, 200);
|
|
|
+ }
|
|
|
+ renderScreenShare(room);
|
|
|
+ })
|
|
|
+ .on(RoomEvent.LocalTrackUnpublished, () => {
|
|
|
+ renderScreenShare(room);
|
|
|
+ })
|
|
|
+ .on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
|
+ appendLog('new metadata for room', metadata);
|
|
|
+ })
|
|
|
+ .on(RoomEvent.MediaDevicesChanged, handleDevicesChanged)
|
|
|
+ .on(RoomEvent.MediaDevicesError, (e) => {
|
|
|
+ const failure = MediaDeviceFailure.getFailure(e);
|
|
|
+ appendLog('media device failure', failure);
|
|
|
+ })
|
|
|
+ .on(
|
|
|
+ RoomEvent.ConnectionQualityChanged,
|
|
|
+ (quality, participant) => {
|
|
|
+ appendLog('connection quality changed', participant?.identity, quality);
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .on(RoomEvent.TrackSubscribed, (track, pub, participant) => {
|
|
|
+ appendLog('subscribed to track', pub.trackSid, participant.identity);
|
|
|
+ renderParticipant(participant);
|
|
|
+ renderScreenShare(room);
|
|
|
+ })
|
|
|
+ .on(RoomEvent.TrackUnsubscribed, (_, pub, participant) => {
|
|
|
+ appendLog('unsubscribed from track', pub.trackSid);
|
|
|
+ renderParticipant(participant);
|
|
|
+ renderScreenShare(room);
|
|
|
+ })
|
|
|
+ .on(RoomEvent.SignalConnected, async () => {
|
|
|
+ const signalConnectionTime = Date.now() - startTime;
|
|
|
+ appendLog(`signal connection established in ${signalConnectionTime}ms`);
|
|
|
+ // speed up publishing by starting to publish before it's fully connected
|
|
|
+ // publishing is accepted as soon as signal connection has established
|
|
|
+ if (shouldPublish) {
|
|
|
+ await room.localParticipant.enableCameraAndMicrophone();
|
|
|
+ appendLog(`tracks published in ${Date.now() - startTime}ms`);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .on(RoomEvent.TrackStreamStateChanged, (pub, streamState, participant) => {
|
|
|
+ appendLog(
|
|
|
+ `stream state changed for ${pub.trackSid} (${participant.identity
|
|
|
+ }) to ${streamState.toString()}`
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ // read and set current key from input
|
|
|
+ const cryptoKey = '';
|
|
|
+ const e2eChack = false;
|
|
|
+ state.e2eeKeyProvider.setKey(cryptoKey);
|
|
|
+ if (e2eChack) {
|
|
|
+ await room.setE2EEEnabled(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ await room.connect(url, token, connectOptions);
|
|
|
+ const elapsed = Date.now() - startTime;
|
|
|
+ appendLog(
|
|
|
+ `successfully connected to ${room.name} in ${Math.round(elapsed)}ms`,
|
|
|
+ await room.engine.getConnectedServerAddress()
|
|
|
+ );
|
|
|
+ } catch (error) {
|
|
|
+ let message = error;
|
|
|
+ if (error.message) {
|
|
|
+ message = error.message;
|
|
|
+ }
|
|
|
+ appendLog('could not connect:', message);
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+ currentRoom = room;
|
|
|
+ window.currentRoom = room;
|
|
|
+
|
|
|
+ room.remoteParticipants.forEach((participant) => {
|
|
|
+ participantConnected(participant);
|
|
|
+ });
|
|
|
+ participantConnected(room.localParticipant);
|
|
|
+ // 禁用视频
|
|
|
+ await currentRoom.localParticipant.setCameraEnabled(false);
|
|
|
+ currentRoom?.startAudio();
|
|
|
+
|
|
|
+ return room;
|
|
|
+};
|
|
|
+
|
|
|
+function renderBitrate() {
|
|
|
+ if (!currentRoom || currentRoom.state !== ConnectionState.Connected) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const participants = [
|
|
|
+ ...currentRoom.remoteParticipants.values(),
|
|
|
+ ];
|
|
|
+ participants.push(currentRoom.localParticipant);
|
|
|
+
|
|
|
+ // eslint-disable-next-line no-restricted-syntax
|
|
|
+ for (const p of participants) {
|
|
|
+ let totalBitrate = 0;
|
|
|
+ // eslint-disable-next-line no-restricted-syntax
|
|
|
+ for (const t of p.trackPublications.values()) {
|
|
|
+ if (t.track) {
|
|
|
+ totalBitrate += t.track.currentBitrate;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (t.source === Track.Source.Camera) {
|
|
|
+ if (t.videoTrack instanceof RemoteVideoTrack) {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(t.videoTrack.getDecoderImplementation() ?? '');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let displayText = '';
|
|
|
+ if (totalBitrate > 0) {
|
|
|
+ displayText = `${Math.round(totalBitrate / 1024).toLocaleString()} kbps`;
|
|
|
+ }
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(displayText);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const connectWithFormInput = async (
|
|
|
+ url,
|
|
|
+ token,
|
|
|
+ disabled
|
|
|
+) => {
|
|
|
+ const simulcast = true; // 同步播出
|
|
|
+ const dynacast = true; // Dynacast
|
|
|
+ const forceTURN = false; // 强制转向
|
|
|
+ const adaptiveStream = true; // 自适应流
|
|
|
+ const shouldPublish = true; // 发布
|
|
|
+ const preferredCodec = '';
|
|
|
+ const scalabilityMode = '';
|
|
|
+ const cryptoKey = '';
|
|
|
+ const autoSubscribe = true; // 自动订阅
|
|
|
+ const e2eeEnabled = false;
|
|
|
+ const audioOutputId = ''; // E2EE密钥
|
|
|
+ setLogLevel(LogLevel.debug);
|
|
|
+
|
|
|
+ const roomOpts = {
|
|
|
+ adaptiveStream,
|
|
|
+ dynacast,
|
|
|
+ audioOutput: {
|
|
|
+ deviceId: audioOutputId,
|
|
|
+ },
|
|
|
+ publishDefaults: {
|
|
|
+ simulcast,
|
|
|
+ videoSimulcastLayers: [VideoPresets.h90, VideoPresets.h216],
|
|
|
+ videoCodec: preferredCodec || 'vp8',
|
|
|
+ dtx: true,
|
|
|
+ red: true,
|
|
|
+ forceStereo: false,
|
|
|
+ screenShareEncoding: ScreenSharePresets.h1080fps30.encoding,
|
|
|
+ scalabilityMode: 'L3T3_KEY',
|
|
|
+ },
|
|
|
+ videoCaptureDefaults: {
|
|
|
+ resolution: VideoPresets.h720.resolution,
|
|
|
+ },
|
|
|
+ e2ee: e2eeEnabled
|
|
|
+ ? { keyProvider: state.e2eeKeyProvider, worker: new E2EEWorker() }
|
|
|
+ : undefined,
|
|
|
+ };
|
|
|
+ if (
|
|
|
+ roomOpts.publishDefaults?.videoCodec === 'av1' ||
|
|
|
+ roomOpts.publishDefaults?.videoCodec === 'vp9'
|
|
|
+ ) {
|
|
|
+ roomOpts.publishDefaults.backupCodec = true;
|
|
|
+ if (scalabilityMode !== '') {
|
|
|
+ roomOpts.publishDefaults.scalabilityMode =
|
|
|
+ scalabilityMode;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const connectOpts = {
|
|
|
+ autoSubscribe,
|
|
|
+ };
|
|
|
+ if (forceTURN) {
|
|
|
+ connectOpts.rtcConfig = {
|
|
|
+ iceTransportPolicy: 'relay',
|
|
|
+ };
|
|
|
+ }
|
|
|
+ await connectToRoom(url, token, roomOpts, connectOpts, shouldPublish);
|
|
|
+ disabled.value = 'open';
|
|
|
+ timer = setTimeout(handleDevicesChanged, 100);
|
|
|
+
|
|
|
+ state.bitrateInterval = setInterval(renderBitrate, 1000);
|
|
|
+};
|
|
|
+
|
|
|
+const disconnectRoom = (disabled) => {
|
|
|
+ if (currentRoom) {
|
|
|
+ currentRoom.disconnect();
|
|
|
+ }
|
|
|
+ if (state.bitrateInterval) {
|
|
|
+ clearInterval(state.bitrateInterval);
|
|
|
+ }
|
|
|
+ disabled.value = 'close';
|
|
|
+};
|
|
|
+
|
|
|
+async function acquireDeviceList() {
|
|
|
+ handleDevicesChanged();
|
|
|
+}
|
|
|
+
|
|
|
+acquireDeviceList();
|
|
|
+
|
|
|
+export const IntercomFn = async (disabled, guid) => {
|
|
|
+ if (disabled.value === 'open') {
|
|
|
+ disconnectRoom(disabled);
|
|
|
+ clearInterval(timer);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ disabled.value = 'loading';
|
|
|
+ const fingerprint = await getFingerprint();
|
|
|
+ const token = await getToken({ Room: guid, Identity: fingerprint });
|
|
|
+ connectWithFormInput(
|
|
|
+ `wss://${import.meta.env.VITE_API_INTERCOM_IP}`,
|
|
|
+ token,
|
|
|
+ disabled
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default null;
|