赞
踩
记一下最近做的腾讯实时视频
业务需要,在客户端加一下跳转小程序,在小程序里面进行和pc客户的视频/语音通话。于是乎就用到了腾讯的tcrc实时视频。博客也有很多大佬做过了,我只是班门弄斧,记录一下自己弄的,我这里只列出了小程序的实现方式和代码,pc的没有列出。
pc端效果:
移动端效果
呼叫中:
接通后语音模式:
接通后视频模式:
1,官方demo
下载官方demo,附地址:https://github.com/undefineders/WXMiniByUniapp-TRTC,然后导入本地HbuilderX中,运行到小程序,如果运行成功进行下一步。
填好secret以及sdkid啥的运行到开发者工具,记住这样是不行的,要真机调试才可以看到效果。
2,我做的是uniapp,所以官方的demo对于我来说不能直接用,找了找,果然有大佬,把官方的转成了uniapp版本。
地址:https://ext.dcloud.net.cn/plugin?id=1286。
3,下载下来,删掉不用的(我这里用的是1v1)
这是我的组件目录
要使用人家这个uniapp里面的两个js文件,人家重写了setData方法
组件代码:基本未动,你只需要监控里面的一些操作(如挂断,切换摄像头等),还有更改一些字体图标
<template> <view> <view class="trtc-room-container"> <view v-if="template === '1v1'"> <view data-type="template" data-is="1v1" data-attr="pusher, streamList, debug"> <view class="template-1v1"> <view v-for="(item, streamID) in streamList" :key="streamID" v-if="item.src && (item.hasVideo || item.hasAudio)" :class="'view-container player-container ' + (item.isVisible ? '' : 'none')" > <!-- 对方视频 --> <!-- <live-player class="player" :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="playerStateChangeFun" @fullscreenchange="playerFullscreenChangeFun" @netstatus="playerNetStatusFun" @audiovolumenotify="playerAudioVolumeNotifyFun" :idAttr="item.streamID" ></live-player> --> </view> <!-- 自身画面 --> <view v-show="pusher.enableCamera" :class="'view-container pusher-container ' + (pusher.isVisible ? '' : 'none') + ' ' + (JSON.stringify(streamList) == '[]' ? 'fullscreen' : '')"> <live-pusher class="pusher" :url="pusher.url" :mode="pusher.mode" :autopush="pusher.autopush" :enable-camera="pusher.enableCamera" :enable-mic="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" @statechange="pusherStateChangeHandlerFun" @netstatus="pusherNetStatusHandlerFun" @error="pusherErrorHandlerFun" @bgmstart="pusherBGMStartHandlerFun" @bgmprogress="pusherBGMProgressHandlerFun" @bgmcomplete="pusherBGMCompleteHandlerFun" ></live-pusher> <view class="loading" v-if="streamList.length === 0"> <view class="loading-text">等待接听中...</view> </view> </view> <cover-view v-if="streamList.length != 0" class="calling-time">通话时间 {{ videoOrVoiceObj.msgIPtime.longTime}}</cover-view> <cover-view class="handle-btns" v-if="videoBtnsVisible"> <!--视频/语音 toggleVideoFun --> <cover-view class="box-wrap" v-if="streamList.length != 0"> <cover-view class="btn-normal" @click="toggleVideoFun"> <cover-image class="closeVideoImg" v-if="pusher.enableCamera" :src="closeVideoImg"></cover-image> <cover-image class="videoImg" v-else :src="videoImg"></cover-image> </cover-view> <cover-view class="btn-word">{{pusher.enableCamera?'关闭摄像头':'打开摄像头'}}</cover-view> </cover-view> <!-- 挂断 --> <cover-view class="box-wrap"> <cover-view class="btn-hangup" @click="hangUpFun"><cover-image :src="overImg"></cover-image></cover-view> <cover-view class="btn-word">挂断</cover-view> </cover-view> <!-- 静音 --> <!-- <cover-view class="btn-normal" bindtap="toggleSoundModeFun"> <cover-image :src=" streamList[0].soundMode === 'ear' ? audioImg : videoImg " ></cover-image> </view> --> <!-- 翻转镜头 --> <cover-view class="box-wrap" v-if="streamList.length != 0"> <cover-view class="btn-normal" @click="switchCamera"><cover-image :src="turnCamera"></cover-image></cover-view> <cover-view class="btn-word">翻转镜头</cover-view> </cover-view> </cover-view> <view class="bottom-btns"> </view> </view> </view> </view> </view> </view> </template> <script> import overImg from '@/static/img/挂断.png' import turnCamera from '@/static/img/翻转镜头.png' import audioImg from '@/static/img/电话.png' import videoImg from '@/static/img/摄像头.png' import closeVideoImg from '@/static/img/关闭摄像头.png' import { setData } from './debug/GenerateTestUserSig'; import UserController from './controller/user-controller'; import Pusher from './model/pusher'; import { EVENT } from './common/constants'; import Event from './utils/event'; import * as ENV from './utils/environment'; const TAG_NAME = 'TRTC-ROOM'; export default { data() { return { //几个图片 overImg, turnCamera, audioImg, videoImg, closeVideoImg, //控制视频按钮显隐 videoBtnsVisible:true, //自己写一个计时功能 courentTime:0, videoOrVoiceObj:{ //计时器 msgIPtime:{ timer: "", content: "", hour: 0, minutes: 0, seconds: 0, longTime:'00:00:00' } }, pusher: null, // debugMode: false, // 是否开启调试模式 debugPanel: true, // 是否打开组件调试面板 debug: false, // 是否打开player pusher 的调试信息 streamList: [], // 用于渲染player列表,存储stram userList: [], // 扁平化的数据用来返回给用户 template: '', // 不能设置默认值,当默认值和传入组件的值不一致时,iOS渲染失败 cameraPosition: '', panelName: '', // 控制面板名称,包括 setting-panel memberlist-panel localVolume: 0, remoteVolumeList: [], appVersion: ENV.APP_VERSION, libVersion: ENV.LIB_VERSION, debugMode: '' }; }, components: {}, props: { // 必要的初始化参数 config: { type: Object, default: () => ({ sdkAppID: '', userID: '', userSig: '', template: '', debugMode: '' }) } }, watch: { streamList(newVal, oldVal){ console.log('streamList::::::::',newVal, oldVal) this.streamList = newVal }, config: { handler: function(newVal, oldVal) { console.log('watch config'); this.propertyObserverFun({ name: 'config', newVal, oldVal }); }, deep: true } }, created: function() { // 在组件实例刚刚被创建时执行 console.log(TAG_NAME,'组建内部生命周期created', ENV); }, beforeMount: function() { // 在组件实例进入页面节点树时执行 console.log(TAG_NAME, '组建内部生命周期beforeMount attached'); this.initFun(); }, mounted: function() { // 在组件在视图层布局完成后执行 console.log(TAG_NAME, '组建内部生命周期Mounted ready'); }, destroyed: function() { // 在组件实例被从页面节点树移除时执行 console.log(TAG_NAME, '组建内部生命周期destroyed detached'); // 停止所有拉流,并重置数据 //退房 this.exitRoom(); }, error: function(error) { // 每当组件方法抛出错误时执行 console.log(TAG_NAME, '组建方法报错 error', error); }, onPageShow: function() { // 组件所在的页面被展示时执行 console.log(TAG_NAME, '组建所在页面展示 show status:', this.status); if (this.status.isPending) { // 经历了 5000 挂起事件 this.status.isPending = false; } if (this.status.isPush) { // 小程序hide - show 有一定概率本地黑屏或静止,远端正常,或者远端和本地同时黑屏或静止,需要手动启动预览,非必现 // this.data.pusher.getPusherContext().startPreview() // this.data.pusher.getPusherContext().resume() } }, onPageHide: function() { // 组件所在的页面被隐藏时执行 console.log(TAG_NAME, 'hide'); }, onPageResize: function(size) { // 组件所在的页面尺寸变化时执行 console.log(TAG_NAME, 'resize', size); }, methods: { setData, //隐藏 hiddenBtns(){ console.log('隐藏') this.videoBtnsVisible = false }, //显示 refreshBtns(){ console.log('显示') this.videoBtnsVisible = true }, //我写的计时功能 initTime() { console.log('不执行吗?') /*时间处理*/ this.timer = setInterval(() => { this.courentTime = new Date().getTime(); }, 1000); }, //通话时长 ipVoiceOrVideoLongTimeClick(){ this.videoOrVoiceObj.msgIPtime.timer = setInterval(this.startMsgIPtimeTimer, 1000); }, startMsgIPtimeTimer () { this.videoOrVoiceObj.msgIPtime.seconds += 1; if ( this.videoOrVoiceObj.msgIPtime.seconds >= 60) { this.videoOrVoiceObj.msgIPtime.seconds = 0; this.videoOrVoiceObj.msgIPtime.minutes += 1; } if ( this.videoOrVoiceObj.msgIPtime.minutes >= 60) { this.videoOrVoiceObj.msgIPtime.minutes = 0; this.videoOrVoiceObj.msgIPtime.hour = this.videoOrVoiceObj.msgIPtime.hour + 1; } this.videoOrVoiceObj.msgIPtime.longTime =( this.videoOrVoiceObj.msgIPtime.hour < 10 ? '0' + this.videoOrVoiceObj.msgIPtime.hour : this.videoOrVoiceObj.msgIPtime.hour) + ':'+ ( this.videoOrVoiceObj.msgIPtime.minutes < 10 ? '0' + this.videoOrVoiceObj.msgIPtime.minutes : this.videoOrVoiceObj.msgIPtime.minutes) + ':' + ( this.videoOrVoiceObj.msgIPtime.seconds < 10 ? '0' + this.videoOrVoiceObj.msgIPtime.seconds : this.videoOrVoiceObj.msgIPtime.seconds); }, resetMsgIPtime(){ //重置 clearInterval(this.videoOrVoiceObj.msgIPtime.timer); this.videoOrVoiceObj.msgIPtime.hour=0; this.videoOrVoiceObj.msgIPtime.minute=0; this.videoOrVoiceObj.msgIPtime.ms=0; this.videoOrVoiceObj.msgIPtime.seconds=0; this.videoOrVoiceObj.msgIPtime.longTime="00:00:00"; }, stopMsgIPtime(){ //暂停 clearInterval(this.videoOrVoiceObj.msgIPtime.timer); }, /** * 初始化各项参数和用户控制模块,在组件实例触发 attached 时调用,此时不建议对View进行变更渲染(调用setData方法) */ initFun: function() { console.log(TAG_NAME, '初始化各参数 _init'); this.userController = new UserController(this); this._emitter = new Event(); this.EVENT = EVENT; //初始化推流拉流状态 this.initStatusFun(); //远端用户和音视频状态处理 this.bindEventFun(); //grid布局, 绑定事件 this.bindEventGridFun(); console.log(TAG_NAME, '初始化参数成功 _init success component:', this); }, /** * 进房 * @param {Object} params 必传 roomID 取值范围 1 ~ 4294967295 * @returns {Promise} */ enterRoom: function(params) { return new Promise((resolve, reject) => { // this.pusher = {} console.log(TAG_NAME, 'enterRoom'); console.log(TAG_NAME, 'params', params); console.log(TAG_NAME, 'config', this.config); console.log(TAG_NAME, 'pusher', this.pusher); // 1. 补齐进房参数,校验必要参数是否齐全 console.log('进房......', params, this.config, this.pusher); if (params) { Object.assign(this.pusher, params); Object.assign(this.config, params); } if (!this.checkParamFun(this.config)) { reject(new Error('缺少必要参数')); return; } // 2. 根据参数拼接 push url,赋值给 live-pusher, console.log('解决',this.config) this.getPushUrlFun(this.config) .then(pushUrl => { this.pusher.url = pushUrl; this.setData( { pusher: this.pusher, }, () => { console.log(TAG_NAME, '进房成功', this.pusher); // view 渲染成功回调后,开始推流 this.pusher.getPusherContext().start(); this.status.isPush = true; resolve(); } ); }) .catch(res => { // 获取 room sig 失败, 进房失败需要通过 pusher state 事件通知 console.error(TAG_NAME, '进房失败', res); reject(res); }); }); }, /** * 退房,停止推流和拉流,并重置数据 * @returns {Promise} */ exitRoom: function() { return new Promise((resolve, reject) => { console.log(TAG_NAME, '退出房间'); this.pusher.reset(); this.status.isPush = false; const result = this.userController.reset(); this.setData( { pusher: this.pusher, userList: result.userList, streamList: result.streamList }, () => { // 在销毁页面时调用,不会走到这里 resolve({ userList: this.userList, streamList: this.streamList }); console.log(TAG_NAME, '退出房间成功', this.pusher, this.streamList, this.userList); } ); }); }, /** * 开启摄像头 * @returns {Promise} */ publishLocalVideo: function() { // 设置 pusher enableCamera console.log(TAG_NAME, '这里这里publishLocalVideo 开启摄像头',this.pusher,this.cameraPosition); return this.setPusherConfigFun({ enableCamera: true, }); }, /** * 关闭摄像头 * @returns {Promise} */ unpublishLocalVideo: function() { // 设置 pusher enableCamera console.log(TAG_NAME, 'unpublshLocalVideo 关闭摄像头'); return this.setPusherConfigFun({ enableCamera: false }); }, /** * 开启麦克风 * @returns {Promise} */ publishLocalAudio: function() { // 设置 pusher enableCamera console.log(TAG_NAME, 'publishLocalAudio 开启麦克风'); return this.setPusherConfigFun({ enableMic: true }); }, /** * 关闭麦克风 * @returns {Promise} */ unpublishLocalAudio: function() { // 设置 pusher enableCamera console.log(TAG_NAME, 'unpublshLocalAudio 关闭麦克风'); return this.setPusherConfigFun({ enableMic: false }); }, /** * 订阅远端视频 主流 小画面 辅流 * @param {Object} params {userID,streamType} streamType 传入 small 时修改对应的主流url的 streamtype 参数为small * @returns {Promise} */ subscribeRemoteVideo(params) { console.log(TAG_NAME, '订阅远端视频', params); // 设置指定 user streamType 的 muteVideo 为 false const config = { muteVideo: false }; // 本地数据结构里的 streamType 只支持 main 和 aux ,订阅small 也是对main进行处理 const streamType = params.streamType === 'small' ? 'main' : params.streamType; if (params.streamType === 'small' || params.streamType === 'main') { const stream = this.userController.getStream({ userID: params.userID, streamType: streamType }); if (stream && stream.streamType === 'main') { console.log(TAG_NAME, 'subscribeRemoteVideo switch small', stream.src); if (params.streamType === 'small') { config.src = stream.src.replace('main', 'small'); config._definitionType = 'small'; // 用于设置面板的渲染 } else if (params.streamType === 'main') { stream.src = stream.src.replace('small', 'main'); config._definitionType = 'main'; } console.log(TAG_NAME, 'subscribeRemoteVideo', stream.src); } } return this.setPlayerConfigFun({ userID: params.userID, streamType: streamType, config: config }); }, /** * 取消订阅远端视频 * @param {Object} params {userID,streamType} * @returns {Promise} */ unsubscribeRemoteVideo(params) { console.log(TAG_NAME, '取消订阅远端视频', params); // 设置指定 user streamType 的 muteVideo 为 true return this.setPlayerConfigFun({ userID: params.userID, streamType: params.streamType, config: { muteVideo: true } }); }, /** * 订阅远端音频 * @param {Object} params userID 用户ID * @returns {Promise} */ subscribeRemoteAudio(params) { console.log(TAG_NAME, '订阅远端音频', params); return this.setPlayerConfigFun({ userID: params.userID, streamType: 'main', config: { muteAudio: false } }); }, /** * 取消订阅远端音频 * @param {Object} params userID 用户ID * @returns {Promise} */ unsubscribeRemoteAudio(params) { console.log(TAG_NAME, '取消订阅远端音频', params); return this.setPlayerConfigFun({ userID: params.userID, streamType: 'main', config: { muteAudio: true } }); }, on: function(eventCode, handler, context) { this._emitter.on(eventCode, handler, context); }, off: function(eventCode, handler) { this._emitter.off(eventCode, handler); }, getRemoteUserList: function() { return this.userList; }, /** * 切换前后摄像头 */ switchCamera: function(val) { console.log('点击了翻转镜头',val) if (!this.cameraPosition) { // this.data.pusher.cameraPosition 是初始值,不支持动态设置 this.cameraPosition = this.pusher.frontCamera; } console.log(TAG_NAME, '翻转镜头', this.cameraPosition); this.cameraPosition = this.cameraPosition === 'front' ? 'back' : 'front'; this.setData( { cameraPosition: this.cameraPosition }, () => { console.log(TAG_NAME, '翻转镜头 成功', this.cameraPosition); } ); // wx 7.0.9 不支持动态设置 pusher.devicePosition ,需要调用api设置,这里修改cameraPosition是为了记录状态 this.pusher.getPusherContext().switchCamera(); }, /** * 设置指定player view的渲染坐标和尺寸 * @param {object} params * userID: string * streamType: string * xAxis: number * yAxis: number * width: number * height: number * @returns {Promise} */ setViewRect: function(params) { console.log(TAG_NAME, 'setViewRect', params); if (this.pusher.template !== 'custom') { console.warn(`如需使用setViewRect方法,请设置template:"custom", 当前 template:"${this.pusher.template}"`); } if (this.pusher.userID === params.userID) { return this.setPusherConfigFun({ xAxis: params.xAxis, yAxis: params.yAxis, width: params.width, height: params.height }); } return this.setPlayerConfigFun({ userID: params.userID, streamType: params.streamType, config: { xAxis: params.xAxis, yAxis: params.yAxis, width: params.width, height: params.height } }); }, /** * 设置指定 player 或者 pusher view 是否可见 * @param {object} params * userID: string * streamType: string * isVisible:boolean * @returns {Promise} */ setViewVisible: function(params) { console.log(TAG_NAME, 'setViewVisible', params); // if (this.data.pusher.template !== 'custom') { // console.warn(`如需使用setViewVisible方法,请设置template:"custom", 当前 template:"${this.data.pusher.template}"`) // } if (this.pusher.userID === params.userID) { return this.setPusherConfigFun({ isVisible: params.isVisible }); } return this.setPlayerConfigFun({ userID: params.userID, streamType: params.streamType, config: { isVisible: params.isVisible } }); }, /** * 设置指定player view的层级 * @param {Object} params * userID: string * streamType: string * zindex: number * @returns {Promise} */ setViewZIndex: function(params) { console.log(TAG_NAME, 'setViewZIndex', params); if (this.pusher.template !== 'custom') { console.warn(`如需使用setViewZIndex方法,请设置template:"custom", 当前 template:"${this.pusher.template}"`); } if (this.pusher.userID === params.userID) { return this.setPusherConfigFun({ zindex: params.zindex }); } return this.setPlayerConfigFun({ userID: params.userID, streamType: params.streamType, config: { zindex: params.zindex } }); }, /** * 播放背景音 * @param {Object} params url * @returns {Promise} */ playBGM: function(params) { return new Promise((resolve, reject) => { this.pusher.getPusherContext().playBGM({ url: params.url, // 已经有相关事件不需要在这里监听,目前用于测试 success: () => { console.log(TAG_NAME, '播放背景音成功'); // this._emitter.emit(EVENT.BGM_PLAY_START) resolve(); }, fail: () => { console.log(TAG_NAME, '播放背景音失败'); this._emitter.emit(EVENT.BGM_PLAY_FAIL); reject(new Error('播放背景音失败')); } // complete: () => { // console.log(TAG_NAME, '背景完成') // this._emitter.emit(EVENT.BGM_PLAY_COMPLETE) // }, }); }); }, stopBGM: function() { this.pusher.getPusherContext().stopBGM(); }, pauseBGM: function() { this.pusher.getPusherContext().pauseBGM(); }, resumeBGM: function() { this.pusher.getPusherContext().resumeBGM(); }, /** * 设置背景音音量 * @param {Object} params volume */ setBGMVolume: function(params) { this.pusher.getPusherContext().setBGMVolume({ volume: params.volume }); }, /** * 设置麦克风音量 * @param {Object} params volume */ setMICVolume: function(params) { this.pusher.getPusherContext().setMICVolume({ volume: params.volume }); }, /** * 发送SEI消息 * @param {Object} params message * @returns {Promise} */ sendSEI: function(params) { return new Promise((resolve, reject) => { this.pusher.getPusherContext().sendMessage({ msg: params.message, success: function(result) { resolve(result); } }); }); }, /** * pusher 和 player 的截图并保存 * @param {Object} params userID streamType * @returns {Promise} */ snapshot: function(params) { console.log(TAG_NAME, 'snapshot', params); return new Promise((resolve, reject) => { this.captureSnapshot(params) .then(result => { wx.saveImageToPhotosAlbum({ filePath: result.tempImagePath, success(res) { wx.showToast({ title: '已保存到相册' }); console.log('save photo is success', res); resolve(result); }, fail: function(error) { wx.showToast({ icon: 'none', title: '保存失败' }); console.log('save photo is fail', error); reject(error); } }); }) .catch(error => { reject(error); }); }); }, /** * 获取pusher 和 player 的截图 * @param {Object} params userID streamType * @returns {Promise} */ captureSnapshot: function(params) { return new Promise((resolve, reject) => { if (params.userID === this.pusher.userID) { // pusher this.pusher.getPusherContext().snapshot({ quality: 'raw', complete: result => { console.log(TAG_NAME, 'snapshot pusher', result); if (result.tempImagePath) { resolve(result); } else { console.log('snapShot 回调失败', result); reject(new Error('截图失败')); } } }); } else { // player this.userController.getStream(params).playerContext.snapshot({ quality: 'raw', complete: result => { console.log(TAG_NAME, 'snapshot player', result); if (result.tempImagePath) { resolve(result); } else { console.log('snapShot 回调失败', result); reject(new Error('截图失败')); } } }); } }); }, /** * 将远端视频全屏 * @param {Object} params userID streamType direction * @returns {Promise} */ enterFullscreen: function(params) { console.log(TAG_NAME, 'enterFullscreen', params); return new Promise((resolve, reject) => { this.userController.getStream(params).playerContext.requestFullScreen({ direction: params.direction || 0, success: event => { console.log(TAG_NAME, 'enterFullscreen success', event); resolve(event); }, fail: event => { console.log(TAG_NAME, 'enterFullscreen fail', event); reject(event); } }); }); }, /** * 将远端视频取消全屏 * @param {Object} params userID streamType * @returns {Promise} */ exitFullscreen: function(params) { console.log(TAG_NAME, 'exitFullscreen', params); return new Promise((resolve, reject) => { this.userController.getStream(params).playerContext.exitFullScreen({ success: event => { console.log(TAG_NAME, 'exitFullScreen success', event); resolve(event); }, fail: event => { console.log(TAG_NAME, 'exitFullScreen fail', event); reject(event); } }); }); }, /** * 设置 player 视图的横竖屏显示 * @param {Object} params userID streamType orientation: vertical, horizontal * @returns {Promise} */ setRemoteOrientation: function(params) { return this.setPlayerConfigFun({ userID: params.userID, streamType: params.streamType, config: { orientation: params.orientation } }); }, // 改为: setViewOrientation: function(params) { return this.setPlayerConfigFun({ userID: params.userID, streamType: params.streamType, config: { orientation: params.orientation } }); }, /** * 设置 player 视图的填充模式 * @param {Object} params userID streamType fillMode: contain,fillCrop * @returns {Promise} */ setRemoteFillMode: function(params) { return this.setPlayerConfigFun({ userID: params.userID, streamType: params.streamType, config: { objectFit: params.fillMode } }); }, // 改为: setViewFillMode: function(params) { return this.setPlayerConfigFun({ userID: params.userID, streamType: params.streamType, config: { objectFit: params.fillMode } }); }, /** * 切换 player 大小画面 * @param {Object} params userID streamType definition: HD SD * @returns {Promise} */ setRemoteDefinitionFun: function(params) { params.streamType = 'main'; return new Promise((resolve, reject) => { const stream = this.userController.getStream({ userID: params.userID, streamType: params.streamType }); if (stream && stream.streamType === 'main') { console.log(TAG_NAME, '_switchStreamType', stream.src); // stream.volume = volume if (stream.src.indexOf('main') > -1) { stream.src = stream.src.replace('main', 'small'); stream._streamType = 'small'; // 用于设置面板的渲染 } else if (stream.src.indexOf('small') > -1) { stream.src = stream.src.replace('small', 'main'); stream._streamType = 'main'; } console.log(TAG_NAME, '_switchStreamType', stream.src); this.setData( { streamList: this.streamList }, () => {} ); } }); }, initStatusFun() { this.status = { isPush: false, // 推流状态 isPending: false // 挂起状态,触发5000事件标记为true,onShow后标记为false }; this._lastTapTime = 0; this._beforeLastTapTime = 0; this._isFullscreen = false; }, /** * 设置推流参数并触发页面渲染更新 * @param {Object} config live-pusher 的配置 * @returns {Promise} */ setPusherConfigFun(config) { console.log(TAG_NAME, '_setPusherConfig', config, this.pusher); return new Promise((resolve, reject) => { if (!this.pusher) { this.pusher = new Pusher(config); } else { Object.assign(this.pusher, config); } this.setData( { pusher: this.pusher }, () => { // console.log(TAG_NAME, '_setPusherConfig setData compelete', 'config:', config, 'pusher:', this.data.pusher) resolve(config); } ); }); }, /** * * @param {Object} params include userID,streamType,config * @returns {Promise} */ setPlayerConfigFun(params) { const userID = params.userID; const streamType = params.streamType; const config = params.config; console.log(TAG_NAME, '_setPlayerConfig', params); return new Promise((resolve, reject) => { // 获取指定的userID streamType 的 stream const user = this.userController.getUser(userID); if (user && user.streams[streamType]) { user.streams[streamType] = Object.assign(user.streams[streamType], config); // user.streams引用的对象和 streamList 里的是同一个 this.setData( { streamList: this.streamList }, () => { // console.log(TAG_NAME, '_setPlayerConfig complete', params, 'streamList:', this.data.streamList) resolve(params); } ); } else { // 不需要reject,静默处理 console.warn(TAG_NAME, '指定 userID 或者 streamType 不存在'); // reject(new Error('指定 userID 或者 streamType 不存在')) } }); }, /** * 必选参数检测 * @param {Object} rtcConfig rtc参数 * @returns {Boolean} */ checkParamFun: function(rtcConfig) { console.log(TAG_NAME, 'checkParam config:', rtcConfig); if (!rtcConfig.sdkAppID) { console.error('未设置 sdkAppID'); return false; } if (rtcConfig.roomID === undefined) { console.error('未设置 roomID'); return false; } if (rtcConfig.roomID < 1 || rtcConfig.roomID > 4294967296) { console.error('roomID 超出取值范围 1 ~ 4294967295'); return false; } if (!rtcConfig.userID) { console.error('未设置 userID'); return false; } if (!rtcConfig.userSig) { console.error('未设置 userSig'); return false; } if (!rtcConfig.template) { console.error('未设置 template'); return false; } return true; }, getPushUrlFun: function(rtcConfig) { // 拼接 puhser url rtmp 方案 console.log(TAG_NAME, 'getPushUrl', rtcConfig); if (ENV.IS_TRTC) { // 版本高于7.0.8,基础库版本高于2.10.0 使用新的 url return new Promise((resolve, reject) => { // appscene videocall live // cloudenv PRO CCC DEV UAT // encsmall 0 // 对外的默认值是rtc ,对内的默认值是videocall rtcConfig.scene = !rtcConfig.scene || rtcConfig.scene === 'rtc' ? 'videocall' : 'live'; rtcConfig.enableBlackStream = rtcConfig.enableBlackStream || 1; rtcConfig.encsmall = rtcConfig.encsmall || 0; rtcConfig.cloudenv = rtcConfig.cloudenv || 'PRO'; setTimeout(() => { const pushUrl = 'room://cloud.tencent.com/rtc?sdkappid=' + rtcConfig.sdkAppID + '&roomid=' + rtcConfig.roomID + '&userid=' + rtcConfig.userID + '&usersig=' + rtcConfig.userSig + '&appscene=' + rtcConfig.scene + '&encsmall=' + rtcConfig.encsmall + '&cloudenv=' + rtcConfig.cloudenv; console.log(TAG_NAME, 'getPushUrl result:', pushUrl); resolve(pushUrl); }, 0); }); } return this.requestSigServerFun(rtcConfig); }, /** * 获取签名和推流地址 * @param {Object} rtcConfig 进房参数配置 * @returns {Promise} */ requestSigServerFun: function(rtcConfig) { console.log('requestSigServer:', rtcConfig); const sdkAppID = rtcConfig.sdkAppID; const userID = rtcConfig.userID; const userSig = rtcConfig.userSig; const roomID = rtcConfig.roomID; const privateMapKey = rtcConfig.privateMapKey; rtcConfig.useCloud = rtcConfig.useCloud === undefined ? true : rtcConfig.useCloud; let url = rtcConfig.useCloud ? 'https://official.opensso.tencent-cloud.com/v4/openim/jsonvideoapp' : 'https://yun.tim.qq.com/v4/openim/jsonvideoapp'; url += '?sdkappid=' + sdkAppID + '&identifier=' + userID + '&usersig=' + userSig + '&random=' + Date.now() + '&contenttype=json'; const reqHead = { Cmd: 1, SeqNo: 1, BusType: 7, GroupId: roomID }; const reqBody = { PrivMapEncrypt: privateMapKey, TerminalType: 1, FromType: 3, SdkVersion: 26280566 }; console.log('requestSigServer:', url, reqHead, reqBody); return new Promise((resolve, reject) => { wx.request({ url: url, data: { ReqHead: reqHead, ReqBody: reqBody }, method: 'POST', success: res => { console.log('requestSigServer success:', res); if (res.data['ErrorCode'] || res.data['RspHead']['ErrorCode'] !== 0) { // console.error(res.data['ErrorInfo'] || res.data['RspHead']['ErrorInfo']) console.error('获取roomsig失败'); reject(res); } const roomSig = JSON.stringify(res.data['RspBody']); let pushUrl = 'room://cloud.tencent.com?sdkappid=' + sdkAppID + '&roomid=' + roomID + '&userid=' + userID + '&roomsig=' + encodeURIComponent(roomSig); // TODO 需要重新整理的逻辑 // 如果有配置纯音频推流或者recordId参数 if (rtcConfig.pureAudioPushMod || rtcConfig.recordId) { const bizbuf = { Str_uc_params: { pure_audio_push_mod: 0, record_id: 0 } }; // 纯音频推流 if (rtcConfig.pureAudioPushMod) { bizbuf.Str_uc_params.pure_audio_push_mod = rtcConfig.pureAudioPushMod; } else { delete bizbuf.Str_uc_params.pure_audio_push_mod; } // 自动录制时业务自定义id if (rtcConfig.recordId) { bizbuf.Str_uc_params.record_id = rtcConfig.recordId; } else { delete bizbuf.Str_uc_params.record_id; } pushUrl += '&bizbuf=' + encodeURIComponent(JSON.stringify(bizbuf)); } console.log('roomSigInfo', pushUrl); resolve(pushUrl); }, fail: res => { console.log('requestSigServer fail:', res); reject(res); } }); }); }, doubleTabToggleFullscreenFun: function(event) { const curTime = event.timeStamp; const lastTime = this._lastTapTime; // 已知问题:上次全屏操作后,必须等待1.5s后才能再次进行全屏操作,否则引发SDK全屏异常,因此增加节流逻辑 const beforeLastTime = this._beforeLastTapTime; console.log(TAG_NAME, 'doubleTabToggleFullscreenFun', event, lastTime, beforeLastTime); if (curTime - lastTime > 0 && curTime - lastTime < 300 && lastTime - beforeLastTime > 1500) { const userID = event.currentTarget.dataset.userid; const streamType = event.currentTarget.dataset.streamtype; if (this._isFullscreen) { this.exitFullscreen({ userID, streamType }) .then(() => { this._isFullscreen = false; }) .catch(() => {}); } else { // const stream = this.userController.getStream({ userID, streamType }) let direction; // // 已知问题:视频的尺寸需要等待player触发NetStatus事件才能获取到,如果进房就双击全屏,全屏后的方向有可能不对。 // if (stream && stream.videoWidth && stream.videoHeight) { // // 如果是横视频,全屏时进行横屏处理。如果是竖视频,则为0 // direction = stream.videoWidth > stream.videoHeight ? 90 : 0 // } this.enterFullscreen({ userID, streamType, direction }) .then(() => { this._isFullscreen = true; }) .catch(() => {}); } this._beforeLastTapTime = lastTime; } this._lastTapTime = curTime; }, /** * TRTC-room 远端用户和音视频状态处理 */ bindEventFun: function() { // 远端用户进房 this.userController.on(EVENT.REMOTE_USER_JOIN, event => { console.log(TAG_NAME, '远端用户进房', event, event.data.userID); this.setData( { userList: event.data.userList }, () => { this._emitter.emit(EVENT.REMOTE_USER_JOIN, { userID: event.data.userID }); },this ); console.log(TAG_NAME, 'REMOTE_USER_JOIN', 'streamList:', this.streamList, 'userList:', this.userList); }); // 远端用户离开 this.userController.on(EVENT.REMOTE_USER_LEAVE, event => { console.log(TAG_NAME, '远端用户离开', event, event.data.userID); if (event.data.userID) { this.setData( { userList: event.data.userList, streamList: event.data.streamList }, () => { this._emitter.emit(EVENT.REMOTE_USER_LEAVE, { userID: event.data.userID }); },this ); } console.log(TAG_NAME, 'REMOTE_USER_LEAVE', 'streamList:', this.streamList, 'userList:', this.userList); }); // 视频状态 true this.userController.on(EVENT.REMOTE_VIDEO_ADD, event => { console.log(TAG_NAME, '远端视频可用', event, event.data.stream.userID); const stream = event.data.stream; this.setData( { userList: event.data.userList, streamList: event.data.streamList }, () => { // 完善 的stream 的 playerContext stream.playerContext = wx.createLivePlayerContext(stream.streamID, this); // 新增的需要触发一次play 默认属性才能生效 // stream.playerContext.play() // console.log(TAG_NAME, 'REMOTE_VIDEO_ADD playerContext.play()', stream) // TODO 视频通话模版默认订阅且显示 this._emitter.emit(EVENT.REMOTE_VIDEO_ADD, { userID: stream.userID, streamType: stream.streamType }); },this ); console.log(TAG_NAME, 'REMOTE_VIDEO_ADD', 'streamList:', this.streamList, 'userList:', this.userList); }); // 视频状态 false this.userController.on(EVENT.REMOTE_VIDEO_REMOVE, event => { console.log(TAG_NAME, '远端视频移除', event, event.data.stream.userID); const stream = event.data.stream; this.setData( { userList: event.data.userList, streamList: event.data.streamList }, () => { // 有可能先触发了退房事件,用户名下的所有stream都已清除 if (stream.userID && stream.streamType) { this._emitter.emit(EVENT.REMOTE_VIDEO_REMOVE, { userID: stream.userID, streamType: stream.streamType }); } },this ); console.log(TAG_NAME, 'REMOTE_VIDEO_REMOVE', 'streamList:', this.streamList, 'userList:', this.userList); }); // 音频可用 this.userController.on(EVENT.REMOTE_AUDIO_ADD, event => { console.log(TAG_NAME, '远端音频可用', event); const stream = event.data.stream; this.setData( { userList: event.data.userList, streamList: event.data.streamList }, () => { stream.playerContext = wx.createLivePlayerContext(stream.streamID, this); // 新增的需要触发一次play 默认属性才能生效 // stream.playerContext.play() // console.log(TAG_NAME, 'REMOTE_AUDIO_ADD playerContext.play()', stream) this._emitter.emit(EVENT.REMOTE_AUDIO_ADD, { userID: stream.userID, streamType: stream.streamType }); },this ); console.log(TAG_NAME, 'REMOTE_AUDIO_ADD', 'streamList:', this.streamList, 'userList:', this.userList); }); // 音频不可用 this.userController.on(EVENT.REMOTE_AUDIO_REMOVE, event => { console.log(TAG_NAME, '远端音频移除', event, event.data.stream.userID); const stream = event.data.stream; this.setData( { userList: event.data.userList, streamList: event.data.streamList }, () => { // 有可能先触发了退房事件,用户名下的所有stream都已清除 if (stream.userID && stream.streamType) { this._emitter.emit(EVENT.REMOTE_AUDIO_REMOVE, { userID: stream.userID, streamType: stream.streamType }); } },this ); console.log(TAG_NAME, 'REMOTE_AUDIO_REMOVE', 'streamList:', this.streamList, 'userList:', this.userList); }); }, /** * pusher event handler * @param {*} event 事件实例 */ pusherStateChangeHandlerFun: function(event) { const code = event.detail.code; const message = event.detail.message; console.log(TAG_NAME, 'pusherStateChange:', code, event); switch (code) { case 0: console.log(message, code); break; case 1001: console.log('已经连接推流服务器', code); break; case 1002: console.log('已经与服务器握手完毕,开始推流', code); break; case 1003: console.log('打开摄像头成功', code); break; case 1004: console.log('录屏启动成功', code); break; case 1005: console.log('推流动态调整分辨率', code); break; case 1006: console.log('推流动态调整码率', code); break; case 1007: console.log('首帧画面采集完成', code); break; case 1008: console.log('编码器启动', code); break; case 1018: console.log('进房成功', code); this._emitter.emit(EVENT.LOCAL_JOIN, { userID: this.pusher.userID }); //进房成功 手动默认关闭摄像头 转后置 this.switchCamera() this.unpublishLocalVideo() // //计时功能 this.initTime() this.ipVoiceOrVideoLongTimeClick() break; case 1019: console.log('退出房间', code); this._emitter.emit(EVENT.LOCAL_LEAVE, { userID: this.pusher.userID }); break; case 2003: console.log('渲染首帧视频', code); break; case 1020: case 1031: case 1032: case 1033: case 1034: // 通过 userController 处理 1020 1031 1032 1033 1034 this.userController.userEventHandler(event); break; case -1301: console.error('打开摄像头失败: ', code); this._emitter.emit(EVENT.ERROR, { code, message }); break; case -1302: console.error('打开麦克风失败: ', code); this._emitter.emit(EVENT.ERROR, { code, message }); break; case -1303: console.error('视频编码失败: ', code); this._emitter.emit(EVENT.ERROR, { code, message }); break; case -1304: console.error('音频编码失败: ', code); this._emitter.emit(EVENT.ERROR, { code, message }); break; case -1307: console.error('推流连接断开: ', code); this._emitter.emit(EVENT.ERROR, { code, message }); break; case -100018: console.error('进房失败: ', code, message); this._emitter.emit(EVENT.ERROR, { code, message }); break; case 5000: console.log('小程序被挂起: ', code); // 终端 sdk 建议执行退房操作,唤起时重新进房,临时解决方案,待小程序SDK完全实现自动重新推流后可以去掉 this.status.isPending = true; if (this.status.isPush) { // this.exitRoom() const tempUrl = this.pusher.url; this.pusher.url = ''; // console.log('5000 小程序被挂起后更换pusher', this.data.pusher.getPusherContext().webviewId) this.setData( { pusher: this.pusher }, () => { this.pusher.url = tempUrl; this.setData( { pusher: this.pusher }, () => { this.pusher.getPusherContext().start(); console.log('5000 小程序被挂起后更换pusher', this.pusher); } ); } ); } break; case 1021: console.log('网络类型发生变化,需要重新进房', code); break; case 2007: console.log('本地视频播放loading: ', code); break; case 2004: console.log('本地视频播放开始: ', code); break; default: console.log(message, code); } this._emitter.emit(EVENT.LOCAL_STATE_UPDATE, { data: event }); }, pusherNetStatusHandlerFun: function(event) { // 触发 LOCAL_NET_STATE_UPDATE this._emitter.emit(EVENT.LOCAL_NET_STATE_UPDATE, event); }, pusherErrorHandlerFun: function(event) { // 触发 ERROR console.warn(TAG_NAME, 'pusher error', event); try { const code = event.detail.errCode; const message = event.detail.errMsg; this._emitter.emit(EVENT.ERROR, { code, message }); } catch (exception) { console.error(TAG_NAME, 'pusher error data parser exception', event, exception); } }, pusherBGMStartHandlerFun: function(event) { // 触发 BGM_START 已经在playBGM方法中进行处理 // this._emitter.emit(EVENT.BGM_PLAY_START, { data: event }) }, pusherBGMProgressHandlerFun: function(event) { // BGM_PROGRESS this._emitter.emit(EVENT.BGM_PLAY_PROGRESS, event); }, pusherBGMCompleteHandlerFun: function(event) { // BGM_COMPLETE this._emitter.emit(EVENT.BGM_PLAY_COMPLETE, event); }, // player event handler // 获取 player ID 再进行触发 playerStateChangeFun: function(event) { // console.log(TAG_NAME, 'playerStateChangeFun', event) this._emitter.emit(EVENT.REMOTE_STATE_UPDATE, event); }, playerFullscreenChangeFun: function(event) { // console.log(TAG_NAME, '_playerFullscreenChange', event) this._emitter.emit(EVENT.REMOTE_NET_STATE_UPDATE, event); }, playerNetStatusFun: function(event) { // console.log(TAG_NAME, 'playerNetStatusFun', event) // 获取player 视频的宽高 const stream = this.userController.getStream({ userID: event.currentTarget.dataset.userid, streamType: event.currentTarget.dataset.streamtype }); if (stream && (stream.videoWidth !== event.detail.info.videoWidth || stream.videoHeight !== event.detail.info.videoHeight)) { console.log(TAG_NAME, 'playerNetStatusFun update video size', event); stream.videoWidth = event.detail.info.videoWidth; stream.videoHeight = event.detail.info.videoHeight; } this._emitter.emit(EVENT.REMOTE_FULLSCREEN_UPDATE, event); }, playerAudioVolumeNotifyFun: function(event) { // console.log(TAG_NAME, 'playerAudioVolumeNotifyFun', event) this._emitter.emit(EVENT.REMOTE_AUDIO_VOLUME_UPDATE, event); }, /** * 监听组件属性变更,外部变更组件属性时触发该监听,用于检查属性设置是否正常 * @param {Object} data 变更数据 */ propertyObserverFun: function(data) { console.log(TAG_NAME, '_propertyObserver', data, this.config); if (data.name === 'config') { // const config = Object.assign(DEFAULT_PUSHER_CONFIG, data.newVal) const config = data.newVal; // querystring 只支持String类型,做一个类型防御 if (typeof config.debugMode === 'string') { config.debugMode === 'true' ? true : false; } // 独立设置与pusher无关的配置 this.setData({ template: config.template, debugMode: config.debugMode || false, debug: config.debugMode || false }); this.setPusherConfigFun(config); } }, toggleVideoFun() { if (this.pusher.enableCamera) { this.unpublishLocalVideo(); } else { this.publishLocalVideo(); } }, toggleAudioFun() { if (this.pusher.enableMic) { this.unpublishLocalAudio(); } else { this.publishLocalAudio(); } }, debugToggleRemoteVideoFun(event) { console.log(TAG_NAME, '_debugToggleRemoteVideo', event.currentTarget.dataset); const userID = event.currentTarget.dataset.userID; const streamType = event.currentTarget.dataset.streamType; const stream = this.streamList.find(item => { return item.userID === userID && item.streamType === streamType; }); if (stream.muteVideo) { this.subscribeRemoteVideo({ userID, streamType }); this.setViewVisible({ userID, streamType, isVisible: true }); } else { this.unsubscribeRemoteVideo({ userID, streamType }); this.setViewVisible({ userID, streamType, isVisible: false }); } }, debugToggleRemoteAudioFun(event) { console.log(TAG_NAME, '_debugToggleRemoteAudio', event.currentTarget.dataset); const userID = event.currentTarget.dataset.userID; const streamType = event.currentTarget.dataset.streamType; const stream = this.streamList.find(item => { return item.userID === userID && item.streamType === streamType; }); if (stream.muteAudio) { this.subscribeRemoteAudio({ userID }); } else { this.unsubscribeRemoteAudio({ userID }); } }, debugToggleVideoDebugFun() { this.setData({ debug: !this.debug }); }, debugExitRoomFun() { this.exitRoom(); }, debugEnterRoomFun() { this.publishLocalVideo(); this.publishLocalAudio(); this.enterRoom({ roomID: this.config.roomID }).then(() => { // 进房后开始推送视频或音频 }); }, debugGoBackFun() { wx.navigateBack({ delta: 1 }); }, debugTogglePanelFun() { this.setData({ debugPanel: !this.debugPanel }); }, toggleAudioVolumeTypeFun() { if (this.pusher.audioVolumeType === 'voicecall') { this.setPusherConfigFun({ audioVolumeType: 'media' }); } else { this.setPusherConfigFun({ audioVolumeType: 'voicecall' }); } }, toggleSoundModeFun(val) { console.log('点击了切换语音/视频',val) if (this.userList.length === 0) { return; } const stream = this.userController.getStream({ userID: this.userList[0].userID, streamType: 'main' }); if (stream) { if (stream.soundMode === 'speaker') { stream['soundMode'] = 'ear'; } else { stream['soundMode'] = 'speaker'; } this.setPlayerConfigFun({ userID: stream.userID, streamType: 'main', config: { soundMode: stream['soundMode'] } }); } }, /** * 退出通话 */ hangUpFun: function() { console.log('点击了挂断') // this.exitRoom(); uni.reLaunch({ url: '/pages/callOver/index', fail: e=>{ uni.switchTab({ url:'/pages/callOver/index' }) } }) setTimeout(()=>{ this.exitRoom(); //停止定时器,重置计时时间 this.resetMsgIPtime() this.stopMsgIPtime() },300) // uni.navigateBack({ // delta: 1 // }); }, /** * 切换订阅音频状态 */ handleSubscribeAudio: function() { if (this.pusher.enableMic) { this.unpublishLocalAudio(); } else { this.publishLocalAudio(); } }, /** * 切换订阅远端视频状态 * @param event */ handleSubscribeRemoteVideoFun: function(event) { const userID = event.currentTarget.dataset.userID; const streamType = event.currentTarget.dataset.streamType; const stream = this.streamList.find(item => { return item.userID === userID && item.streamType === streamType; }); if (stream.muteVideo) { this.subscribeRemoteVideo({ userID, streamType }); } else { this.unsubscribeRemoteVideo({ userID, streamType }); } }, /** * 将远端视频取消全屏 * @param event */ handleSubscribeRemoteAudioFun: function(event) { const userID = event.currentTarget.dataset.userID; const streamType = event.currentTarget.dataset.streamType; const stream = this.streamList.find(item => { return item.userID === userID && item.streamType === streamType; }); if (stream.muteAudio) { this.subscribeRemoteAudio({ userID }); } else { this.unsubscribeRemoteAudio({ userID }); } }, /** * grid布局, 唤起 memberlist-panel */ switchMemberListPanelFun() { this.setData({ panelName: this.panelName !== 'memberlist-panel' ? 'memberlist-panel' : '' }); }, /** * grid布局, 唤起setting-panel */ switchSettingPanelFun() { this.setData({ panelName: this.panelName !== 'setting-panel' ? 'setting-panel' : '' }); }, handleMaskerClickFun() { this.setData({ panelName: '' }); }, setPuserPropertyFun(event) { // console.log(TAG_NAME, '_setPuserProperty', event) const key = event.currentTarget.dataset.key; let value = event.currentTarget.dataset.value; const config = {}; if (value === 'true') { value = true; } else if (value === 'false') { value = false; } if (typeof value === 'boolean') { config[key] = !this.pusher[key]; } else if (typeof value === 'string' && value.indexOf('|') > 0) { value = value.split('|'); if (this.pusher[key] === value[0]) { config[key] = value[1]; } else { config[key] = value[0]; } } // console.log(TAG_NAME, '_setPuserProperty', config) this.setPusherConfigFun(config); }, setPlayerPropertyFun(event) { console.log(TAG_NAME, '_setPlayerProperty', event); const userID = event.currentTarget.dataset.userid; const streamType = event.currentTarget.dataset.streamtype; const key = event.currentTarget.dataset.key; let value = event.currentTarget.dataset.value; const stream = this.userController.getStream({ userID: userID, streamType: streamType }); if (!stream) { return; } const config = {}; if (value === 'true') { value = true; } else if (value === 'false') { value = false; } if (typeof value === 'boolean') { config[key] = !stream[key]; } else if (typeof value === 'string' && value.indexOf('|') > 0) { value = value.split('|'); if (stream[key] === value[0]) { config[key] = value[1]; } else { config[key] = value[0]; } } console.log(TAG_NAME, '_setPlayerProperty', config); this.setPlayerConfigFun({ userID, streamType, config }); }, switchStreamTypeFun(event) { const userID = event.currentTarget.dataset.userid; const streamType = event.currentTarget.dataset.streamtype; const stream = this.userController.getStream({ userID: userID, streamType: streamType }); if (stream && stream.streamType === 'main') { if (stream._definitionType === 'small') { this.subscribeRemoteVideo({ userID, streamType: 'main' }); } else { this.subscribeRemoteVideo({ userID, streamType: 'small' }); } } }, handleSnapshotClickFun(event) { wx.showToast({ title: '开始截屏', icon: 'none', duration: 1000 }); const userID = event.currentTarget.dataset.userid; const streamType = event.currentTarget.dataset.streamtype; this.snapshot({ userID, streamType }); }, /** * grid布局, 绑定事件 */ bindEventGridFun() { // 远端音量变更 this.on(EVENT.REMOTE_AUDIO_VOLUME_UPDATE, event => { const data = event.data; const userID = data.currentTarget.dataset.userid; const streamType = data.currentTarget.dataset.streamtype; const volume = data.detail.volume; // console.log(TAG_NAME, '远端音量变更', userID, streamType, volume) const stream = this.userController.getStream({ userID: userID, streamType: streamType }); if (stream) { stream.volume = volume; } this.setData( { streamList: this.streamList }, () => {} ); }); }, toggleFullscreenFun(event) { console.log(TAG_NAME, '_toggleFullscreen', event); const userID = event.currentTarget.dataset.userID; const streamType = event.currentTarget.dataset.streamType; if (this._isFullscreen) { this.exitFullscreen({ userID, streamType }) .then(() => { this._isFullscreen = false; }) .catch(() => {}); } else { // const stream = this.userController.getStream({ userID, streamType }) const direction = 0; // 已知问题:视频的尺寸需要等待player触发NetStatus事件才能获取到,如果进房就双击全屏,全屏后的方向有可能不对。 // if (stream && stream.videoWidth && stream.videoHeight) { // // 如果是横视频,全屏时进行横屏处理。如果是竖视频,则为0 // direction = stream.videoWidth > stream.videoHeight ? 90 : 0 // } this.enterFullscreen({ userID, streamType, direction }) .then(() => { this._isFullscreen = true; }) .catch(() => {}); } } } }; </script> <style> @import './trtc-room.css'; </style>
4,这是使用的页面使用的方式(原生组件,所以有个元素层级默认最高,覆盖所有,得使用cover-view才能盖在上面)。一定要有ref,否则不能初始化方法,不能创建实例,还得有config相关配置
<trtc-room id="trtcComponent" :config.sync="playVidoe.input.rtcConfig" ref="trtcComponent"> </trtc-room>
4.1别忘了下载两个js到本地
用于测试研签,如果你有后端,签名和用户id就是后端提供给你。但你仍需要历练的setData方法
5,生命周期函数里面(等页面元素有了之后)
初始化
data(){ return{ callVideo:{ input:{ template: '1v1', debugMode: false, cloudenv: 'PRO', callingMode:'video', //通话模式 语音/视频 audio/video cameraMode:'back', //通话模式 前/后 front、after } }, playVidoe:{ input:{ rtcConfig: { sdkAppID: xxxxxx, // 必要参数 开通实时音视频服务创建应用后分配的 sdkAppID userID: 'test001', // 必要参数 用户 ID 可以由您的帐号系统指定 userSig: '"xxxxxx"', // 必要参数 身份签名,相当于登录密码的作用 template: '1v1', // 必要参数 组件模版,支持的值 1v1 grid custom ,注意:不支持动态修改, iOS 不支持 pusher 动态渲染 }, } }, } } async mounted() { // 获取 rtcroom 实例 this.trtcComponent = this.$refs['trtcComponent'] console.log('this.trtcComponent',this.trtcComponent) // 监听TRTC Room 关键事件 this.methods('bindTRTCRoomEvent') //基本数据 this.callVideo.input 通话模式等 Object.getOwnPropertyNames(this.callVideo.input).forEach(key => { if (this.callVideo.input[key] === 'true') { this.callVideo.input[key] = true; } if (this.callVideo.input[key] === 'false') { this.callVideo.input[key] = false; } }); this.options = this.callVideo.input; }, 然后我这里用了websocket,在create里面建立连接,等待推房间号给我,我进房 this.methods('initRtcConfig',{userID :'test001',roomID:this.curRoomId,template:'1v1'}) this.methods('enterRoomBefore')
6,事件
初始化并监控组件事件(如远端退房等)
//引入js import { genTestUserSig } from '../../libs/debug/GenerateTestUserSig.js' //初始化config initRtcConfig(data){ data.template = data.template data.roomID = data.roomID data.userID = data.userID console.log('* room enterRoom','走进来99999999', data) const Signature = genTestUserSig(data.userID) data.sdkAppID = Signature.sdkAppID data.userSig = Signature.userSig this.template = data.template this.playVidoe.input.rtcConfig = { sdkAppID: data.sdkAppID, // 您的实时音视频服务创建应用后分配的 sdkAppID userID: data.userID, roomID : data.roomID, userSig: data.userSig, template: data.template, // 1v1 grid custom debugMode: data.debugMode, // 非必要参数,打开组件的调试模式,开发调试时建议设置为 true beautyLevel: 9, // 默认开启美颜 enableIM: false, // 可选,仅支持初始化设置(进房前设置),不支持动态修改 audioVolumeType: data.audioVolumeType, } } async enterRoomBefore(){ let isOk = await this.methods('checkDeviceAuthorize') if(isOk){ //近房间 let data = { roomID:this.playVidoe.input.rtcConfig.roomID, userID:this.playVidoe.input.rtcConfig.userID, template:this.callVideo.input.template, debugMode:this.callVideo.input.debugMode, cloudenv:this.callVideo.input.cloudenv, } this.methods('enterRoom',data) }else{ console.log('出错') } } async checkDeviceAuthorize(){ let isOk = false this.hasOpenDeviceAuthorizeModal = false await new Promise((resolve, reject) => { if (!uni.getSetting || !uni.getSetting()) { // 微信测试版 获取授权API异常,目前只能即使没授权也可以通过 resolve() } uni.getSetting().then((result)=> { // console.log('getSetting', result[1]) this.authorizeMic = result[1].authSetting['scope.record'] this.authorizeCamera = result[1].authSetting['scope.camera'] if (result[1].authSetting['scope.camera'] && result[1].authSetting['scope.record']) { // 授权成功 isOk = true resolve() } else { // 没有授权,弹出授权窗口 // 注意: uni.authorize 只有首次调用会弹框,之后调用只返回结果,如果没有授权需要自行弹框提示处理 console.log('getSetting 没有授权,弹出授权窗口', result) uni.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 }) uni.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')) }) } }) }) return isOk } enterRoom(data) { this.trtcComponent.enterRoom({ roomID: data.roomID }).then(()=>{ console.log('HDFH11111111111111111111111111111',data) if (this.template === '1v1') { // 设置推流端视窗的坐标和尺寸 console.log('HDFH',data) this.trtcComponent.setViewRect({ userID: data.userID, xAxis: '480rpx', yAxis: '160rpx', width: '240rpx', height: '320rpx', }) } }).catch((res)=>{ //这里出现了一下奇怪的报错 连续拨通挂断,不懂是腾讯对签名有限制还是怎样,连续操作20几次会出错,进不去房间。。。。 这里需要加个检测失败的事件,失败跳出 console.error('组建所在页面 进房失败:', res) }) } bindTRTCRoomEvent() { const TRTC_EVENT = this.trtcComponent.EVENT this.timestamp = [] // 初始化事件订阅 this.trtcComponent.on(TRTC_EVENT.LOCAL_JOIN, (event)=>{ // console.log('* room LOCAL_JOIN', event) // 进房成功,触发该事件后可以对本地视频和音频进行设置 if (this.options.localVideo === true || this.options.template === '1v1') { this.trtcComponent.publishLocalVideo() } if (this.options.localAudio === true || this.options.template === '1v1') { this.trtcComponent.publishLocalAudio() } if (this.callVideo.input.template === 'custom') { this.trtcComponent.setViewRect({ userID: event.userID, xAxis: '0rpx', yAxis: '0rpx', width: '240rpx', height: '320rpx', }) } }) this.trtcComponent.on(TRTC_EVENT.LOCAL_LEAVE, (event)=>{ console.log('* room LOCAL_LEAVE', event) }) this.trtcComponent.on(TRTC_EVENT.ERROR, (event)=>{ console.log('* room ERROR', event) }) // 远端用户进房 this.trtcComponent.on(TRTC_EVENT.REMOTE_USER_JOIN, (event)=>{ console.log('组件所在页面 远端用户进房', event, this.trtcComponent.getRemoteUserList()) this.timestamp.push(new Date()) // 1v1视频通话时限制人数为两人的简易逻辑,建议通过后端实现房间人数管理 // 2人以上同时进行通话请选择网格布局 if (this.template === '1v1' && this.timestamp.length > 1) { const interval = this.timestamp[1] - this.timestamp[0] if (interval < 1000) { // 房间里已经有两个人 // this.setData({ // showTipToast: true, // }, () => { // setTimeout(()=>{ // this.setData({ // showTipToast: false, // }) uni.navigateBack({ delta: 1, }) // }, 4000) // }) } } }) // 远端用户退出 this.trtcComponent.on(TRTC_EVENT.REMOTE_USER_LEAVE, (event)=>{ console.log('组件所在页面 远端用户退出', event, this.trtcComponent.getRemoteUserList()) //跳转到感谢致电页面 this.methods('toPage','/pages/callOver/index') if (this.template === '1v1') { this.timestamp = [] } if (this.template === '1v1' && this.remoteUser === event.data.userID) { this.remoteUser = null } }) // 远端用户推送视频 this.trtcComponent.on(TRTC_EVENT.REMOTE_VIDEO_ADD, (event)=>{ console.log('组件所在页面 远端用户推送视频', event, this.template,this.remoteUser) // 订阅视频 const userList = this.trtcComponent.getRemoteUserList() const data = event.data if (this.template === '1v1' && (!this.remoteUser || this.remoteUser === data.userID)) { // 1v1 只订阅第一个远端流 this.remoteUser = data.userID; this.trtcComponent.subscribeRemoteVideo({ userID: data.userID, streamType: data.streamType }); } else if (this.template === 'grid') { this.trtcComponent.subscribeRemoteVideo({ userID: data.userID, streamType: data.streamType }); } if (this.template === 'custom' && data.userID && data.streamType) { let index = userList.findIndex((item)=>{ return item.userID === data.userID }) index++ const y = 320 * index + 160 // 设置远端视图坐标和尺寸 this.trtcComponent.setViewRect({ userID: data.userID, streamType: data.streamType, xAxis: '480rpx', yAxis: y + 'rpx', width: '240rpx', height: '320rpx', }) } }) // 远端用户取消推送视频 this.trtcComponent.on(TRTC_EVENT.REMOTE_VIDEO_REMOVE, (event)=>{ console.log('组件所在页面 远端用户取消推送视频', event, this.trtcComponent.getRemoteUserList()) }) // 远端用户推送音频 this.trtcComponent.on(TRTC_EVENT.REMOTE_AUDIO_ADD, (event)=>{ console.log('组件所在页面 远端用户推送音频', event, this.template,this.remoteUser ) // 订阅音频 const data = event.data if (this.template === '1v1' && (!this.remoteUser || this.remoteUser === data.userID)) { this.remoteUser = data.userID; this.trtcComponent.subscribeRemoteAudio({ userID: data.userID }); } else if (this.template === 'grid' || this.template === 'custom') { this.trtcComponent.subscribeRemoteAudio({ userID: data.userID }); } }) // 远端用户取消推送音频 this.trtcComponent.on(TRTC_EVENT.REMOTE_AUDIO_REMOVE, (event)=>{ console.log('组件所在页面 远端用户取消推送音频', event, this.trtcComponent.getRemoteUserList()) }) // this.trtcComponent.on(TRTC_EVENT.LOCAL_NET_STATE_UPDATE, (event)=>{ // console.log('* room LOCAL_NET_STATE_UPDATE', event) // }) // this.trtcComponent.on(TRTC_EVENT.REMOTE_NET_STATE_UPDATE, (event)=>{ // console.log('* room REMOTE_NET_STATE_UPDATE', event) // }) this.trtcComponent.on(TRTC_EVENT.IM_READY, (event)=>{ console.log('* room IM_READY', event) }) this.trtcComponent.on(TRTC_EVENT.IM_MESSAGE_RECEIVED, (event)=>{ console.log('* room IM_MESSAGE_RECEIVED', event) }) }
记得在微信公众平台打开视频推流拉流权限
无意间在博客看到一个广告,也不知道怎么样,有没有人用过声网的这个实时视频sdk
https://www.agora.io/cn/usecase,看起来和腾讯的很相似
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。