企鹅辅导是腾讯推出的针对小学、初中、高中课外的在线学习服务平台。近期,企鹅辅导为了方便用户更加快速便捷地上课,新增了移动端H5的上课形式。本文主要介绍了H5“实时直播”和“点播”的选型,以及开发过程当中遇到的问题和解决方案。html
企鹅辅导的上课功能是产品较为核心的功能,而原有上课方式只有如下两种:APP上课/PC浏览器上课。react
如今多了一种上课方式和载体 —— 移动端H5上课。它的特色在于:分享连接便可进入指定课程指定节,易于传播。无需求下载app,进入连接报名便可上课。主要用于快速体验上课的场景ios
H5上课页入口git
1.非APP环境下,原先H5课程详情页、支付成功页,均提供入口进入H5上课页,(目前入口灰度)。对于买了课的学生在不下载APP的状况下,快捷进入辅导上课页,当即体验上课。github
2.经过群里指定分享连接,连接带有课程ID和节ID了,便可自动进入指定节的直播课堂,或查看节的回放内容。节ID缺省状况下,也会自动定位最近一节直播课。web
H5上课页功能需求chrome
1.上课直播: 经过腾讯云WebRTC直播(优先方案)以及hls直播(降级方案)两种方式完成。 2.点播回放: 经过腾讯云TcPlayer超级点播的进行hls播放。 3.讨论互动: 基于impush,学生能发送和接收文案+表情。其他互动场景经过toast提示。 4.课程大纲: 查看课程每节的内容及时间,可在同一页面直接切换节进行直播/回放。浏览器
其它需求 在PC Web 上课的业务逻辑基础上,还有如下需求: 一、根据不一样状态展现不一样封面。 二、横竖屏检测及播放、切换全屏播放。 三、网络类型检测及提示,wifi => 4G流量 暂停并提示等。 四、引导跳转:①购买检查引导跳转购课。②引导APP更好体验上课。缓存
WebRTC直播 降级hls直播bash
因为上课直播的同时,还有讨论区模块以及其余交互模块(如举手,答题等)。若是音视频存在延迟学生用户可以明显感知,体验会大打折扣。所以PlanA采用低延时的直播方案——WebRTC,时延能在1秒内。
但因为WebRTC在移动端兼容性并不够好,尤为在苹果机型上,须要IOS版本11.1.2以上才逐渐支持。所以须要一个兼容性覆盖面广的降级直播方案做为PlanB,保证能兜底直播。
降级直播方案采用HLS协议直播方式,它在移动端H5有良好普遍的兼容性。但时延较高达到20秒以上。所以降级直播时,讨论区的消息也顺延相应的时间长度,且禁止发言功能。经过只让学生单方面的接收互动消息,而不能发送消息的方式,来屏蔽低延时的感知。
这里为何不选用延迟比hls更少的其余直播方式做为降级方案呢? 缘由在于即使其余直播方式延时更少(如flv可达到5-7秒),可是降级方案优先考虑兼容性,而再也不考虑延迟。即使延迟5-7秒仍会被感知,也须要禁言等单向信息流的方式了抹掉延迟感,延迟多少反而没那么重要。因此采用移动端支持性更好的HLS。
点播回放
点播方案采用腾讯云点播服务,web侧使用加密 HLS 的点播方案。因为以前的在PC上经验,使用了腾讯云的超级点播播放器,目前这块相比直播更加稳定。因为防盗须要对加密点播资源解密,主要的工做就是token身份认证换取解密密钥等鉴权处理。
直播接入 webrtc使用的是在线教育webrtc-live-player,依赖腾讯云WebRTC SDK,须要支持到3.4.1以上(缘由IOS新版本已经支持unifiedPlan)。直播播放器接入,比较简单。肯定容器dom,指定相关业务上报模块。而后实例化,执行preconnect。
const opts = {
// 一些上报等配置项
};
this.webrtcPlayer = new WebRTCLivePlayer(opts);
this.preconnect();
复制代码
这里的preconnect,只是先创建webrtc的websocket信令链接,因为webtc的usersig可能不会第一时间下发。预先建立信令链接,能够节省信令建连的耗时。
当usersig准备好后,执行init,异步建立播放器成功后能够设置播放器宽高。最后执行connet进入webrtc的房间成功后,交换SDP以及candidate创建ICE链接,等待视频流后就能够播放了。
this.webrtcPlayer.init(sig, roomId).then(() => {
// 设置播放器宽高
this.webrtcPlayer.setContainerStyle({
width: xxx,
height: xxx,
});
this.webrtcPlayer.connect();
});
复制代码
固然接入webrtc播放器,实际业务层还有更多的事情要作,涉及流更新处理,视频宽高变化更新布局,重连机制,以及上报处理等。
降级Hls直播接入 基于TCPlayerLite,传入m3u8混流地址,实例化播放器便可。初始化参数请参考:cloud.tencent.com/document/pr…。
TCPlayerLite 采用 H5<video>
和 Flash 相结合的方式来进行视频播放,根据不一样的播放环境,播放器会选择默认最合适的播放方案。 在移动端须要使用m3u8,因为咱们获取混流地址业务接口提供rtmp,flv和hls地址,所以在移动端H5下传入options地址中,要把rtmp和flv去掉只传m3u8地址。
const player = new TcPlayer('tcplayer_container', {
"m3u8": "http://2157.liveplay.myqcloud.com/2157_358535a.m3u8",//举例
"autoplay" : false,(备注:true只对大部分 PC 平台生效)
"width":"100%",
"height":"100%",
"live":true,
"x5_player":true
});
复制代码
为了更好理解直播的步骤,本身梳理了直播的流程和架构图。
腾讯云TCPlayer点播接入
点播资源做为学生用户付费获取的资源,天然不可能凭借一个m3u8地址就能播放,必须是有加密的。所以点播的接入,除了须要点播资源fileId以外,还须要用户的身份信息校验用于视频解密。
加密HLS请参考:cloud.tencent.com/document/pr… 里面提供了两个视频播放方案。简单来讲都是用cookie生成token,经过业务的DK接口结果获取解密密钥DK。
方案1:经过 QueryString 传递身份认证token信息。
http://example.vod2.myqcloud.com/path/to/a/voddrm.token.ABC123.video.m3u8复制代码
方案2:播放器在访问 EXT-X-KEY 标签所标识的 URL 时会带上 Cookie。
但这里有坑点:PC上咱们采用了方案2,同域的业务dk接口带上cookie是没有问题,可是在安卓H5下怎么也播不了。抓包发现,在安卓下明明是同域的接口却没法携带任何cookie。查回文档才发现这么一句。
看来咱们在移动端H5上咱们没法采用和PC同样简单的方案二,可是方案一根据cookie生成token外,还要在播放地址参数,而咱们本来只有filedId和dk接口,而并不是用m3u8地址进行播放。 通过请教课堂的同事,原来Tcplayer提供一个文档中没有的参数HLStoken来传递token。
而这里的token,在播放器请求/cgi-bin/qcloud/get_dk时携带token,这样安卓浏览器上没办法携带cookie也不要紧。
this.player = new window.TCPlaye(this.elIDId, {
fileID, // 请传入须要播放的视频filID 必须
appID // 请传入点播帐号的appID 必须
autoplay: false,
plugins: {
HLSToken: {
token, //根据cookie生成的token,这个参数文档中并无。
},
},
});
复制代码
因为移动端H5除了系统及版本(IOS和安卓)各不相同、浏览器环境(微信、手Q和自带浏览器)也比PC复杂不少。加之WebRTC在移动端兼容性问题,从ios11.1.2到最新的12.4表现也存在差异。所以特地把一些踩过的主要坑作下总结。
error
、timeupdate
、load
、loadedmetadata
、loadeddata
、progress
、fullscreen
、play
、playing
、pause
、ended
、seeking
、seeked
、resize
、volumechange
解决方法:部分机器为触发播放后,才触发loadmeatadata和loaddata。调整首帧时间为可播放时间。<video controls >
设为false,本身用dom实现控制栏,两个控制栏必备按键为:静音键,设置video.muted=true/false便可,另外一个全屏键,参考下面全屏方案以及requestfullscreen API 相关内容。至于网页全屏怎么实现,这里最简单的方式:进入网页全屏时,video设为fixed,调整z-index值,control栏同理占满。在横竖屏下处理稍有不一样,对比屏幕和video的宽高比例,以宽或高设置为100%,另外一边按比例计算便可,最终目的是达到contain的填充效果。
支持屏幕全屏的接口有两种,一种称为 Fullscreen API,经过 Fullscreen API 进入屏幕全屏后的特色是,进入全屏后仍然能够看到由 HTML CSS 组成的播放器界面。另外一种接口为 webkitEnterFullScreen,该接口只能做用于 video 标签,一般用于移动端不支持 Fullscreen API 的状况,经过该接口全屏后,播放器界面为系统自带的界面。
在实际业务中,对于全屏优先执行video.webkitEnterFullscreen();
(苹果只支持这个API),当执行不成功则再执行video.webkitRequestFullScreen();
。
这里我使用了第三方检测代码:github.com/shrekshrek/… 这断代码除了能检测横竖屏外,还能检测重力及水平仪角度等,而且作了多终端兼容处理。
使用方法很简单:初始化监听,当dir值发生变化即为横竖屏变化,随即更新布局便可。
const _orienter = new window.Orienter();
this.props.updateOrientation(_orienter.direction); //先判断一次当前横竖屏
_orienter.onOrient = function (obj) {
if (this.dir !== obj.dir) {
this.dir = obj.dir;
setTimeout(() => {
//延迟200ms更新布局
this.props.updateOrientation(obj.dir);
}, 200);
}
};
//开始监听横竖屏
_orienter.on();
复制代码
注意:这里有设置200毫秒延时,缘由是当横竖屏变化以后,取到的屏幕宽高仍然是“变化前”的屏幕宽高,当即更新布局会有问题。所以延迟200毫秒再更新布局。
当拿到需求和交互视觉稿后,会发现一节课的有多种的封面,且封面可能在一个页面中随时间发生来回变化。这一切都源于上课状态的复杂,判断的维度有三个:1.当前时间。2.老师端是否上课。3.是否有视频流。 所以如何控制封面的变化,主要是维护和控制课堂状态的store值,而后映射封面便可。
封面状态设计:采用分级封面,状态映射来控制。组封面覆盖在子封面之上。 主封面组件:负责时间维度及CGI能够判断的大的课程状态,单向流程,不会切换。 子封面组件:负责播放状态的控制,根据老师上课状态、流状态控制,非单向流程,可能会来回切换。
在PC浏览器上课,通常都是wifi和有线上网,可是在移动端浏览器看直播和点播,除了wifi可能会消耗流量,需求是须要可以检测当前的网络状态(流量模式/wifi模式)进行判断。从而给用户响应的提示,避免用户在不知情的状况下消耗流量。
经过浏览器自己navigator.connection.type
确实能够判断浏览器,但只有chrome支持移动端,其余浏览器几乎都不可用。
所以判断网络类型,仍是要依赖于app提供的能力,基于两大app内浏览器,手Q和微信都有提供JS API判断网络类型,下面是代码实现。
return new Promise((resolve, reject) => {
if (window.WeixinJSBridge) {
//微信检测networktype
window.WeixinJSBridge.invoke('getNetworkType', {}, (e) => {
resolve({
isNetWorkFlow:
e.err_msg !== 'network_type:wifi' &&
e.err_msg !== 'network_type:fail',
isNetworkBroken: e.err_msg === 'network_type:fail',
});
});
}
if (window.mqq) {
//手Q检测networktype
window.mqq.device.getNetworkInfo((res) => {
resolve({
isNetWorkFlow: res.type === 2 || res.type === 3 || res.type === 4,
isNetworkBroken: res.type === 0,
});
});
} else {
reject();
}
});
复制代码
经过每3秒轮讯回调,除了判断网络类型是否为流量isNetWorkFlow
,还判断当前网络isNetworkBroken
是否断开。最后根据回调结果基于不一样的提示。
在PC web项目中,每一个上课页只对应一节课,连接的参数对应每节课的信息和当前课堂状态。
https://fudao.qq.com/pc/webclass.html?term_id=2000010153&course_id=113536&lesson=0&lesson_id=93561&status=2&sub_termid=0复制代码
course_id
和term_id
: 课程id和班级id lesson_id
: 节id status
:课堂状态 0未开始、1直播中、2回放、三、生成回放中 sub_termid
:小班id
在PC浏览器,从课程任务页跳到上课页,连接上的query参数在上个页面已经肯定,能够当即拿到status参数(由时间计算),这样有个好处就是,页面加载能当即根据课堂状态加载播放器和相应样式,不须要等待CGI请求;而坏处是切换课程是要从新加载刷新页面。
因为H5上课页包含了“课程大纲”,至关于把本来PC上的“课程任务页”和“上课页”集中在一个页面,产品但愿能达到如下要求: 1. 课程切换不刷新页面。 2. 当连接上没有lesson_id,能定位到最近的未开始的一节课。 3. 切换节后,分享的连接是能定位到当前节。
以上三点就是实现起来比较困难,缘由在于本来代码中,有至关的多地方是从连接上一次性获取参数,而且因为分享机制,也须要在不刷新页面修改连接参数,而且从新获取连接参数,从新加载播放器以及字幕组件。实现一个switchLesson方法,思路以下: