【多人会议功能】uniapp - 微信小程序 - 腾讯云

目的:uniapp微信小程序通过腾讯云实现多人会议功能。

效果展示:

功能实现:

前提条件: 注册腾讯云 账号,并完成 实名认证

推拉流标签不支持个人小程序,要求申请的企业类微信小程序。

步骤一:开通微信小程序权限

推拉流标签不支持个人小程序,要求申请的企业类微信小程序登录微信公众平台 =》开发 =》开发管理 =》接口设置,在其他接口中打开实时播放音视频流实时播放音视频流。如图:

步骤二:在微信小程序控制台配置域名

微信公众平台 =》 开发 =》 开发管理 =》 开发设置 =》 服务器域名中设置 request合法域名socket合法域名,如下图所示:

  •  request 合法域名:
    https://official.opensso.tencent-cloud.com
    https://yun.tim.qq.com
    https://cloud.tencent.com
    https://webim.tim.qq.com
    https://query.tencent-cloud.com
    https://web.sdk.qcloud.com
  • socket 合法域名:
    wss://wss.im.qcloud.com
    wss://wss.tim.qq.com

步骤三:下载多人会议功能源码  源码下载地址

 项目结构如图所示:

步骤四: 开通腾讯云服务 

  1. 登录到 即时通信 IM 控制台,单击创建新应用,在弹出的对话框中输入您的应用名称,并单击确定

  2. 单击刚刚创建出的应用,进入基本配置页面,并在页面的右下角找到开通腾讯实时音视频服务功能区,单击免费体验即可开通 TUICallKit 的 7 天免费试用服务。
  3. 在同一页面找到 SDKAppID密钥并记录下来。

步骤四: 配置 meeting =》debug =》GenerateTestUserSig.js 工程文件

设置 GenerateTestUserSig.js 文件中的相关参数:

  • SDKAPPID:默认为0,请设置为实际的 SDKAppID。
  • SECRETKEY:默认为空字符串,请设置为实际的密钥信息。

步骤五:calling.vue为入口页。

<template>
	<view class="container">
		<view class="trtc-demo-container">
		  <!-- <view class='title'  >
		    <view>多人会议</view>
		  </view> -->
		  <view class="input-box">
		    <input type="number" v-model="roomID" maxlength="10"  placeholder="请输入房间号" placeholder-style="opacity: 0.55;"/>
		  </view>
		  <view class="choice-content">
		    <view class="label" >
		      <text>开启摄像头</text>
		      <u-switch inactiveColor="#999999" activeColor="#00B38A" v-model="localVideo"  @change="switchHandler"/>
		    </view>
		    <view class="label">
		      <text>开启麦克风</text>
		      <u-switch  inactiveColor="#999999" activeColor="#00B38A" v-model="localAudio"  @change="switchHandler2"/>
		    </view>
		  </view>
		</view>
		
		<view class='bottom-btn'>
		  <button class="btn" @click="enterRoom" hover-class="none">进入房间</button>
		</view>
	</view>
</template>
<script>

import { genTestUserSig } from './debug/GenerateTestUserSig'
import { mapState } from 'vuex';
	
	export default {
		data() {
			return {
				roomID: '',
				localVideo: true,
				localAudio: false,
			}
		},
		computed: {
			...mapState(['userInfo'])
		},
		onLaunch(){

		},
		onLoad() {

		},
		methods: {
			enterRoom() {
			    const nowTime = new Date()
			    if (nowTime - this.tapTime < 1000) {
			      return
			    }
			    if (!this.roomID) {
			      uni.showToast({
			        title: '请输入房间号',
			        icon: 'none',
			        duration: 2000,
			      })
			      return
			    }
			    if (/^\d*$/.test(this.roomID) === false) {
			      uni.showToast({
			        title: '房间号只能为数字',
			        icon: 'none',
			        duration: 2000,
			      })
			      return
			    }
			    if (this.roomID > 4294967295 || this.roomID < 1) {
			      uni.showToast({
			        title: '房间号取值范围为 1~4294967295',
			        icon: 'none',
			        duration: 2000,
			      })
			      return
			    }
			    const userID = this.userInfo.userId || '123'; //userID
			    const Signature = genTestUserSig(userID)
			    const url = `./room/room?roomID=${this.roomID}&localVideo=${this.localVideo}&localAudio=${this.localAudio}&userID=${userID}&sdkAppID=${Signature.sdkAppID}&userSig=${Signature.userSig}`
			    this.tapTime = nowTime
			    this.checkDeviceAuthorize().then((result) => {
			      console.log('授权成功', result)
			      wx.navigateTo({ url })
			    })
			      .catch((error) => {
			        console.log('没有授权', error)
			      })
			  },
			  checkDeviceAuthorize() {
			    this.hasOpenDeviceAuthorizeModal = false
			    return new Promise((resolve, reject) => {
			      if (!wx.getSetting || !wx.getSetting()) {
			        // 微信测试版 获取授权API异常,目前只能即使没授权也可以通过
			        resolve()
			      }
			      wx.getSetting().then((result) => {
			        console.log('getSetting', result)
			        this.authorizeMic = result.authSetting['scope.record']
			        this.authorizeCamera = result.authSetting['scope.camera']
			        if (result.authSetting['scope.camera'] && result.authSetting['scope.record']) {
			          // 授权成功
			          resolve()
			        } else {
			          // 没有授权,弹出授权窗口
			          // 注意: wx.authorize 只有首次调用会弹框,之后调用只返回结果,如果没有授权需要自行弹框提示处理
			          console.log('getSetting 没有授权,弹出授权窗口', result)
			          wx.authorize({
			            scope: 'scope.record',
			          }).then((res) => {
			            console.log('authorize mic', res)
			            this.authorizeMic = true
			            if (this.authorizeCamera) {
			              resolve()
			            }
			          })
			            .catch((error) => {
			              console.log('authorize mic error', error)
			              this.authorizeMic = false
			            })
			          wx.authorize({
			            scope: 'scope.camera',
			          }).then((res) => {
			            console.log('authorize camera', res)
			            this.authorizeCamera = true
			            if (this.authorizeMic) {
			              resolve()
			            } else {
			              this.openConfirm()
			              reject(new Error('authorize fail'))
			            }
			          })
			            .catch((error) => {
			              console.log('authorize camera error', error)
			              this.authorizeCamera = false
			              this.openConfirm()
			              reject(new Error('authorize fail'))
			            })
			        }
			      })
			    })
			  },
			  openConfirm() {
			    if (this.hasOpenDeviceAuthorizeModal) {
			      return
			    }
			    this.hasOpenDeviceAuthorizeModal = true
			    return uni.showModal({
			      content: '您没有打开麦克风和摄像头的权限,是否去设置打开?',
			      confirmText: '确认',
			      cancelText: '取消',
			      success: (res) => {
			        this.hasOpenDeviceAuthorizeModal = false
			        console.log(res)
			        // 点击“确认”时打开设置页面
			        if (res.confirm) {
			          console.log('用户点击确认')
			          wx.openSetting({
			            success: (res) => { },
			          })
			        } else {
			          console.log('用户点击取消')
			        }
			      },
			    })
			  },
			  switchHandler(e) {
				  this.localVideo = e;
			  },
			  switchHandler2(e) {
			  		this.localAudio = e;
			  },
			  onBack() {
			    wx.navigateBack({
			      delta: 1,
			    })
			  },
		}
		
	}
</script>
<style scoped>
	.container {
		width: 100vw;
		height: 100vh;
		background-color: #F5F5F5;
		position: fixed;
		top: 0;
		right: 0;
		left: 0;
		bottom: 0;
	}

	.trtc-demo-container {
	  /* background-image: url(https://mc.qcloudimg.com/static/img/7da57e0050d308e2e1b1e31afbc42929/bg.png); */
	  /* background-color: #333; */
	  /* background-repeat:no-repeat;
	  background-size: cover; */
	  width: 100vw;
	  height: 100vh;
	  display: flex;
	  flex-direction: column;
	  align-items: center;
	  box-sizing: border-box;
	}
	.trtc-demo-container .title{
	  color: #FFFFFF;
	  padding-top: 65rpx;
	  line-height: 60rpx;
	}
	.trtc-demo-container .input-box {
	  background-color: transparent;
	  color: #333;
	  padding: 2vw 5vw 1vw;
	  border-bottom: 1px solid #577785;
	  margin: 100rpx 0 40rpx 0;
	  text-align: center;
	  box-sizing: border-box;
	  width: 80vw;
	}
	.trtc-demo-container .input-box input{
	  font-size: 20px;
	}
	.choice-content {
	  margin-top: 20rpx;
	  width: 80vw;
	  display: flex;
	  flex-direction: column;
	  /* justify-content: space-between;
	  flex-wrap: wrap; */
	  font-size: 14px;
	  color: #333;
	}
	.label{
		display: flex;
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
		padding: 24rpx 0;
	}
	.choice-content switch {
	  color: #00B38A;
	  transform:scale(0.8);
	}
	
	
	.bottom-btn {
	  position: fixed;
	  width: 100vw;
	  text-align: center;
	  bottom: 5vh;
	}
	.bottom-btn .btn{
	  width: 80%;
	  background-color: #00B38A;
	  border-radius: 50px;
	  color: #ffffff;
	}
	
	.close {
	  position:absolute;
	  padding-left:5vw;
	  padding-right:5vw;
	  width:50rpx;
	  height:60rpx;
	}
