原生小程序玩转TRTC

753 阅读5分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

公司新起了一个项目关于小程序相关的,我是用原声写的。

为什么要用原生呢?

  1. 我是第一次玩小程序---新手上路(掉入了填坑爬坑......)
  2. 是从一个老的项目直接改的

需求

  1. 类似微信音视频通讯
  2. 某一个有身份的可以对其他进入房间里面的人进行截屏(可以截自己、可以截其他人)
  3. 是否全屏
  4. 反转摄像头
  5. 签名(下篇文章写)

需求大不多这样了,我们开干

npm

首先就是关于trtc相关的npm包了,安排

npm i trtc-wx-sdk

我这里的版本是

image.png

当然原生小程序开发下完包是不能直接引入使用的,我们这样

image.png

这样就会在node_modules的平级出现它

image.png

当然没出来也不要紧,我们配置一下下

image.png

接下来我们就可以愉快的code了

创建trtc.js

随便在pages下建立一个trtc.js

import TRTC from '../../miniprogram_npm/trtc-wx-sdk/index.js';  //这样引入

Page({
    data:{
        sdkAppID: '', // 必要参数 开通实时音视频服务创建应用后分配的 sdkAppID
        userID: '', // 必要参数 用户 ID 可以由您的帐号系统指定
        userSig: '', // 必要参数 身份签名,相当于登录密码的作用
        localVideo: false,//本地视频
        localAudio: false,//本地音频
        webcam: false,//摄像头
        
        pusher: null,//推流数据
        playerList: [],//拉流数据
        
        //是否全屏
        fullScreenFlag: true,
        currentPlayer: '',
    }
})

首先就是获取后端数据拿到userID、sdkAppID、userSig

getTRTCConfig().then(res=>{
    this.setData({
         userID: res.userId,
         sdkAppID: res.sdkAppId,
         userSig: res.userSig
    })
})

里面还有一个处理对象里面关于boolean值用字符串包裹的转换

// 将String 类型的 true false 转换成 boolean
   Object.getOwnPropertyNames(options).forEach((key) => {
      if (options[key] === 'true') {
           options[key] = true
       }
      if (options[key] === 'false') {
            options[key] = false
       }
   })

初始化

我们需要在onLoad初始化一些东西了

this.init()
this.enterRoom({ roomID: 999 })  //随便写个房间号 
this.bindTRTCRoomEvent()  //监听各种

init

先让我们看下init里面干了些什么

init () {
// pusher 初始化参数       
const pusher = this.TRTC.createPusher()
this.setData({
    pusher: pusher.pusherAttributes,
    })
},

啥也不是,这是我们开始使用trtc最初的地方

enterRoom

完事就是我们要进入到房间了,_是lodash的方法的简写,感兴趣的大家自己可以下载一下

enterRoom (options) {
const roomID = options.roomID  //房间号
const config = _.assign(_.pick(this.data, ['sdkAppID', 'userID', 'userSig']), { roomID })
this.setData({
    pusher: this.TRTC.enterRoom(config),
 }, () => {
    this.TRTC.getPusherInstance().start() // 开始推流
    })
},

exitRoom

有进入房间就有退出房间(有主动、有被动)

 exitRoom () {  //主动
 const result = this.TRTC.exitRoom()
 this.setData({
     pusher: result.pusher,
     playerList: result.playerList,
   })
 },

 onKickedout () {  //被动
     console.log('被服务端踢出或房间被解散')
 },

bindTRTCRoomEvent

接下来我们去绑定监听一些东西,就是我们的bindTRTCRoomEvent函数啦,里面监听的东西比较多哦,且让我cv一下哈(主要是推流、拉流各种状态的极限拉扯)

//事件监听
    bindTRTCRoomEvent () {
        const TRTC_EVENT = this.TRTC.EVENT
        // 初始化事件订阅
        this.TRTC.on(TRTC_EVENT.LOCAL_JOIN, (event) => {
            console.log('* room LOCAL_JOIN', event)
            getApp().aegisReportEvent('inMeetingRoom', 'inMeetingRoom-success')
            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.data
            wx.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.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.data
            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
            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.setData({
                playerList: playerList
            })
        })
        // 本地用户音量更新
        this.TRTC.on(TRTC_EVENT.LOCAL_AUDIO_VOLUME_UPDATE, (event) => {
            // console.log('* room LOCAL_AUDIO_VOLUME_UPDATE', event)
            const { pusher } = event.data
            this.setData({
                pusher: pusher
            })
        })
        //被动退出当前房间
        this.TRTC.on(TRTC_EVENT.KICKED_OUT, this.onKickedout)
    },

pusher、player

有两个概念。。。。。pusher(我们自己)----player(看到远端的) 安排

// 设置 pusher 属性
    setPusherAttributesHandler (options) {
        console.log(this.TRTC.setPusherAttributes(options), 'this.TRTC.setPusherAttributes(options)')
        this.setData({
            pusher: this.TRTC.setPusherAttributes(options),
        })
    },
// 设置某个 player 属性
    setPlayerAttributesHandler (player, options) {
        let playerList = this.TRTC.setPlayerAttributes(player.streamID, options)
        this.setData({
            playerList
        })
    },

Audio相关

