前言
实现音视频会议和屏幕共享功能
前期准备
注册声网账号,申请声网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>