</style>

room.vue 会议页面(样式按自己需求调整): 实时音视频 API 概览-含 UI 集成方案-文档中心-腾讯云 (tencent.com)

<template>
	<view class="template-grid">
		<view class="column-1">
			<!-- :class="playerList.length !=0? 'fullscreen':'fullscreen2'" -->
			<view class="view-container fullscreen">
				<live-pusher class="pusher"
					:data-userid="pusher.userID"
					:data-streamid="pusher.streamID"
					:data-streamtype="pusher.streamType"
					: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"
					@statechange="_pusherStateChangeHandler" @netstatus="_pusherNetStatusHandler"
					@error="_pusherErrorHandler" @bgmstart="_pusherBGMStartHandler"
					@bgmprogress="_pusherBGMProgressHandler" @bgmcomplete="_pusherBGMCompleteHandler"
					@audiovolumenotify="_pusherAudioVolumeNotify" />
				<view class="no-video" v-if="!pusher.enableCamera">
					<image class="image" :src="require('../static/images/mute-camera-white.png')"></image>
				</view>
				<view class="no-audio" v-if="!pusher.enableMic">
					<image class="image" :src="require('../static/images/mute-mic-white.png')"></image>
				</view>
				<view class="audio-volume" v-if="pusher.enableMic">
						<image class="image" :src="require('../static/images/micro-open.png')"></image>
						<view class="audio-active" :style="'height:' + pusher.volume + '%'">
							<image class="image" :src="require('../static/images/audio-active.png')"></image>
						</view>
					</view>
			</view>
		</view>
		<swiper v-show="show_memberList" class="swiper" :indicator-dots="true" indicatorActiveColor="#00B38A">
			<swiper-item class="swiper-item" v-for="(items, index) in playerList" :key="index">
					<view v-for="(item, streamID) in items" :key="streamID" class="player-container"
					:id="'player-' + item.streamID">
					<live-player class="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"
						@statechange="_playerStateChange" @fullscreenchange="_playerFullscreenChange"
						@netstatus="_playerNetStatus" @audiovolumenotify="_playerAudioVolumeNotify" />
					<view class="no-video" v-if="item.muteVideo">
						<image class="image" :src="require('../static/images/display-pause-white.png')"></image>
						<view class="text">
							<p>{
   
   { item.userID }}</p>
						</view>
					</view>
					<view class="no-video" v-if="!item.hasVideo && !item.muteVideo">
						<image class="image" :src="require('../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" v-if="!item.hasAudio">
						<image class="image" :src="require('../static/images/mute-mic-white.png')"></image>
					</view>
					<view class="audio-volume" v-if="item.hasAudio">
						<image class="image" :src="require('../static/images/micro-open.png')"></image>
						<view class="audio-active" :style="'height:' + item.volume + '%'">
							<image class="image" :src="require('../static/images/audio-active.png')"></image>
						</view>
					</view>
					<view class="sub-box">
						<image class="audio-image" @click="_mutePlayerAudio(item)" 
							:src="item.muteAudio? require('../static/images/mute-mic-white.png') : require('../static/images/micro-open.png')">
						</image>
						<image class="audio-image" @click="_mutePlayerVideo(item)" 
							:src="item.muteVideo? require('../static/images/mute-camera-white.png') : require('../static/images/camera.png')">
						</image>
					</view>
				</view>
			</swiper-item>
		</swiper>
	
		<view class="bottom-box">
			<view class="bottom-btns">
				<view class="btn-normal" @click="_pusherAudioHandler">
					<image class="btn-image"
						:src="pusher.enableMic? require('../static/images/audio-true.png') : require('../static/images/audio-false.png')">
					</image>
				</view>
				<view class="btn-normal" @click="_pusherVideoHandler">
					<image class="btn-image"
						:src="pusher.enableCamera? require('../static/images/camera-true.png') : require('../static/images/camera-false.png')">
					</image>
				</view>
				<view class="btn-hangup" @click="_hangUp">
					<image class="btn-image" :src="require('../static/images/hangup.png')"></image>
				</view>
				<view class="btn-normal"
					@click="_setPusherBeautyHandle">
					<image class="btn-image"
						:src="pusher.beautyLevel == 9? require('../static/images/beauty-true.png') : require('../static/images/beauty-false.png')">
					</image>
				</view>
				<view class="btn-normal" @click="_switchMemberListPanel">
					<image class="btn-image" :src="require('../static/images/list.png')"></image>
				</view>
				
			</view>
		</view>
	
		<!-- <view class="panel memberlist-panel" v-if="show_memberList">
			<view @click="_handleClose" class='close-btn'>X</view>
			<view class="panel-header">成员列表</view>
			<view class="panel-body">
				<view class="panel-tips" v-if="playerList.length == 0">暂无成员</view>
				<scroll-view class="scroll-container" scroll-y="true">
					<view v-for="(items, index) in playerList" :key="index">
						<view class="member-item" v-for="(item, streamID) in items" :key="streamID">
							<view class="member-id">{
   
   { item.userID }}</view>
							<view class="member-btns">
								<view class="btn">
									<image class="audio-image" @click="_mutePlayerAudio(item)"
										:src="item.muteAudio? require('../static/images/mute-mic-white.png') : require('../static/images/micro-open.png')">
									</image>
								</view>
								<view class="btn">
									<image class="audio-image" @click="_mutePlayerVideo(item)"
										:src="item.muteVideo? require('../static/images/mute-camera-white.png') : require('../static/images/camera.png')">
									</image>
								</view>
							</view>
						</view>
					</view>
				</scroll-view>
			</view>
		</view> -->
		
	</view>
