小程序原生实时音视频实现
api文档:https://developers.weixin.qq.com/miniprogram/dev/api/media/live/wx.createLivePusherContext.html
开通标签使用权限:live-pusher 和 live-player 是小程序内部用于支持音视频上行能力的功能标签
-
目前支持这两个标签的类目如下表格所示(只有非个人主体才有以下类目):
-
符合类目要求的小程序,需要在小程序管理后台的【开发】>【接口设置】中自助开通推拉流标签的使用权限:
live-player:是小程序内部用于支持音视频下行(播放)能力的功能标签。 !!!(基础库 1.7.0 开始支持,低版本需做兼容处理.
属性定义
属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
---|---|---|---|---|---|
src | string | 否 | 音视频地址。目前仅支持 flv , rtmp 格式 |
1.7.0 | |
mode | string | live | 否 | 模式 | 1.7.0 |
autoplay | boolean | false | 否 | 自动播放 | 1.7.0 |
muted | boolean | false | 否 | 是否静音 | 1.7.0 |
orientation | string | vertical | 否 | 画面方向 | 1.7.0 |
object-fit | string | contain | 否 | 填充模式,可选值有 contain ,fillCrop
|
1.7.0 |
background-mute | boolean | false | 否 | 进入后台时是否静音(已废弃,默认退后台静音) | 1.7.0 |
min-cache | number | 1 | 否 | 最小缓冲区,单位s(RTC 模式推荐 0.2s) | 1.7.0 |
max-cache | number | 3 | 否 | 最大缓冲区,单位s(RTC 模式推荐 0.8s)。缓冲区用来抵抗网络波动,缓冲数据越多,网络抗性越好,但时延越大。 | 1.7.0 |
sound-mode | string | speaker | 否 | 声音输出方式 | 1.9.90 |
auto-pause-if-navigate | boolean | true | 否 | 当跳转到本小程序的其他页面时,是否自动暂停本页面的实时音视频播放 | 2.5.0 |
auto-pause-if-open-native | boolean | true | 否 | 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放 | 2.5.0 |
picture-in-picture-mode | string/Array | 否 | 设置小窗模式: push, pop,空字符串或通过数组形式设置多种模式(如: ["push", "pop"]) | 2.10.3 | |
referrer-policy | string | no-referrer | 否 | 格式固定为 https://servicewechat.com/{appid}/{version}/page-frame.html ,其中 {appid} 为小程序的 appid,{version} 为小程序的版本号,版本号为 0 表示为开发版、体验版以及审核版本,版本号为 devtools 表示为开发者工具,其余为正式版本; |
2.13.0 |
bindstatechange | eventhandle | 否 | 播放状态变化事件,detail = {code} | 1.7.0 | |
bindfullscreenchange | eventhandle | 否 | 全屏变化事件,detail = {direction, fullScreen} | 1.7.0 | |
bindnetstatus | eventhandle | 否 | 网络状态通知,detail = {info} | 1.9.0 | |
bindaudiovolumenotify | eventhandler | 否 | 播放音量大小通知,detail = {} | 2.10.0 | |
bindenterpictureinpicture | eventhandler | 否 | 播放器进入小窗 | 2.11.0 | |
bindleavepictureinpicture | eventhandler | 否 | 播放器退出小窗 | 2.11.0 |
状态码
代码 | 说明 |
---|---|
2001 | 已经连接服务器 |
2002 | 已经连接 RTMP 服务器,开始拉流 |
2003 | 网络接收到首个视频数据包(IDR) |
2004 | 视频播放开始 |
2005 | 视频播放进度 |
2006 | 视频播放结束 |
2007 | 视频播放Loading |
2008 | 解码器启动 |
2009 | 视频分辨率改变 |
-2301 | 网络断连,且经多次重连抢救无效,更多重试请自行重启播放 |
-2302 | 获取加速拉流地址失败 |
2101 | 当前视频帧解码失败 |
2102 | 当前音频帧解码失败 |
2103 | 网络断连, 已启动自动重连 |
2104 | 网络来包不稳:可能是下行带宽不足,或由于主播端出流不均匀 |
2105 | 当前视频播放出现卡顿 |
2106 | 硬解启动失败,采用软解 |
2107 | 当前视频帧不连续,可能丢帧 |
2108 | 当前流硬解第一个I帧失败,SDK自动切软解 |
3001 | RTMP -DNS解析失败 |
3002 | RTMP服务器连接失败 |
3003 | RTMP服务器握手失败 |
3005 | RTMP 读/写失败,之后会发起网络重试 |
示例代码
<!--
class 动态选择谁的屏幕放大
src 播放地址
mode 模式 live-直播 RTC-实时通话
binderrorPusher 渲染错误事件,detail = {errMsg, errCode}
beauty-style 设置美颜类型 smooth 光滑美颜 nature 自然美颜
auto-pause-if-open-native 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
bindstatechange 状态变化事件,detail = {code}
object-fit 填充模式
picture-in-picture-mode 设置小窗模式: push, pop,空字符串或通过数组形式设置多种模式(如: ["push", "pop"])
-->
<live-player
id="player"
src="{{url_user}}"
mode="RTC"
autoplay
bindstatechange="statechangePlayer"
beauty-style="smooth"
auto-pause-if-open-native="{{false}}"
object-fit="fillCrop"
picture-in-picture-mode="['push', 'pop']"
class="{{is_small == 2 ?'small-window' : 'player'}}"
data-class="{{is_small == 2 ?'small-window' : 'pusher'}}"
bindtap="bindPlayerClick"/>
超低时延
的 RTC 模式支持500ms以内的超低时延链路,可以应用在视频通话和远程遥控等场景中,要使用超低时延播放,需要注意如下几点:
(1)推流端如果是微信小程序,请使用 的 RTC 模式。
(2)推流端如果是 iOS 或者 Android SDK,请使用 setVideoQuality 的 MAIN_PUBLISHER 模式。
(3)推流端如果是 Windows,请不要使用 OBS,延时太高,可以使用我们的 Windows SDK。
(4)min-cache 和 max-cache 请不要自行设置,使用默认值。
(5)播放地址请使用超低延时播放地址,也就是带了防盗链签名的rtmp://地址,如下:
普通直播URL rtmp://3891.liveplay.myqcloud.com/live/3891_test_clock_for_rtmpacc >2s
超低延时URL rtmp://3891.liveplay.myqcloud.com/live/3891_test_clock_for_rtmpacc?bizid=bizid&txTime=5FD4431C&txSerect=20e6d865f462dff61ada209d53c71cf9 < 500ms
对象方法
LivePlayerContext
LivePlayerContext
实例,可通过 wx.createLivePlayerContext 获取。
LivePlayerContext 通过 id
跟一个 live-player 组件绑定,操作对应的 live-player 组件。
wx.createLivePlayerContext('player', this)
LivePlayerContext.play()//播放
LivePlayerContext.stop()//停止
LivePlayerContext.mute()//静音
LivePlayerContext.pause()//暂停
LivePlayerContext.resume()//恢复
LivePlayerContext.requestFullScreen(Object object)//进入全屏
LivePlayerContext.exitFullScreen()//退出全屏
LivePlayerContext.exitPictureInPicture()//退出小窗,该方法可在任意页面调用
LivePlayerContext.snapshot(Object object)//截图
LivePlayerContext.requestPictureInPicture()//进入小窗
var player = wx.createLivePlayerContext('pusher');
player.requestFullScreen({
success(){
console.log('enter full screen mode success!')
}
fail(){
console.log('enter full screen mode failed!')
}
complete(){
console.log('enter full screen mode complete!')
}
});
小窗特性说明
live-player 小窗支持以下三种触发模式(在组件上设置 picture-in-picture-mode 属性):
1.push 模式,即从当前页跳转至下一页时出现小窗(页面栈push)
2.pop 模式,即离开当前页面时触发(页面栈pop)
3.以上两种路由行为均触发小窗
此外,小窗还支持以下特性:
- 小窗容器尺寸会根据原组件尺寸自动判断
- 点击小窗,用户会被导航回小窗对应的播放器页面
- 小窗出现后,用户可点击小窗右上角的关闭按钮或调用 context.exitPictureInPicture() 接口关闭小窗
- 当播放器进入小窗模式后,播放器所在页面处于 hide 状态(触发 onHide 生命周期),该页面不会被销毁。当小窗被关闭时,播放器所在页面会被 unload (触发 onUnload 生命周期)。
分割线
live-pusher 实时音视频录制(v2.9.1 起支持同层渲染)。需要用户授权
scope.camera
、scope.record
。
live-pusher支持的类目同live-player一样
属性定义
属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
---|---|---|---|---|---|
url | string | 否 | 推流地址。目前仅支持 rtmp 格式 |
1.7.0 | |
mode | string | RTC | 否 |
SD (标清), HD (高清), FHD (超清), RTC (实时通话) |
1.7.0 |
autopush | boolean | false | 否 | 自动推流 | 1.7.0 |
muted | boolean | false | 否 | 是否静音。即将废弃,可用 enable-mic 替代 |
1.7.0 |
enable-camera | boolean | true | 否 | 开启摄像头 | 1.7.0 |
auto-focus | boolean | true | 否 | 自动聚集 | 1.7.0 |
orientation | string | vertical | 否 | 画面方向 | 1.7.0 |
beauty | number | 0 | 否 | 美颜,取值范围 0-9 ,0 表示关闭 | 1.7.0 |
whiteness | number | 0 | 否 | 美白,取值范围 0-9 ,0 表示关闭 | 1.7.0 |
aspect | string | 9:16 | 否 | 宽高比,可选值有 3:4 , 9:16
|
1.7.0 |
min-bitrate | number | 200 | 否 | 最小码率 | 1.7.0 |
max-bitrate | number | 1000 | 否 | 最大码率 | 1.7.0 |
audio-quality | string | high | 否 | 高音质(48KHz)或低音质(16KHz),值为high , low
|
1.7.0 |
waiting-image | string | 否 | 进入后台时推流的等待画面 | 1.7.0 | |
waiting-image-hash | string | 否 | 等待画面资源的MD5值 | 1.7.0 | |
zoom | boolean | false | 否 | 调整焦距 | 2.1.0 |
device-position | string | front | 否 | 前置或后置,值为front , back
|
2.3.0 |
background-mute | boolean | false | 否 | 进入后台时是否静音(已废弃,默认退后台静音) | 1.7.0 |
mirror | boolean | false | 否 | 设置推流画面是否镜像,产生的效果在 live-player 反应到 | 2.7.0 |
remote-mirror | boolean | false | 否 | 同 mirror 属性,后续 mirror 将废弃 | 2.10.0 |
local-mirror | string | auto | 否 | 控制本地预览画面是否镜像 | 2.10.0 |
audio-reverb-type | number | 0 | 否 | 音频混响类型 | 2.10.0 |
enable-mic | boolean | true | 否 | 开启或关闭麦克风 | 2.10.0 |
enable-agc | boolean | false | 否 | 是否开启音频自动增益 | 2.10.0 |
enable-ans | boolean | false | 否 | 是否开启音频噪声抑制 | 2.10.0 |
audio-volume-type | string | auto | 否 | 音量类型 | 2.10.0 |
video-width | number | 360 | 否 | 上推的视频流的分辨率宽度 | 2.10.0 |
video-height | number | 640 | 否 | 上推的视频流的分辨率高度 | 2.10.0 |
beauty-style | string | smooth | 否 | 设置美颜类型 | 2.12.0 |
filter | string | standard | 否 | 设置色彩滤镜 | 2.12.0 |
bindstatechange | eventhandle | 否 | 状态变化事件,detail = {code} | 1.7.0 | |
bindnetstatus | eventhandle | 否 | 网络状态通知,detail = {info} | 1.9.0 | |
binderror | eventhandle | 否 | 渲染错误事件,detail = {errMsg, errCode} | 1.7.4 | |
bindbgmstart | eventhandle | 否 | 背景音开始播放时触发 | 2.4.0 | |
bindbgmprogress | eventhandle | 否 | 背景音进度变化时触发,detail = {progress, duration} | 2.4.0 | |
bindbgmcomplete | eventhandle | 否 | 背景音播放完成时触发 | 2.4.0 | |
bindaudiovolumenotify | eventhandle | 否 | 返回麦克风采集的音量大小 | 2.12.0 |
状态码
代码 | 说明 |
---|---|
1001 | 已经连接推流服务器 |
1002 | 已经与服务器握手完毕,开始推流 |
1003 | 打开摄像头成功 |
1004 | 录屏启动成功 |
1005 | 推流动态调整分辨率 |
1006 | 推流动态调整码率 |
1007 | 首帧画面采集完成 |
1008 | 编码器启动 |
-1301 | 打开摄像头失败 |
-1302 | 打开麦克风失败 |
-1303 | 视频编码失败 |
-1304 | 音频编码失败 |
-1305 | 不支持的视频分辨率 |
-1306 | 不支持的音频采样率 |
-1307 | 网络断连,且经多次重连抢救无效,更多重试请自行重启推流 |
-1308 | 开始录屏失败,可能是被用户拒绝 |
-1309 | 录屏失败,不支持的Android系统版本,需要5.0以上的系统 |
-1310 | 录屏被其他应用打断了 |
-1311 | Android Mic打开成功,但是录不到音频数据 |
-1312 | 录屏动态切横竖屏失败 |
1101 | 网络状况不佳:上行带宽太小,上传数据受阻 |
1102 | 网络断连, 已启动自动重连 |
1103 | 硬编码启动失败,采用软编码 |
1104 | 视频编码失败 |
1105 | 新美颜软编码启动失败,采用老的软编码 |
1106 | 新美颜软编码启动失败,采用老的软编码 |
3001 | RTMP -DNS解析失败 |
3002 | RTMP服务器连接失败 |
3003 | RTMP服务器握手失败 |
3004 | RTMP服务器主动断开,请检查推流地址的合法性或防盗链有效期 |
3005 | RTMP 读/写失败 |
示例代码
<live-pusher
id="pusher"
url="{{url_doctor}}"
mode="RTC"
autopush
bindstatechange="statechangePusher"
beauty-style="smooth"
binderror="binderrorPusher"
class="{{is_small == 1 ?'small-window' : 'pusher'}}"
data-class="{{is_small == 1 ?'small-window' : 'pusher'}}"
bindtap="bindPusherClick" />
对象方法
LivePusherContext
LivePusherContext
实例,可通过 wx.createLivePusherContext 获取。
LivePusherContext.start() //开始推流,同时开启摄像头预览
LivePusherContext.stop() //停止推流,同时停止摄像头预览
LivePusherContext.pause() //暂停推流
LivePusherContext.resume() //恢复推流
LivePusherContext.switchCamera() //切换前后摄像头
LivePusherContext.snapshot(Object object) //快照
LivePusherContext.toggleTorch() //切换手电筒
LivePusherContext.playBGM(Object object) //播放背景音
LivePusherContext.stopBGM() //停止背景音
LivePusherContext.pauseBGM() //暂停背景音
LivePusherContext.resumeBGM() //恢复背景音
LivePusherContext.setBGMVolume(Object object) //设置背景音音量
LivePusherContext.setMICVolume(Object object) //设置麦克风音量
LivePusherContext.startPreview() //开启摄像头预览
LivePusherContext.stopPreview() //关闭摄像头预览
LivePusherContext.sendMessage(Object object) //发送SEI消息
Bug & Tip
-
tip
:开发者工具上暂不支持。 -
tip
:live-pusher 默认宽度为100%、无默认高度,请通过wxss设置宽高。 -
tip
:waiting-image
属性在 2.3.0 起完整支持网络路径、临时文件和包内路径。 -
tip
:请注意原生组件使用限制。 -
tip
: 相关介绍和原理可参考此文章
附上完整示例
!!!要注意两端推流播放 比如是a端和b端
a端 | b端 |
---|---|
pusher _a | pusher _b |
player_b | player_a |
<!-- wxml-->
<view class="chat">
<!--
binderrorPusher 渲染错误事件,detail = {errMsg, errCode}
bindstatechange 状态变化事件,detail = {code}
beauty-style 设置美颜类型 smooth 光滑美颜 nature 自然美颜
beauty 0 美颜,取值范围 0-9 ,0 表示关闭
device-position front 前置或后置,值为front, back
-->
<live-pusher
id="pusher"
class="{{is_small == 1 ?'small-window' : 'pusher'}}"
url="{{push_stream}}"
mode="RTC"
autopush
bindstatechange="statechangePusher"
beauty-style="smooth"
binderror="binderrorPusher"
data-class="{{is_small == 1 ?'small-window' : 'pusher'}}"
bindtap="bindPusherClick"
device-position="{{device_position}}" />
<!--
class 动态选择谁的屏幕放大
src 播放地址
mode 模式 live-直播 RTC-实时通话
binderrorPusher 渲染错误事件,detail = {errMsg, errCode}
beauty-style 设置美颜类型 smooth 光滑美颜 nature 自然美颜
auto-pause-if-open-native 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
bindstatechange 状态变化事件,detail = {code}
object-fit 填充模式
picture-in-picture-mode 设置小窗模式: push, pop,空字符串或通过数组形式设置多种模式(如: ["push", "pop"])
-->
<live-player
id="player"
src="{{pull_stream}}"
mode="RTC"
autoplay
bindstatechange="statechangePlayer"
beauty-style="smooth"
auto-pause-if-open-native="{{false}}"
object-fit="fillCrop"
picture-in-picture-mode="['push', 'pop']"
class="{{is_small == 2 ?'small-window' : 'player'}}"
data-class="{{is_small == 2 ?'small-window' : 'pusher'}}"
bindtap="bindPlayerClick"/>
<view class="fixed">
<view class="" style="height: auto;">
<image src="/images/gd.png" bindtap="hangUp"></image>
<image src="/images/qh.png" bindtap="cutCamera"></image>
</view>
</view>
</view>
//js
const app = getApp()
Page({
data: {
player: '',
pusher: '',
is_small: 2, // 1-pusher 2-player
pull_stream: 'https://domain/pull_stream',
push_stream: 'https://domain/push_stream'
},
onLoad() {
this.setData({
player: wx.createLivePlayerContext('player', this),
pusher: wx.createLivePusherContext(),
})
},
onReady() {},
onShow() {},
//状态变化事件
statechangePusher(e) {
const code = e.detail.code
this.colse(code, 'Pusher')
},
//状态变化事件
statechangePlayer(e) {
const code = e.detail.code
// 2004 视频播放开始
if (code == '2004') {
// this.data.pusher.pauseBGM()
}
this.colse(code, 'Player')
},
//挂断的操作
colse(code, type) {
console.log(type)
// 2006 视频播放结束
// -2301网络断连,且经多次重连抢救无效,更多重试请自行重启播放
// -1310 录屏被其他应用打断了
// 3001 RTMP -DNS解析失败
// 3002 RTMP服务器连接失败
// 3003 RTMP服务器握手失败
// 3005 RTMP 读/写失败,之后会发起网络重试
let code_list = ['2006', '-2301', '-1310', '3001', '3002', '3003', '3005']
if (code_list.includes(code)) {
if (type == 'Pusher') {
//挂断
this.hangUp()
} else {
//挂断视频、不推流、关闭页面
//请求后台接口
this.hangUp()
}
}
},
//渲染错误事件
binderrorPusher(errMsg, errCode) {
// 10001 用户禁止使用摄像头
// 10002 用户禁止使用录音
if (errCode == '10001' || errCode == '10002') {
api.showToast(errMsg)
}
},
//切换大小屏
bindPusherClick(e) {
const _class = e.currentTarget.dataset.class
if (_class == 'small-window' && this.data.is_small == 1) { // 说明pusher是小屏幕
this.setData({
is_small: 2
})
}
},
//切换大小屏
bindPlayerClick(e) {
const _class = e.currentTarget.dataset.class
if (_class == 'small-window' && this.data.is_small == 2) { // 说明player是小屏幕
this.setData({
is_small: 1
})
}
},
//挂断
hangUp() {
console.log('挂断');
// 停止推流,同时停止摄像头预览
this.data.pusher.stop({
success() {
//挂断视频、不推流、关闭页面
//dosomething
},
fail() {}
})
},
//切换摄像头
cutCamera() {
this.data.pusher.switchCamera({
success() {
console.log('切换摄像头 success!')
},
})
},
})
//css
.chat{
position: relative;
height: 100vh;
width: 100%;
}
.player,.pusher{
width: 100% ;height: 100%
}
.small-window{
position: absolute;
top: 0px;
right: 0px;
width: 150px;
height: 200px;
z-index: 100;
}
.fixed{
position: fixed;
bottom: 50px;
left: 50px;
right: 50px;
z-index: 101;
text-align: center;
}
.fixed image{
width: 50px;
height: 50px;
margin: 0px 40px;
}