前言:虽然说的有点乱,我觉得有必要说一下什么是音视频与整体开发需要些什么东西,因为当时我也是不知道这是啥?
.我的理解音视频就是类似微信视频通话的东西,以自己的角度来看,我与好友建立视频的步骤简单来讲就是我把我的视频信息推送到微信,微信处理发给我好友,然后我再将好友的视频信息拉下来,我们就可以实时视频了.
开发角度看需要的主要用户信息就是:房间号
/adkAppId
(腾讯云生成后一般写死)/userSig
(签名,后端返回)/userId
(手机号/账号之类)
开发流程以我们的业务来说有两条线,
- 一是登录页收集需要的信息>实时刷新页面看有无房间>无房间可以创建房间,后台生成房间号进入房间>获取后台签名接口>向腾讯云推拉流>获取后台房主/开房人的信息以便可以向后台发送踢人操作等等>静音关视频退房间等零碎操作,就看各自业务
- 二是加入已有房间的,也是需要登录拿信息>进房间>推拉信息
如果按照腾讯云demo的话是有人退出,后面的用户会往上排,但如果你跟我一样需要写死例如5个位置,并且房主在第一位放大的.可以借鉴如下操作:1.写好固定的四位远端对象,并给一个常量判断有无人,例如
isEmpty: true
,在接收完远端后就遍历判断有位置就储存数据,并改为false
.2.接收完远端与本地流后遍历匹配与房主的信息,例如我是拿userId匹配的,如果符合,给房主加样式order:-999就会排到第一位了.
一.在腾讯云申请账号,拿到adkAppID
二.下载腾讯云demo
三.分析demo
- 跑通Demo与快速集成有官方介绍,以下记录自己理解.
1.目录分析
2.多人会议分析
- metting>room文件夹
- 里面主要有
<live-pusher>
与<live-player>
两个组件,官网也有讲,这个是基于微信原生组件开发的,所以在微信开放文档可以找到相应解释.<live-pusher>
推流,指的是把采集阶段封包好的内容传输到服务器的过程。<live-player>
拉流是指服务器已有直播内容,用指定地址进行拉取的过程。- 用官方例子来解释就是,比方是一个老师与三名学生的教学直播,你是这名老师,那么推流会把你的视频信息通过url推送(
this.TRTC.getPusherInstance().start()
)服务器,同时会将三名学生的数据拉下来.同样要是你是学生,那你也会推送自己的数据,拉下一名老师与两位同学的信息.
room.wxml
<view class="template-grid"><view class="column-layout"><view class="column-1"><view id="grid-container-id" class="grid-container"><view class="view-container pusher-container {{playerList.length===0? 'fullscreen':''}} {{playerList.length===1? 'one-player':''}}"><!-- 1.传入一个推流地址,然后调用start --><!-- 2.推流展示自己 --><!-- 属性:url:推流地址。目前仅支持 rtmp 格式mode:SD(标清), HD(高清), FHD(超清), RTC(实时通话)autopush:自动推流enable-camera:开启摄像头enable-mic:开启或关闭麦克风muted:是否静音enable-agc:是否开启音频自动增益enable-ans:是否开启音频噪声抑制auto-focus:自动聚集zoom:调整焦距min-bitrate:最小码率max-bitrate:最大码率video-width:上推的视频流的分辨率宽度video-height:上推的视频流的分辨率高度beauty:美颜,取值范围 0-9 ,0 表示关闭whiteness:美白,取值范围 0-9 ,0 表示关闭orientation:画面方向aspect:宽高比,可选值有 3:4, 9:16device-position:前置或后置,值为front, backremote-mirror:同 mirror 属性,后续 mirror 将废弃local-mirror:控制本地预览画面是否镜像background-mute:进入后台时是否静音(已废弃,默认退后台静音)audio-quality:高音质(48KHz)或低音质(16KHz),值为high, lowaudio-volume-type:音量类型audio-reverb-type:音频混响类型waiting-image:进入后台时推流的等待画面beauty-style:设置美颜类型filter:设置色彩滤镜bindstatechange:状态变化事件,detail = {code}bindnetstatus:网络状态通知,detail = {info}binderror:渲染错误事件,detail = {errMsg, errCode}bindbgmstart:背景音开始播放时触发bindbgmprogress:背景音进度变化时触发,detail = {progress, duration}bindbgmcomplete:背景音播放完成时触发bindaudiovolumenotify:返回麦克风采集的音量大小--><live-pusherclass="pusher"url="{{pusher.url}}"mode="{{pusher.mode}}"autopush="{{pusher.autopush}}"enable-camera="{{pusher.enableCamera}}"enable-mic="{{pusher.enableMic}}"muted="{{!pusher.enableMic}}"enable-agc="{{pusher.enableAgc}}"enable-ans="{{pusher.enableAns}}"enable-ear-monitor="{{pusher.enableEarMonitor}}"auto-focus="{{pusher.enableAutoFocus}}"zoom="{{pusher.enableZoom}}"min-bitrate="{{pusher.minBitrate}}"max-bitrate="{{pusher.maxBitrate}}"video-width="{{pusher.videoWidth}}"video-height="{{pusher.videoHeight}}"beauty="{{pusher.beautyLevel}}"whiteness="{{pusher.whitenessLevel}}"orientation="{{pusher.videoOrientation}}"aspect="{{pusher.videoAspect}}"device-position="{{pusher.frontCamera}}"remote-mirror="{{pusher.enableRemoteMirror}}"local-mirror="{{pusher.localMirror}}"background-mute="{{pusher.enableBackgroundMute}}"audio-quality="{{pusher.audioQuality}}"audio-volume-type="{{pusher.audioVolumeType}}"audio-reverb-type="{{pusher.audioReverbType}}"waiting-image="{{pusher.waitingImage}}"debug="{{debug}}"beauty-style="{{pusher.beautyStyle}}"filter="{{pusher.filter}}"bindstatechange="_pusherStateChangeHandler"bindnetstatus="_pusherNetStatusHandler"binderror="_pusherErrorHandler"bindbgmstart="_pusherBGMStartHandler"bindbgmprogress="_pusherBGMProgressHandler"bindbgmcomplete="_pusherBGMCompleteHandler"bindaudiovolumenotify="_pusherAudioVolumeNotify"/><view class="no-video" wx:if="{{!pusher.enableCamera}}"><image class="image" src="../../../static/images/mute-camera-white.png"></image></view><!-- 左下角话筒样式 --><view class="no-audio" wx:if="{{!pusher.enableMic}}"><image class="image" src="../../../static/images/mute-mic-white.png"></image></view><view class="audio-volume" wx:if="{{pusher.enableMic}}"><image class="image" src="../../../static/images/micro-open.png"></image><view class="audio-active" style="height:{{pusher.volume}}%"><image class="image" src="../../../static/images/audio-active.png"></image></view></view></view><!-- playerList决定房间有几个人的数组 --><view wx:for="{{playerList}}"wx:key="streamID"class="view-container player-container {{playerList.length===1? 'one-player':''}}"id="{{'player-'+item.streamID}}"data-userid="{{item.userID}}"data-streamtype="{{item.streamType}}"bindtap="_doubleTabToggleFullscreen"><!-- 1.可以设置autoplay,检测到有推流链接就自动拉流 --><!-- 2.拉流展示别人 --><!-- 属性:data-streamid:状态机需要区分标签绑定的 handler 是从哪一个 player 调用的src:音视频地址。目前仅支持 flv, rtmp 格式mode:live(直播),RTC(实时通话,该模式时延更低)autoplay:自动播放mute-audio:应该是静音mute-video:应该是静音orientation:画面方向,vertical(竖直),horizontal(水平)object-fit:填充模式,可选值有 contain(图像长边填满屏幕,短边区域会被填充⿊⾊),fillCrop(图像铺满屏幕,超出显示区域的部分将被截掉)background-mute:进入后台时是否静音(已废弃,默认退后台静音)min-cache:最小缓冲区,单位s(RTC 模式推荐 0.2s)max-cache:最大缓冲区sound-mode:声音输出方式,speaker(扬声器),ear(听筒)auto-pause-if-navigate:当跳转到本小程序的其他页面时,是否自动暂停本页面的实时音视频播放auto-pause-if-open-native:当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放bindstatechange:播放状态变化事件,detail = {code}bindfullscreenchange:全屏变化事件,detail = {direction, fullScreen}bindnetstatus:网络状态通知,detail = {info}bindaudiovolumenotify:播放音量大小通知,detail = {}--><live-playerclass="player"id="{{item.id}}"data-userid="{{item.userID}}"data-streamid="{{item.streamID}}"data-streamtype="{{item.streamType}}"src= "{{item.src}}"mode= "RTC"autoplay= "{{item.autoplay}}"mute-audio= "{{item.muteAudio}}"mute-video= "{{item.muteVideo}}"orientation= "{{item.orientation}}"object-fit= "{{item.objectFit}}"background-mute= "{{item.enableBackgroundMute}}"min-cache= "{{item.minCache}}"max-cache= "{{item.maxCache}}"sound-mode= "{{item.soundMode}}"enable-recv-message= "{{item.enableRecvMessage}}"auto-pause-if-navigate= "{{item.autoPauseIfNavigate}}"auto-pause-if-open-native= "{{item.autoPauseIfOpenNative}}"debug="{{debug}}"bindstatechange="_playerStateChange"bindfullscreenchange="_playerFullscreenChange"bindnetstatus="_playerNetStatus"bindaudiovolumenotify="_playerAudioVolumeNotify"/><view class="no-video" wx:if="{{item.muteVideo}}"><image class="image" src="../../../static/images/display-pause-white.png"></image><view class="text"><p>{{item.userID}}</p></view></view><view class="no-video" wx:if="{{!item.hasVideo && !item.muteVideo}}"><image class="image" src="../../../static/images/mute-camera-white.png"></image><view class="text"><p>{{item.userID}}</p></view><view class="text"><p>对方摄像头未打开</p></view></view><view class="no-audio" wx:if="{{!item.hasAudio}}"><image class="image" src="../../../static/images/mute-mic-white.png"></image></view><view class="audio-volume" wx:if="{{item.hasAudio}}"><image class="image" src="../../../static/images/micro-open.png"></image><view class="audio-active" style="height:{{item.volume}}%"><image class="image" src="../../../static/images/audio-active.png"></image></view></view><view class="sub-box"><image class="audio-image" bindtap="_mutePlayerAudio" data-value="{{item}}" src="{{item.muteAudio? '../../../static/images/mute-mic-white.png': '../../../static/images/micro-open.png'}} "></image><image class="audio-image" bindtap="_mutePlayerVideo" data-value="{{item}}" src="{{item.muteVideo? '../../../static/images/mute-camera-white.png': '../../../static/images/camera.png'}} "></image></view></view></view></view><!-- 底部工具栏 --><view class="bottom-box"><view class="bottom-btns"><view class="btn-normal" bindtap="_pusherAudioHandler"><image class="btn-image" src="{{pusher.enableMic? '../../../static/images/audio-true.png': '../../../static/images/audio-false.png'}} "></image></view><view class="btn-normal" bindtap="_pusherVideoHandler"><image class="btn-image" src="{{pusher.enableCamera? '../../../static/images/camera-true.png': '../../../static/images/camera-false.png'}} "></image></view><view class="btn-normal" data-key="beautyLevel" data-value="9|0" data-value-type="number" bindtap="_setPusherBeautyHandle"><image class="btn-image" src="{{pusher.beautyLevel == 9 ? '../../../static/images/beauty-true.png': '../../../static/images/beauty-false.png'}} "></image></view><view class="btn-normal" bindtap="_switchMemberListPanel"><image class="btn-image" src="../../../static/images/list.png"></image></view><view class="btn-hangup" bindtap="_hangUp"><image class="btn-image" src="../../../static/images/hangup.png"></image></view></view></view><!-- 成员列表 --><view class="panel memberlist-panel" wx:if="{{show_memberList}}"><view bindtap="_handleClose" class='close-btn'>X</view><view class="panel-header">成员列表</view><view class="panel-body"><view class="panel-tips" wx:if="{{playerList.length === 0}}">暂无成员</view><scroll-view class="scroll-container" scroll-y="true"><view class="member-item" wx:for="{{playerList}}" wx:key="streamID" ><view class="member-id">{{item.userID}}</view><view class="member-btns"><button class="btn" hover-class="btn-hover"><image class="audio-image" bindtap="_mutePlayerAudio" data-value="{{item}}" src="{{item.muteAudio? '../../../static/images/mute-mic-white.png': '../../../static/images/micro-open.png'}} "></image></button><button class="btn" hover-class="btn-hover"><image class="audio-image" bindtap="_mutePlayerVideo" data-value="{{item}}" src="{{item.muteVideo? '../../../static/images/mute-camera-white.png': '../../../static/images/camera.png'}} "></image></button></view></view></scroll-view></view></view></view></view>
room.js
import TRTC from '../../../static/trtc-wx'Page({data: {_rtcConfig: {sdkAppID: '', // 必要参数 开通实时音视频服务创建应用后分配的 sdkAppIDuserID: '', // 必要参数 用户 ID 可以由您的帐号系统指定userSig: '', // 必要参数 身份签名,相当于登录密码的作用},pusher: null,//推流数据playerList: [],//拉流数据show_memberList: false,//控制成员列表显示隐藏localAudio: false,localVideo: false},/*** 生命周期函数--监听页面加载*/onLoad(options) {console.log('room onload', options)wx.setKeepScreenOn({keepScreenOn: true,})// 在生命周期中新建一个TRTC的类this.TRTC = new TRTC(this)// 将String 类型的 true false 转换成 booleanObject.getOwnPropertyNames(options).forEach((key) => {if (options[key] === 'true') {options[key] = true}if (options[key] === 'false') {options[key] = false}})this.init(options)this.bindTRTCRoomEvent()this.enterRoom({ roomID: options.roomID })},onReady() {console.log('room ready')},onUnload() {console.log('room unload')},/*** @method 初始化*/init(options) {// pusher 初始化参数const pusherConfig = {beautyLevel: 9,}// pusherConfig 是初始化参数,返回初始化后的推流状态,需要与模板相结合,const pusher = this.TRTC.createPusher(pusherConfig)this.setData({_rtcConfig: {userID: options.userID,sdkAppID: options.sdkAppID,userSig: options.userSig,},pusher: pusher.pusherAttributes,//创建推流实例localAudio: options.localAudio,localVideo: options.localVideo})console.log(this.data.pusher,123);},/*** @method 进入TRTC房间,开始推流*/enterRoom(options) {const roomID = options.roomID//必填,您要进入的房间号,如该房间不存在,系统会为您自动创建const config = Object.assign(this.data._rtcConfig, { roomID })this.setData({pusher: this.TRTC.enterRoom(config),}, () => {this.TRTC.getPusherInstance().start() // 开始推流})},/*** @method 结束音视频通话,重置所有状态并同步到页面中,防止下次谨防发生状态的混乱*/exitRoom() {const result = this.TRTC.exitRoom()this.setData({pusher: result.pusher,playerList: result.playerList,})},// 设置 pusher 属性//需要变更 live-pusher 标签上 enable-mic 和 enable-camera 的属性,您可以通过调用 setPusherAttributes 对状态机中管理的推流状态进行改变,会返回给您更新后的状态值,更新到页面中。setPusherAttributesHandler(options) {this.setData({pusher: this.TRTC.setPusherAttributes(options),})},// 设置某个 player 属性setPlayerAttributesHandler(player, options) {this.setData({playerList: this.TRTC.setPlayerAttributes(player.streamID, options),})},// 事件监听bindTRTCRoomEvent() {const TRTC_EVENT = this.TRTC.EVENT// 初始化事件订阅this.TRTC.on(TRTC_EVENT.LOCAL_JOIN, (event) => {console.log('* room LOCAL_JOIN', event)if (this.data.localVideo) {this.setPusherAttributesHandler({ enableCamera: true })// 进房默认开启视频上行}if (this.data.localAudio) {this.setPusherAttributesHandler({ enableMic: true })//进房默认开启音频上行}})this.TRTC.on(TRTC_EVENT.LOCAL_LEAVE, (event) => {console.log('* room LOCAL_LEAVE', event)})this.TRTC.on(TRTC_EVENT.ERROR, (event) => {console.log('* room ERROR', event)})this.TRTC.on(TRTC_EVENT.REMOTE_USER_JOIN, (event) => {console.log('* room REMOTE_USER_JOIN', event)const { userID } = event.datawx.showToast({title: `${userID} 进入了房间`,icon: 'none',duration: 2000,})})// 远端用户退出this.TRTC.on(TRTC_EVENT.REMOTE_USER_LEAVE, (event) => {console.log('* room REMOTE_USER_LEAVE', event)const { userID, playerList } = event.datathis.setData({playerList: playerList})wx.showToast({title: `${userID} 离开了房间`,icon: 'none',duration: 2000,})})// 远端用户推送视频this.TRTC.on(TRTC_EVENT.REMOTE_VIDEO_ADD, (event) => {console.log('* room REMOTE_VIDEO_ADD', event)const { player } = event.data// 开始播放远端的视频流,默认是不播放的this.setPlayerAttributesHandler(player, { muteVideo: false })})// 远端用户取消推送视频this.TRTC.on(TRTC_EVENT.REMOTE_VIDEO_REMOVE, (event) => {console.log('* room REMOTE_VIDEO_REMOVE', event)const { player } = event.datathis.setPlayerAttributesHandler(player, { muteVideo: true })})// 远端用户推送音频this.TRTC.on(TRTC_EVENT.REMOTE_AUDIO_ADD, (event) => {console.log('* room REMOTE_AUDIO_ADD', event)const { player } = event.datathis.setPlayerAttributesHandler(player, { muteAudio: false })})// 远端用户取消推送音频this.TRTC.on(TRTC_EVENT.REMOTE_AUDIO_REMOVE, (event) => {console.log('* room REMOTE_AUDIO_REMOVE', event)const { player } = event.datathis.setPlayerAttributesHandler(player, { muteAudio: true })})this.TRTC.on(TRTC_EVENT.REMOTE_AUDIO_VOLUME_UPDATE, (event) => {console.log('* room REMOTE_AUDIO_VOLUME_UPDATE', event)const { playerList } = event.datathis.setData({playerList: playerList})})this.TRTC.on(TRTC_EVENT.LOCAL_AUDIO_VOLUME_UPDATE, (event) => {// console.log('* room LOCAL_AUDIO_VOLUME_UPDATE', event)const { pusher } = event.datathis.setData({pusher: pusher})})},// 是否订阅某一个player Audio_mutePlayerAudio(event) {const player = event.currentTarget.dataset.valueif (player.hasAudio && player.muteAudio) {this.setPlayerAttributesHandler(player, { muteAudio: false })return}if (player.hasAudio && !player.muteAudio) {this.setPlayerAttributesHandler(player, { muteAudio: true })return}},// 订阅 / 取消订阅某一个player Audio_mutePlayerVideo(event) {const player = event.currentTarget.dataset.valueif (player.hasVideo && player.muteVideo) {this.setPlayerAttributesHandler(player, { muteVideo: false })return}if (player.hasVideo && !player.muteVideo) {this.setPlayerAttributesHandler(player, { muteVideo: true })return}},// 挂断退出房间_hangUp() {this.exitRoom()wx.navigateBack({delta: 1,})},// 设置美颜_setPusherBeautyHandle() {const beautyLevel = this.data.pusher.beautyLevel === 0 ? 9 : 0this.setPusherAttributesHandler({ beautyLevel })},//开启/关闭麦克风_pusherAudioHandler() {if (this.data.pusher.enableMic) {this.setPusherAttributesHandler({ enableMic: false })} else {this.setPusherAttributesHandler({ enableMic: true })}},// 开启/关闭摄像头_pusherVideoHandler() {if (this.data.pusher.enableCamera) {this.setPusherAttributesHandler({ enableCamera: false })} else {this.setPusherAttributesHandler({ enableCamera: true })}},/*** @method 控制显示隐藏成员列表*/_switchMemberListPanel() {this.setData({show_memberList: true})},_handleClose() {this.setData({show_memberList: false})},// 请保持跟 wxml 中绑定的事件名称一致// 推流/*** @method 状态变化事件,detail = {code}*/_pusherStateChangeHandler(event) {this.TRTC.pusherEventHandler(event)},/*** @method 网络状态通知,detail = {info}*/_pusherNetStatusHandler(event) {this.TRTC.pusherNetStatusHandler(event)},/*** @method 渲染错误事件,detail = {errMsg, errCode}*/_pusherErrorHandler(event) {this.TRTC.pusherErrorHandler(event)},/*** @method 背景音开始播放时触发*/_pusherBGMStartHandler(event) {this.TRTC.pusherBGMStartHandler(event)},/*** @method 背景音进度变化时触发,detail = {progress, duration}*/_pusherBGMProgressHandler(event) {this.TRTC.pusherBGMProgressHandler(event)},/*** @method 背景音播放完成时触发*/_pusherBGMCompleteHandler(event) {this.TRTC.pusherBGMCompleteHandler(event)},/*** @method 返回麦克风采集的音量大小*/_pusherAudioVolumeNotify(event) {this.TRTC.pusherAudioVolumeNotify(event)},// 拉流/*** @method 播放状态变化事件,detail = {code}*/_playerStateChange(event) {this.TRTC.playerEventHandler(event)},/*** @method 全屏变化事件,detail = {direction, fullScreen}*/_playerFullscreenChange(event) {this.TRTC.playerFullscreenChange(event)},/*** @method 网络状态通知,detail = {info}*/_playerNetStatus(event) {this.TRTC.playerNetStatus(event)},/*** @method 播放音量大小通知,detail = {}*/_playerAudioVolumeNotify(event) {this.TRTC.playerAudioVolumeNotify(event)},
})