快速开始
初始化房间并加入房间
到这里您应该已经完成了开发准备,您已经可以拿到 RoomToken。SDK 通过传入 RoomToken 来完成加入房间。RoomToken 是一个包含了一次连麦所需要的主要信息的 token,这些信息包括 七牛的账户标识、连麦的应用 ID(appId)、连麦的房间号 (roomName)、连麦的用户名(userId)、有效期等等。
如果您还不知道如何生成 RoomToken,请先阅读 七牛实时音视频云接入指南
import { RoomSession } from 'pili-rtc-wxapp'
const session = new RoomSession()
// 先拿到 roomToken
session.joinRoomWithToken(roomToken, userData)
.then(() => {
// 加入房间成功,接下来可以调用 session.publish 和 session.subscribe
})
.catch((err) => {
// 加入房间失败
console.log(err)
})
发布流
使用 qiniu-wx-player 组件中 <pusher>
发布流,这里使用的参数 min-bitrate="200"
最小码率 max-bitrate="400"
最大码率 mode="RTC"
RTC模式,加入房间之后我们需要调用 publish 返回一个 rtmp 推流地址。
<!-- index.wxml -->
<pusher
autopush
min-bitrate="200"
max-bitrate="400"
mode="RTC"
url="{{publishPath}}"
>
</pusher>
先使用 wx.createLivePusherContext 创建 LivePusherContext,再使用 setData 设置好 publishPath 之后发布。
// index.js
Page({
data: {
publishPath: undefined,
},
publish() {
// joinRoom 之后调用
// 创建 LivePusherContext
const pushContext = wx.createLivePusherContext()
const path = session.publish()
this.setData(
{ publishPath: path },
() => {
pushContext.start({
success: () => {
console.log('推流成功')
},
fail: () => {
console.log('推流开始失败')
}
})
})
}
})
订阅流
注意:订阅流我们提供两个 api 。subscribe 返回一个 rtmp 拉流地址(该 api 只返回的是 master 为 true 的 rtmp 地址),getSubscribeAddressList 返回 rtmp 拉流地址列表(可以返回 master 为 ture 及 false 的 rtmp 拉流地址),该列表可以包含主讲的共享画面的拉流地址,从而可以显示主讲的共享画面。
subscribe
使用 qiniu-wx-player 组件中 <player>
订阅流,加入房间之后我们可以调用 subscribe 返回一个 rtmp 拉流地址。
下面我们使用了 wx:for 遍历 data.subscribeList 渲染一个订阅的列表。
<!-- index.wxml -->
<player
autoplay
wx:key="{{item.key}}"
wx:for="{{subscribeList}}"
min-cache="0.2"
max-cache="0.8"
src="{{item.url}}"
mode="RTC"
>
</player>
一般来说有两个地方会触发订阅:
- joinRoomWithToken 之后;
- 监听
track-add
事件的回调中。
接下来我们实现一个自动订阅的逻辑:
// index.js
Page({
data: {
subscribeList: [],
},
subscribe(playerid) {
const path = session.subscribe(playerid)
if (path) {
const sub = this.data.subscribeList.filter(v => v.key !== playerid)
sub.push({
url: path,
key: playerid,
})
this.setData({
subscribeList: sub,
})
}
},
onLoad() {
// 1. joinRoom 之后订阅
session.joinRoomWithToken(roomToken)
.then(() => {
// 加入房间成功,接下来可以调用 session.publish 和 session.subscribe
session.users
.filter(v => v.playerid !== app.session.userId)
.forEach(v => {
this.subscribe(v.playerid)
})
})
// 2. 事件 track-add 之后订阅
session.on('track-add', (tracks) => {
console.log('track-add', tracks)
const set = {}
for (const track of tracks) {
// 每个 playerid 只订阅一次
if (!set[track.playerid]) {
set[track.playerid] = true
this.subscribe(track.playerid)
}
}
})
}
})
getSubscribeAddressList
使用 qiniu-wx-player 组件中 <player>
订阅流,加入房间之后我们可以调用 getSubscribeAddressList 返回 rtmp 拉流地址列表,该列表可以包含主讲的共享画面的拉流地址,从而可以显示主讲的共享画面。
下面我们使用了 wx:for 遍历 data.subscribeList 渲染一个订阅的列表。
<!-- index.wxml -->
<player
autoplay
wx:key="{{item.key}}"
wx:for="{{subscribeList}}"
min-cache="0.2"
max-cache="0.8"
src="{{item.url}}"
mode="RTC"
>
</player>
一般来说有两个地方会触发订阅:
- joinRoomWithToken 之后;
- 监听
track-add
事件的回调中。
接下来我们实现一个自动订阅的逻辑:
// index.js
Page({
data: {
subscribeList: [],
},
subscribe(playerid) {
const addressList = session.getSubscribeAddressList(playerid)
if (addressList.length > 0) {
const sub = this.data.subscribeList.filter(v => v.userid !== playerid)
const urlList = []
addressList.forEach(((item, index)=> {
urlList.push(Object.assign({}, item, {
key:playerid + Math.random().toString(36).slice(-8),
userid:playerid
}))
}))
urlList.forEach(e => {
sub.push(e)
})
this.setData({
subscribeList: sub,
}, () => {
console.log('subscribeList:', this.data.subscribeList)
})
}
},
onLoad() {
// 1. joinRoom 之后订阅
session.joinRoomWithToken(roomToken, userData)
.then(() => {
// 加入房间成功,接下来可以调用 session.publish 和 session.getSubscribeAddressList
session.users
.filter(v => v.playerid !== app.session.userId)
.forEach(v => {
this.subscribe(v.playerid)
})
})
// 2. 事件 track-add 之后订阅
session.on('track-add', (tracks) => {
console.log('track-add', tracks)
const set = {}
for (const track of tracks) {
// 每个 playerid 只订阅一次
if (!set[track.playerid]) {
set[track.playerid] = true
this.subscribe(track.playerid)
}
}
})
}
})
合流
合流操作中涉及 createMergeJob, updateMergeTracks, stopMerge 三个操作。应该先调用 createMergeJob 创建合流的任务。接着在合流任务中添加/删除媒体流,此时在合理任务对应的订阅流中已经能够看到合流后的效果。最后调用 stopMerge 来停止合流。考虑到一般用户添加和删除媒体流操作一般不同时进行,另外添加了 addMergeTracks 和 removeMergeTracks 两个接口方便用户操作。
- createMergeJob 创建合流任务,需要指定发布流地址、任务ID,媒体宽、高等,此外还可以指定水印、背景等信息。
- updateMergeTracks 更新合流中的媒体流。添加时,需要指定媒体流的 trackid、宽(w)、高(h)、横轴偏移(x)、纵轴偏移(y)、深度偏移(z)、画面填充方式( stretchMode,可选值,为空继承
createMergeJob
中 stretchMode 的值)。删除时,只需要指定 trackid 。 - addMergeTracks 合流任务中添加媒体流
- removeMergeTracks 合流任务中删除媒体流
- stopMerge 停止合流,需要指定合流任务的 ID
涉及到的相关信息
- 媒体流:session实例中的
tracks
为远程媒体流,包括所有连上麦的其他用户的媒体流,localTracks
为本地媒体流,连上麦以后才能获到。 - 合流任务:session实例中的
mergeJobs
包含了本次回话中创建的所有的合流任务。调用 stopMerge 或者回话结算后,合流任务自动销毁。
const mergeJobParam = {
id: '1234',
publishUrl: 'rtmp://10.200.20.28:1935/devtest/merge_test_job?domain=pili-publish.devtest.qbox.net',
width: 480,
height: 320,
x: 0,
y: 0,
fps: 60,
kbps: 1000
}
function createMergeJob() {
session.createMergeJob(mergeJobParam.id, mergeJobParam)
}
function addMergeTracks() {
// 获取所有远程媒体流和本地媒体流
const allTracks = session.tracks.concat(session.localTracks)
const addTracks = allTracks.map((track, idx) => ({ trackid: track.trackid, w: 200, h: 120, x: 0, y: 120 * idx }) )
session.addMergeTracks(addTracks, mergeJobParam.id)
}
function removeMergeTracks() {
// 获取所有远程媒体流和本地媒体流
const allTracks = session.tracks.concat(session.localTracks)
const removeTracks = allTracks.map(track => ({ trackid: track.trackid }) )
session.removeMergeTracks(removeTracks, mergeJobParam.id)
}
function stopMerge() {
session.stopMerge(mergeJobParam.id)
}
离开房间
离开房间,断开信令,一般在 生命周期 onUnload 里调用。
Page({
onUnload() {
session.leaveRoom()
}
})
断网重连
SDK 会承担一部分重连工作,当 SDK 自动重连失败时(比如RoomToken过期),会抛出 error
事件。
此时应该重新获取 RoomToken 再 joinRoom。
session.on('error', () => {
// 重新获取 RoomToken -> joinRoom
reconnect()
})
// 注意一定在 reconnected 事件后重新推流与拉流,SDK自动重连之后也会触发 reconnected 事件
session.on('reconnected', () => {
pushContext.start({
success: () => {
console.log('推流成功')
},
fail: () => {
console.log('推流失败')
},
})
for (const track of this.data.subscribeList) {
const ctx = wx.createLivePlayerContext(track.key)
if (ctx) {
ctx.play()
}
}
})
结语
到了这里我们已经了解 SDK 的基本使用方式。 更多请参考我们的 github 开源 Demo