123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- import type { Ref } from 'vue';
- // import { Message as $msg } from '@arco-design/web-vue';
- import { getToken } from '@/api/system';
- import E2EEWorker from 'livekit-client/e2ee-worker?worker';
- import type {
- // ChatMessage,
- RoomConnectOptions,
- RoomOptions,
- // ScalabilityMode,
- // SimulationScenario,
- // VideoCaptureOptions,
- // VideoCodec,
- } from 'livekit-client';
- 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<MediaDeviceKind, string>(),
- bitrateInterval: undefined as any,
- e2eeKeyProvider: new ExternalE2EEKeyProvider(),
- };
- let currentRoom: Room | undefined;
- let startTime: number;
- function appendLog(...args: any[]) {
- 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 participantConnected(participant: Participant) {
- appendLog(
- 'participant',
- participant.identity,
- 'connected',
- participant.metadata
- );
- // eslint-disable-next-line no-console
- console.log('tracks', participant.trackPublications);
- participant
- .on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
- appendLog('track was muted', pub.trackSid, participant.identity);
- })
- .on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
- appendLog('track was unmuted', pub.trackSid, participant.identity);
- })
- .on(ParticipantEvent.IsSpeakingChanged, () => {
- // eslint-disable-next-line no-console
- console.log(participant);
- })
- .on(ParticipantEvent.ConnectionQualityChanged, () => {
- // eslint-disable-next-line no-console
- console.log(participant);
- });
- }
- function participantDisconnected(participant: RemoteParticipant) {
- appendLog('participant', participant.sid, 'disconnected');
- // eslint-disable-next-line no-console
- console.log(participant);
- }
- function handleRoomDisconnect(reason?: DisconnectReason) {
- if (!currentRoom) return;
- appendLog('disconnected from room', { reason });
- currentRoom = undefined;
- (window as { [k: string]: any }).currentRoom = undefined;
- }
- const connectToRoom = async (
- url: string,
- token: string,
- roomOptions?: RoomOptions,
- connectOptions?: RoomConnectOptions,
- shouldPublish?: boolean
- ): Promise<Room | undefined> => {
- 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 as LocalAudioTrack;
- if (track instanceof LocalAudioTrack) {
- // const { calculateVolume } = createAudioAnalyser(track);
- // setInterval(() => {
- // $('local-volume')?.setAttribute(
- // 'value',
- // calculateVolume().toFixed(4)
- // );
- // }, 200);
- }
- })
- .on(RoomEvent.RoomMetadataChanged, (metadata) => {
- appendLog('new metadata for room', metadata);
- })
- .on(RoomEvent.MediaDevicesError, (e: Error) => {
- const failure = MediaDeviceFailure.getFailure(e);
- appendLog('media device failure', failure);
- })
- .on(
- RoomEvent.ConnectionQualityChanged,
- (quality: ConnectionQuality, participant?: Participant) => {
- appendLog('connection quality changed', participant?.identity, quality);
- }
- )
- .on(RoomEvent.TrackSubscribed, (track, pub, participant) => {
- appendLog('subscribed to track', pub.trackSid, participant.identity);
- })
- .on(RoomEvent.TrackUnsubscribed, (_, pub) => {
- appendLog('unsubscribed from track', pub.trackSid);
- })
- .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: any) {
- let message: any = error;
- if (error.message) {
- message = error.message;
- }
- appendLog('could not connect:', message);
- return undefined;
- }
- currentRoom = room;
- (window as { [k: string]: any }).currentRoom = room;
- room.remoteParticipants.forEach((participant) => {
- participantConnected(participant);
- });
- participantConnected(room.localParticipant);
- return room;
- };
- function renderBitrate() {
- if (!currentRoom || currentRoom.state !== ConnectionState.Connected) {
- return;
- }
- const participants: Participant[] = [
- ...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: any,
- token: any,
- disabled: Ref<string>
- ) => {
- const simulcast = true; // 同步播出
- const dynacast = true; // Dynacast
- const forceTURN = false; // 强制转向
- const adaptiveStream = true; // 自适应流
- const shouldPublish = true; // 发布
- // const preferredCodec = '' as VideoCodec;
- // const scalabilityMode = '';
- // const cryptoKey = '';
- const autoSubscribe = true; // 自动订阅
- const e2eeEnabled = false;
- const audioOutputId = ''; // E2EE密钥
- setLogLevel(LogLevel.debug);
- const roomOpts: RoomOptions = {
- 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 as ScalabilityMode;
- // }
- // }
- const connectOpts: RoomConnectOptions = {
- autoSubscribe,
- };
- if (forceTURN) {
- connectOpts.rtcConfig = {
- iceTransportPolicy: 'relay',
- };
- }
- await connectToRoom(url, token, roomOpts, connectOpts, shouldPublish);
- disabled.value = 'open';
- state.bitrateInterval = setInterval(renderBitrate, 1000);
- };
- const disconnectRoom = (disabled: Ref<string>) => {
- if (currentRoom) {
- currentRoom.disconnect();
- }
- if (state.bitrateInterval) {
- clearInterval(state.bitrateInterval);
- }
- disabled.value = 'close';
- };
- export const IntercomFn = async (disabled: Ref<string>, guid: string) => {
- if (disabled.value === 'open') {
- disconnectRoom(disabled);
- 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;
|