我们要订阅pusher、player的Audio

// 是否订阅某一个player Audio
    _mutePlayerAudio (event) {
        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 (event) {
        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
        }
    },
    
    // 订阅 / 取消订阅 Audio
    _pusherAudioHandler () {
        if (this.data.pusher.enableMic) {
            this.setPusherAttributesHandler({ enableMic: false })
        } else {
            this.setPusherAttributesHandler({ enableMic: true })
        }
    },

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

其他辅助函数

有一堆他们trtc内部的函数我这里也cv上,我查文档做了注释,他们demo没有

// 请保持跟 wxml 中绑定的事件名称一致
    // 推流  
    // 状态变化事件,detail = {code}
    _pusherStateChangeHandler (event) {
        this.TRTC.pusherEventHandler(event)
    },
    // 网络状态通知,detail = {info}
    _pusherNetStatusHandler (event) {
        this.TRTC.pusherNetStatusHandler(event)
    },
    // 渲染错误事件,detail = {errMsg, errCode}
    _pusherErrorHandler (event) {
        this.TRTC.pusherErrorHandler(event)
    },
    // 背景音开始播放时触发
    _pusherBGMStartHandler (event) {
        this.TRTC.pusherBGMStartHandler(event)
    },
    // 背景音进度变化时触发,detail = {progress, duration}
    _pusherBGMProgressHandler (event) {
        this.TRTC.pusherBGMProgressHandler(event)
    },
    // 背景音进度变化时触发,detail = {progress, duration}
    _pusherBGMCompleteHandler (event) {
        this.TRTC.pusherBGMCompleteHandler(event)
    },
    // 返回麦克风采集的音量大小
    _pusherAudioVolumeNotify (event) {
        this.TRTC.pusherAudioVolumeNotify(event)
    },

    // 拉流
    // 播放状态变化事件,detail = {code}
    _playerStateChange (event) {
        this.TRTC.playerEventHandler(event)
    },
    // 全屏变化事件,detail = {direction, fullScreen}
    _playerFullscreenChange (event) {
        this.TRTC.playerFullscreenChange(event)
    },
    // 网络状态通知,detail = {info}
    _playerNetStatus (event) {
        this.TRTC.playerNetStatus(event)
    },
    // 播放音量大小通知,detail = {}
    _playerAudioVolumeNotify (event) {
        this.TRTC.playerAudioVolumeNotify(event)
    },

想主动离开退出房间

// 挂断退出房间
    _hangUp () {
        this.exitRoom()
        wx.navigateBack({
            delta: 1,
        })
    },

到这里两个手机小程序就可以悄悄的音视频通话啦!

接下来做一些功能

1.裁剪

可以截自己pusher。 可以截别人playerList的某一个 安排输出一个base64的给到后端大佬

这里的截屏的api 使用的是人家微信原生的(trtc的取不到是undefined)

onReady () {
        this.ctx = wx.createLivePusherContext('pusher')
        this.ctx_player = wx.createLivePlayerContext('player')
    },
const FileSystemManager = wx.getFileSystemManager()

bindTailor () {
        this.ctx_player.snapshot({
            success: res => {
                console.log('res: ', res);
                FileSystemManager.readFile({
                    filePath: res.tempImagePath, //选择图片返回的相对路径
                    encoding: 'base64', //编码格式
                    success: res => {
                        console.log('res: ', res);
                        let base64 = res.data
                        // console.log('base64: ', base64);
                    },
                    fail: res => {
                        console.log("readFile失败", res)
                        wx.showToast({
                            title: '读取拍照文件失败',
                            icon: 'none'
                        })
                    }
                })
            },
            fail: res => {
                console.log("截屏失败", res)
                wx.showToast({
                    title: '截取视频流失败',
                    icon: 'none'
                })
            },
        })

    },

2.是否全屏

这个用的trtc的api

//是否全屏
    onFullScreen (event) {
        this.setData({
            fullScreenFlag: false
        })
        const player = this.TRTC.getPlayerList()
        this.TRTC.getPlayerInstance(player[0].id).requestFullScreen(90).then(res => {
            console.log('res: ', res);
        }).catch(err => {
            console.log('err: ', err);
        })
    },
    unFullScreen (event) {
        this.setData({
            fullScreenFlag: true
        })
        const player = this.TRTC.getPlayerList()
        this.TRTC.requestExitFullScreen(player[0].id).requestFullScreen(90).then(res => {
            console.log('res: ', res);
        }).catch(err => {
            console.log('err: ', err);
        })
    },

3.切换摄像头

微信原生api

//切换摄像头
    bindSwitchCamera () {
        this.ctx.switchCamera({
            success: res => {
                console.log('switchCamera success')
                this.setData({
                    webcam: !this.data.webcam
                })
            },
            fail: res => {
                console.log('switchCamera fail')
            }
        })
    },

wxml 的代码大家可以down一下腾讯的我就不贴了

github.com/LiteAVSDK/L…

总结

  1. 要有钻研精神,解决不了就提工单给腾讯,毕竟看文档找的不如人家快,当然也有他们trtc的api解决不了的,完事就是踢皮球给微信原生api,极限拉扯ing.....
  2. 累了 毁灭吧 只有一条
  3. 下篇文章搞一下签名