</template>
<script>
import TRTC from '../static/trtc-wx';
import { mapState } from 'vuex';

export default {
	data() {
		return {
			RtcConfig: {
				sdkAppID: '', // 必要参数 开通实时音视频服务创建应用后分配的 sdkAppID
				userID: '', // 必要参数 用户 ID 可以由您的帐号系统指定
				userSig: '', // 必要参数 身份签名,相当于登录密码的作用
			},
			pusher: {
				enableCamera: false,
			},
			//切换后的主频
			pushed: {
				enableCamera: false,
			},
			playerList: [
				// {"sre":"room://cloud.tencent.com/rtc?userid=654&streamtype=main",
				//  "mode":"RTC", 
				//  "autoplay":true, 
				//  "muteAudio" :false, 
				//  "mute Video" :true, 
				//  " orientation": "vertical",
				// "objectFit":"fillCrop",
				// "enableBackgroundMute":false,
				// "minCache":1," maxCache":2, "soundMode":"speaker", 
				// "enableRecMessage":false, "autoPauselfNavigate":true,
				// "autoPauselfOpenNative":true,"isVisible":true,
				// "_definitionType": "main", "netStatus":
				// {"videoBitrate":0, " audioBitrate":42, "videoFPS":0, "netSpeed"
				// :42, "netQualityLevel":1, " videoWidth":640, " videoHeight":480
				// }, "userID": "654","streamType": "main","streamID": "654_main", id: "654_main","hasVideo" :false, "hasAudio" :false, "volume":54},
				// {"sre":"room://cloud.tencent.com/rtc?userid=654&streamtype=main",
				//  "mode":"RTC", 
				//  "autoplay":true, 
				//  "muteAudio" :false, 
				//  "mute Video" :true, 
				//  " orientation": "vertical",
				// "objectFit":"fillCrop",
				// "enableBackgroundMute":false,
				// "minCache":1," maxCache":2, "soundMode":"speaker", 
				// "enableRecMessage":false, "autoPauselfNavigate":true,
				// "autoPauselfOpenNative":true,"isVisible":true,
				// "_definitionType": "main", "netStatus":
				// {"videoBitrate":0, " audioBitrate":42, "videoFPS":0, "netSpeed"
				// :42, "netQualityLevel":1, " videoWidth":640, " videoHeight":480
				// }, "userID": "654","streamType": "main","streamID": "654_main", id: "654_main","hasVideo" :false, "hasAudio" :false, "volume":54},
				// {"sre":"room://cloud.tencent.com/rtc?userid=654&streamtype=main",
				//  "mode":"RTC", 
				//  "autoplay":true, 
				//  "muteAudio" :false, 
				//  "mute Video" :true, 
				//  " orientation": "vertical",
				// "objectFit":"fillCrop",
				// "enableBackgroundMute":false,
				// "minCache":1," maxCache":2, "soundMode":"speaker", 
				// "enableRecMessage":false, "autoPauselfNavigate":true,
				// "autoPauselfOpenNative":true,"isVisible":true,
				// "_definitionType": "main", "netStatus":
				// {"videoBitrate":0, " audioBitrate":42, "videoFPS":0, "netSpeed"
				// :42, "netQualityLevel":1, " videoWidth":640, " videoHeight":480
				// }, "userID": "654","streamType": "main","streamID": "654_main", id: "654_main","hasVideo" :true, "hasAudio" :true, "volume":54},
				// {"sre":"room://cloud.tencent.com/rtc?userid=654&streamtype=main",
				//  "mode":"RTC", 
				//  "autoplay":true, 
				//  "muteAudio" :false, 
				//  "mute Video" :true, 
				//  " orientation": "vertical",
				// "objectFit":"fillCrop",
				// "enableBackgroundMute":false,
				// "minCache":1," maxCache":2, "soundMode":"speaker", 
				// "enableRecMessage":false, "autoPauselfNavigate":true,
				// "autoPauselfOpenNative":true,"isVisible":true,
				// "_definitionType": "main", "netStatus":
				// {"videoBitrate":0, " audioBitrate":42, "videoFPS":0, "netSpeed"
				// :42, "netQualityLevel":1, " videoWidth":640, " videoHeight":480
				// }, "userID": "654","streamType": "main","streamID": "654_main", id: "654_main","hasVideo" :true, "hasAudio" :true, "volume":54},
				// {"sre":"room://cloud.tencent.com/rtc?userid=654&streamtype=main",
				//  "mode":"RTC", 
				//  "autoplay":true, 
				//  "muteAudio" :false, 
				//  "mute Video" :true, 
				//  " orientation": "vertical",
				// "objectFit":"fillCrop",
				// "enableBackgroundMute":false,
				// "minCache":1," maxCache":2, "soundMode":"speaker", 
				// "enableRecMessage":false, "autoPauselfNavigate":true,
				// "autoPauselfOpenNative":true,"isVisible":true,
				// "_definitionType": "main", "netStatus":
				// {"videoBitrate":0, " audioBitrate":42, "videoFPS":0, "netSpeed"
				// :42, "netQualityLevel":1, " videoWidth":640, " videoHeight":480
				// }, "userID": "654","streamType": "main","streamID": "654_main", id: "654_main","hasVideo" :true, "hasAudio" :true, "volume":54}
			],
			show_memberList: false,
			localAudio: false,
			localVideo: false,
			myshow:true,
			shownum:true,
		}
	},
	/**
	* 生命周期函数--监听页面加载
	*/

	computed: {
		...mapState(['userInfo'])
	},
	onLoad(options) {
		console.log('room onload', options)
		wx.setKeepScreenOn({
			keepScreenOn: true,
		})
		this.TRTC = new TRTC(this)
		// 将String 类型的 true false 转换成 boolean
		Object.getOwnPropertyNames(options).forEach((key) => {
			if (options[key] === 'true') {
				options[key] = true
			}
			if (options[key] === 'false') {
				options[key] = false
			}
		})
		 // this.playerList = this.sliceIntoChunks(this.playerList,2)//测试
		this.init(options)
		this.bindTRTCRoomEvent()
		this.enterRoom({ roomID: options.roomID })
	},
	onReady() {
		console.log('room ready')
	},
	onUnload() {
		console.log('room unload')
	},
	methods: {
		init(options) {

			console.log("options", options)
			// pusher 初始化参数
			const pusherConfig = {
				beautyLevel: 9,
			}
			const pusher = this.TRTC.createPusher(pusherConfig)
			console.log("pusher", pusher)
			console.log('userID', this.RtcConfig)
			this.RtcConfig.userID = options.userID;
			this.RtcConfig.sdkAppID = options.sdkAppID;
			this.RtcConfig.userSig = options.userSig;
			this.pusher = pusher.pusherAttributes;
			this.localAudio = options.localAudio;
			this.localVideo = options.localVideo;
			console.log(this.localAudio, this.localVideo)
			console.log("000000000000000")
		},

		enterRoom(options) {
			const roomID = options.roomID
			const config = Object.assign(this.RtcConfig, { roomID })
			this.pusher = this.TRTC.enterRoom(config);
			console.log("this.pusher", this.pusher)
			if (this.pusher) {
				this.TRTC.getPusherInstance().start() // 开始推流
			}
		},

		exitRoom() {
			const result = this.TRTC.exitRoom();
			this.pusher = result.pusher;
			this.playerList = this.sliceIntoChunks(result.playerList,2);
		},

		// 设置 pusher 属性
		setPusherAttributesHandler(options) {
			this.pusher = this.TRTC.setPusherAttributes(options);
		},

		// 设置某个 player 属性
		setPlayerAttributesHandler(player, options) {
			console.log("123",player, options,)
			//this.playerList = this.TRTC.setPlayerAttributes(player.streamID, options);
			let playerList = this.TRTC.setPlayerAttributes(player.streamID, options)
			this.playerList = this.sliceIntoChunks(playerList,2);
			console.log("12345678:",this.playerList)
		},
		// 事件监听
		bindTRTCRoomEvent() {
			const TRTC_EVENT = this.TRTC.EVENT
			console.log("xxxxxxxxxxxx", TRTC_EVENT)
			// 初始化事件订阅
			this.TRTC.on(TRTC_EVENT.LOCAL_JOIN, (event) => {
				console.log('* room LOCAL_JOIN', event)
				if (this.localVideo) {
					this.setPusherAttributesHandler({ enableCamera: true })
				}
				if (this.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.data;
				uni.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.data
				this.playerList = this.sliceIntoChunks(playerList,2);
				uni.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.data
				console.log("234",player)
				this.setPlayerAttributesHandler(player, { muteVideo: true })
			})
			// 远端用户推送音频
			this.TRTC.on(TRTC_EVENT.REMOTE_AUDIO_ADD, (event) => {
				console.log('* room REMOTE_AUDIO_ADD', event)
				const { player } = event.data
				console.log("345",player)
				this.setPlayerAttributesHandler(player, { muteAudio: false })
			})
			// 远端用户取消推送音频
			this.TRTC.on(TRTC_EVENT.REMOTE_AUDIO_REMOVE, (event) => {
				console.log('* room REMOTE_AUDIO_REMOVE', event)
				const { player } = event.data
				this.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.data;
				this.playerList = this.sliceIntoChunks(playerList,2);
				console.log("@@@@", this.playerList)
			})
			this.TRTC.on(TRTC_EVENT.LOCAL_AUDIO_VOLUME_UPDATE, (event) => {
				// console.log('* room LOCAL_AUDIO_VOLUME_UPDATE', event)
				const { pusher } = event.data
				this.pusher = pusher;
			})
		},

		// 是否订阅某一个player Audio
		_mutePlayerAudio(player) {
			console.log('22222',player)
			//const player = event.currentTarget.dataset.value
			if (player.hasAudio && player.muteAudio) {
				this.setPlayerAttributesHandler(player, { muteAudio: false })
				return
			}
			if (player.hasAudio && !player.muteAudio) {
				this.setPlayerAttributesHandler(player, { muteAudio: true })
				return
			}
		},

		// 订阅 / 取消订阅某一个player Audio
		_mutePlayerVideo(player) {
			console.log("1111")
			console.log(player)
			//const player = event.currentTarget.dataset.value
			if (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.pusher.beautyLevel === 0 ? 9 : 0
			this.setPusherAttributesHandler({ beautyLevel })
		},

		// 订阅 / 取消订阅 Audio
		_pusherAudioHandler() {
			if (this.pusher.enableMic) {
				this.setPusherAttributesHandler({ enableMic: false })
			} else {
				this.setPusherAttributesHandler({ enableMic: true })
			}
		},

		// 订阅 / 取消订阅 Video
		_pusherVideoHandler() {
			if (this.pusher.enableCamera) {
				this.setPusherAttributesHandler({ enableCamera: false })
			} else {
				this.setPusherAttributesHandler({ enableCamera: true })
			}
		},

		_switchMemberListPanel() {
			if(this.playerList.length == 0){
				uni.showToast({
					title: "暂无成员",
					icon: 'none',
					duration: 2000,
				})
			}else{
				this.show_memberList = !this.show_memberList;
			}
			// this.setData({
			//   show_memberList: true
			// })
		},
		_handleClose() {
			this.show_memberList = false;
			// this.setData({
			//   show_memberList: false
			// })
		},
		// 请保持跟 wxml 中绑定的事件名称一致
		_pusherStateChangeHandler(event) {
			this.TRTC.pusherEventHandler(event)
		},
		_pusherNetStatusHandler(event) {
			this.TRTC.pusherNetStatusHandler(event)
		},
		_pusherErrorHandler(event) {
			this.TRTC.pusherErrorHandler(event)
		},
		_pusherBGMStartHandler(event) {
			this.TRTC.pusherBGMStartHandler(event)
		},
		_pusherBGMProgressHandler(event) {
			this.TRTC.pusherBGMProgressHandler(event)
		},
		_pusherBGMCompleteHandler(event) {
			this.TRTC.pusherBGMCompleteHandler(event)
		},
		_pusherAudioVolumeNotify(event) {
			this.TRTC.pusherAudioVolumeNotify(event)
		},
		_playerStateChange(event) {
			this.TRTC.playerEventHandler(event)
		},
		_playerFullscreenChange(event) {
			this.TRTC.playerFullscreenChange(event)
		},
		_playerNetStatus(event) {
			this.TRTC.playerNetStatus(event)
		},
		_playerAudioVolumeNotify(event) {
			this.TRTC.playerAudioVolumeNotify(event)
		},

		//数组重构
		sliceIntoChunks(arr, chunkSize) {
				const res = [];
				console.log(arr.length)
				for (let i = 0; i < arr.length; i += chunkSize) {
					const chunk = arr.slice(i, i + chunkSize);
					console.log(chunk)
					res.push(chunk);
			}
				return res;
		},
		//切换为主频
		toggle(e,i,j){
			// this.setPlayerAttributesHandler(e, { muteVideo: false })
			// console.log(this.shownum)
			// console.log('######',e,i,j)
			// console.log('zhu:',this.pusher,this.pusher.muteVideo,e.muteVideo)
			// if(e.userID == this.userInfo.userId){
			// 	this.setPlayerAttributesHandler(this.pushed, { muteVideo: false })
			// 	this.playerList[i].splice(j,1,this.pushed);
			// 	this.myshow = true;
			// 	this.shownum = true
			// }else{
			// 	this.myshow = false;
			// 	if(this.shownum){
			// 		this.playerList[i].splice(j,1,this.pusher);
			// 		this.pushed = e;
			// 		this.shownum = false
			// 	}else{
			// 		this.shownum = false
			// 		 //this.setPlayerAttributesHandler(this.pushed, { muteVideo: false })
			// 		this.playerList[i].splice(j,1,this.pushed);
			// 		this.pushed = e;
			// 	}
			// }
			
			// this.setPlayerAttributesHandler(e, { muteVideo: false })
			// console.log('######',e,i,j)
			// console.log('zhu:',this.pusher,this.pusher.muteVideo,e.muteVideo)
			if(e.userID == this.userInfo.userId){
					this.playerList[i].splice(j,1,this.pushed);
					this.myshow = true;
			}else{
					this.playerList[i].splice(j,1,this.pusher);
					this.pushed = e;
					this.myshow = false;
			}
			// this.setPlayerAttributesHandler(this.playerList, { muteVideo: false })
			console.log('$$$$$$$$$$$',this.playerList)
		},
		/**
			 * 切换前后摄像头
			 */
			switchCamera() {
				if (!this.cameraPosition) {
					// this.data.pusher.cameraPosition 是初始值,不支持动态设置
					this.cameraPosition = this.pusher.frontCamera;
				}

				console.log(TAG_NAME, 'switchCamera', this.cameraPosition);
				this.cameraPosition = this.cameraPosition === 'front' ? 'back' : 'front';
				this.setData({
					cameraPosition: this.cameraPosition
				}, () => {
					console.log(TAG_NAME, 'switchCamera success', this.cameraPosition);
				}); // wx 7.0.9 不支持动态设置 pusher.frontCamera ,只支持调用 API switchCamer() 设置,这里修改 cameraPosition 是为了记录状态

				this.pusher.getPusherContext().switchCamera();
			},
	}
}
</script>
<style lang="less" scoped>
/* 9人 会议模版 */
.template-grid{
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background-color: #F5F5F5;
  /* background-image: url(https://mc.qcloudimg.com/static/img/7da57e0050d308e2e1b1e31afbc42929/bg.png); */
  /* display: flex;
  flex-direction: row;
  flex-wrap: wrap; */
}
.pusher {
  height: 100%;
}
.player{
  height: 100%;
}

.column-1{
  // max-height: calc(100vh - 170rpx);
  // min-height: calc(100vh - 170rpx);
  display: flex;
  flex-direction: column;
  /*flex: 1;*/
}


.view-container {
  position: relative;
	width: 100vh;
}
.no-video{
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	color:#fff;
	background-color: rgba(0, 0, 0, 0.4);
	font-size: 24rpx;
	border-radius: 16rpx;
	.image{
		width: 60rpx;
		height: 60rpx;
	}
}


.fullscreen{
	width: 100vw;
	height: calc(100vh - 196rpx);
}

live-player {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border-radius: 16rpx;
}


.template-grid .btn-normal {
  width: 64rpx;
  height: 64rpx;
  margin: 0 6rpx;
  box-sizing: border-box;
  display: flex;
  background: rgba(255, 255, 255, 1);
  justify-content: center;
  align-items: center;
  border-radius: 50%;
}

.template-grid .btn-normal .btn-image{
  width: 36rpx;
  height: 36rpx;
}

.template-grid .btn-hangup {
  background: #f75c45;
}

.template-grid .panel{
  position: absolute;
  background: rgba(0, 0, 0, 0.8);
  width: 90vw;
  height: auto;
  z-index: 999;
  top: 50vh;
  left: 50vw;
  transform: translate(-50%, -50%);
  color: white;
  display: flex;
  flex-direction: column;
  padding: 20rpx 0;
  border-radius: 10rpx;
  box-sizing: border-box;
  font-size: 24rpx;
}
.panel .close-btn {
  position: absolute;
  top: 0;
  right: 0;
  padding: 10rpx 20rpx;
}
.panel .panel-header{
  text-align: center;
  padding-bottom: 20rpx;
}
.panel .panel-tips {
  color: #999;
  text-align: center;
}
.panel .panel-body{
  flex: 1;
  max-height: 50vh;
}
.panel .panel-body .scroll-container{
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}

.memberlist-panel .panel-body{
  height: 30vh;
  .audio-image {
  	padding: 0 12rpx;
  	width: 40rpx;
  	height: 40rpx;
  }
}
.memberlist-panel .member-item {
  display: flex;
  /* border-bottom: 1px solid #999; */
  margin: 16rpx 16rpx 16rpx 32rpx;
}
.memberlist-panel .member-id {
  width: 60%;
  font-size: 24rpx;
  line-height: 64rpx;
}
.memberlist-panel .member-btns{
  width: 70%;
  display: flex;
  justify-content: flex-end;
}
.memberlist-panel .member-btns .btn-normal{
  margin-left: 0;
}
.memberlist-panel .member-btns .btn{
  margin-right: 0;
}
 .sub-box{
  position: absolute;
  right: 10rpx;
  bottom: 24rpx;
  width: 80rpx;
  height: 172rpx;
  background-color: rgba(0,0,0,0.7);
  border-radius: 8rpx;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
	.audio-image {
		padding: 0 14rpx;
		width: 48rpx;
		height: 48rpx;
	}
}

.no-audio , .audio-volume{
  position: absolute;
  bottom: 20rpx;
  left: 20rpx;
  width: 36rpx;
  height: 36rpx;
	.image{
		width: 36rpx;
		height: 36rpx;
		position: absolute; /*android 的bug ,image absolute后会向上漂移几个像素,如果要对其必须都设置absolute*/
	}
}

.audio-active {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 0;
  overflow: hidden;
}
.audio-active .image{
  bottom: 0;
}

.slide-up-tips {
  position: absolute;
  bottom: -100rpx;
  left: 50%;
  transform: translate(-50%, 0);
  width: 200rpx;
  height: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  font-size: 24rpx;
  color: #fff;
  background-color: rgba(0, 0, 0, 0.4);
  box-sizing: border-box;
  padding: 20rpx;
  border-radius: 10rpx;
  opacity: 0;
}
.slide-up-tips .image {
  width: 100rpx;
  height: 100rpx;
}
.player-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.player-placeholder .image {
  width: 100rpx;
  height: 100rpx;
}

.bottom-box {
	width: 100vw;
	height: 196rpx;
	background-color: rgba(0,0,0,0.7);
  
  .bottom-btns {
    z-index: 3;
	width: 100vw;
	height: 100%;
    display: flex;
    flex-direction: row;
	align-items: center;
    justify-content: space-around;
	.btn-hangup  {
	  width: 100rpx;
	  height: 100rpx;
	  background: #f75c45;
	  box-sizing: border-box;
	  display: flex;
	  justify-content: center;
	  align-items: center;
	  border-radius: 50%;
	}
  }
}
.btn-normal {
  width: 72rpx;
  height: 72rpx;
  box-sizing: border-box;
  display: flex;
  background: white;
  justify-content: center;
  align-items: center;
  border-radius: 50%;
}
.btn-hangup .btn-image,
.btn-normal .btn-image{
  width: 48rpx;
  height: 48rpx;
}
.swiper{
	position: absolute;
	top: 40%;
	width: 100vw;
	height: 48vh;
	padding: 0 12rpx;
	background-color: #F5F5F5;
	.swiper-item{
		position: relative;
		background-color: #F5F5F5;
		display: flex;
		flex-direction: row;
		// flex-wrap: wrap;
		height: 95% !important;
		.player-container {
			border-radius: 16rpx;
			position: relative;
			margin: 24rpx 12rpx 12rpx 12rpx;
			width: 45%;
			height: 93%;
		}
	}
}
</style>

猜你喜欢

转载自blog.csdn.net/qq_39891453/article/details/127422071