基于声网实现音视频会议

458 阅读3分钟

前言

实现音视频会议和屏幕共享功能

前期准备

注册声网账号,申请声网APPID、临时Token ,详见开始使用声网平台

如果你还没有声网账号,可以通过这里免费注册,每个账户每月都有10000分钟免费额度。如果是个人学习/调试,时长完全够用。

安装依赖

npm i agora-rtc-sdk-ng

封装调用类

创建 live.ts 实现自主上传音频流

import AgoraRtc, { IAgoraRTCClient, IMicrophoneAudioTrack, ICameraVideoTrack, ILocalVideoTrack, ClientRole } from 'agora-rtc-sdk-ng'

export interface TJcLive {
  [key: string]: any
  client: any
  localAudioTrack: any
  localVideoTrack: any
  screenVideoTrack: any
  join(param: TLiveJoinParams): Promise<void>
  createTracks(video: boolean, audio: boolean): Promise<void>
  createAudioTrack(): Promise<void>
  createVideoTrack(): Promise<void>
  createScreenVideoTrack(cd: Function): Promise<void>
  closeScreenVideoTrack(): Promise<void>
  closeAorV(type: MediaType): void
  setVolume(number: number): void
  leave(): void
  onCameraChanged(): Promise<void>
}

export interface TLiveOptions {
  userId: string
}

export interface TLiveJoinParams {
  channel: string //频道号
  role?: ClientRole //用户角色
  audio: boolean
  video: boolean
}

export enum MediaType {
  AUDIO = 'audio' /*音频*/,
  VIDEO = 'video' /*视频*/
}



const getToken = ({channelName, userId}:{channelName: string, userId:string})=>{
  // 通过接口获取token
  return 'token'
}

const appid = 'appid' // 注册声网后在控制台获取


export class JcLive implements TJcLive {
  /**
   * 本地客服端
   */
  client: IAgoraRTCClient

  /**
   * 本地音频流
   */
  localAudioTrack: IMicrophoneAudioTrack

  /**
   * 本地视频流
   */
  localVideoTrack: ICameraVideoTrack

  /**
   * 本地投屏流
   */
  screenVideoTrack: ILocalVideoTrack
  index: number

  constructor(private options: TLiveOptions) {
    this.client = AgoraRtc.createClient({ mode: 'live', codec: 'vp8' })
  }

  /**
   * 加入频道
   */
  async join({
    role = 'host',
    channel,
    audio,
    video,
  }: TLiveJoinParams) {
    // 声网设置角色 主播人or观众
    this.client.setClientRole(role)
    // 获取 声网 token 邀请会议时发消息需要带上token 所以存在先或者到token 的情况
    const token = await getToken({ channelName: channel, userId: this.options.userId })
    const uid = await this.client.join(appid, channel, token, this.options.userId)
    //如果角色是主播 、则创建本地流
    if (role === 'host') {
      this.createTracks(video, audio)
    }
  }

  /**
   * 创建本地音视频流
   * @param video 视频
   * @param audio 音频
   */
  async createTracks(video: boolean, audio: boolean) {
    if (video) {
      this.createVideoTrack()
    }
    if (audio) {
      this.createAudioTrack()
    }
  }

  /**
   * 创建本地音频流
   */
  async createAudioTrack() {
    if (this.localAudioTrack) {
      this.localAudioTrack.close()
    }
    // 创建本地音频流
    this.localAudioTrack = await AgoraRtc.createMicrophoneAudioTrack()
    // 推送
    this.client.publish(this.localAudioTrack)
  }

  /**
   * 创建本地视频流
   */
  async createVideoTrack() {
    if (this.localVideoTrack) {
      this.localVideoTrack.close()
    }
    this.localVideoTrack = await AgoraRtc.createCameraVideoTrack()
    //设置分辨率 720p_1
    await this.localVideoTrack.setEncoderConfiguration('720p_1')

    this.client.publish(this.localVideoTrack)

    //播放本地视频 this.options.userId 是用户id 实际入参是dom id 
    this.localVideoTrack.play(this.options.userId, { fit: 'contain' })
  }

  /**
   * 创建分享屏幕
   */
  async createScreenVideoTrack(cd: Function) {
    try {
      this.screenVideoTrack = await AgoraRtc.createScreenVideoTrack({ encoderConfig: '720p_1' }, 'disable')
      this.client.publish(this.screenVideoTrack)
      this.screenVideoTrack.play(this.options.userId)
      //  分享屏幕时点击取消分享时的回调
      this.screenVideoTrack.once('track-ended', () => {
        cd()
      })
    } catch (error) {
      console.log(error, '获取权限失败')
      cd()
    }
  }

  /**
   * 关闭投屏
   */
  async closeScreenVideoTrack() {
    if (this.screenVideoTrack) {
      this.screenVideoTrack.close()
      this.client.unpublish(this.screenVideoTrack)
    }
  }

  /**
   * 关闭音频或者视频
   */
  closeAorV(type: MediaType) {
    if (type === MediaType.VIDEO) {
      if (this.localVideoTrack) {
        this.localVideoTrack.close()
        this.client.unpublish(this.localVideoTrack)
      }
    } else if (type === MediaType.AUDIO) {
      if (this.localAudioTrack) {
        this.localAudioTrack.close()
        this.client.unpublish(this.localAudioTrack)
      }
    }
  }

  // 设置音量
  setVolume(number: number) {
    this.localAudioTrack.setVolume(number)
  }
  /**
   * 离开频道
   */
  leave() {
    if (this.localAudioTrack) {
      this.localAudioTrack.close()
    }
    if (this.localVideoTrack) {
      this.localVideoTrack.close()
    }
    if (this.screenVideoTrack) {
      this.screenVideoTrack.close()
    }
    this.client.leave()
  }

  // 切换前后摄像头
  onCameraChanged = async () => {
    const videoList = await AgoraRtc.getCameras()
    this.index = this.index === 0 ? 1 : 0
    if (videoList && videoList.length > 1) {
      this.localVideoTrack
        .setDevice(videoList[this.index === 1 ? videoList.length - 1 : this.index].deviceId)
        .then(() => {
          console.log('set device success')
        })
        .catch(e => {
          console.log('set device error', e)
        })
    }
  }
}

功能集成:音频、视频、屏幕分享、手动关闭屏幕分享、关闭音频或者视频、设置音量、离开房间、切换前后摄像头

创建 event.ts 实现接受远程房间成员的流

import { IAgoraRTCClient } from 'agora-rtc-sdk-ng'

export class JcLiveEvents {
  constructor(private client: IAgoraRTCClient) {}
  init() {
    //监听用户流发布
    this.client.on('user-published', async (user: any, mediaType: any) => {
      //订阅远程流  本地流 是否能走到这个监听???
      await this.client.subscribe(user, mediaType)
      if (mediaType === 'audio') {
        user.audioTrack?.play()
      } else if (mediaType === 'video') {
        const userId = user.uid.toString()
        user.videoTrack?.play(userId, { fit: 'contain' })
      }
    })

    //监听远程流卸载
    this.client.on('user-unpublished', (user: any, mediaType: any) => {
      //TODO 卸载 
    })

    //监听用户离开
    this.client.on('user-left', (user: any, reason: any) => {
      //TODO 卸载
    })

    //监听当前用户网络质量
    this.client.on('network-quality', (stats: any) => {
      if (stats.downlinkNetworkQuality > 3 || stats.uplinkNetworkQuality > 3) {
        console.log('network-quality 当前网络质量不好')
        //TODO
      }
    })
  }

  getPacketLossRate() {
    const obj = this.client.getRemoteVideoStats()
    for (const key in obj) {
      if (obj[key].currentPacketLossRate > 40) {
        console.log('getPacketLossRate', key, '丢包率大')
      }
    }
    //TODO 回调 返回丢包大的远程流id
  }
}

至此,封装的两个类已经完成,接下来使用

  impor { JcLive } from './live'
  impor { JcLiveEvents } from './event'
  const live = new JcLive({ userId:'userId'}) // 创建音视频实例
  live.join({ channel:'随机生成', video: true, audio: true }) // 加入音视频
  const liveEvents = new JcLiveEvents(live.client) // 创建音视频件监听实例
!注意 页面中的视频流的播放都是通过获取元素id来播放的 <div id="userid"></div>