手机浏览器上视频和音频自动播放问题

记录一个线上bug!

问题场景:

iphone6 safari浏览器打开爱奇艺电视剧播放页,如你好,旧时光,然后直接点第二集,播放器一直显示loading浮层,如下图所示

image.png

预期结果应该是可以正常加载第二集视频并播放的。但是如果你进入播放页后,先点击第一集的播放按钮,等待视频播放后,再切第二集,又是可以正常播放的。

这是一个兼容性问题,因为用iphone6 qq浏览器无法复现。因此我首先作了一个统计,看哪些浏览器可以复现,针对ios和andorid各选了几款机型,统计如下:

  1. 机型:iphone8/iphone x/iphone7P,系统:ios11+
    safari、qq、百度、手百均没有问题
  2. 机型:iphone6,系统:ios10
    qq没有问题,safari、百度、手百有问题
  3. 机型:iphone5,系统:ios8.1
    qq没有问题,safari、百度、手百有问题
  4. 机型:小米6,系统:android7
    qq、小米浏览器没有问题,chrome、百度、手百有问题
  5. 机型:oppo r9,系统:android6
    qq没有问题,chrome、百度、手百、系统自带浏览器有问题

从以上来看,安卓chrome、百度、手百以及ios10以下的safari、百度和手百均是有问题的,QQ浏览器比较特殊,在android和ios均表现正常。

视频播放的过程

我们先分析下播放视频的流程,首先会先向后台发送鉴权请求,其实就是根据你的用户信息,判断是否播广告、试看6分钟或是否要用券等等信息,比较复杂,但最终而言,都是为了拿到一个视频的播放链接(如果是鉴权后无法播放的,那就需要显示播放器的提示浮层了,暂不考虑这种情况)。然后再分析以下几种操作场景的区别:
1. 打开播放页,直接点封面图的播放按钮
打开页面的过程中,js会提前发送鉴权请求拿到播放的url地址(这是为了缩短用户开播时间而做的优化),然后将url赋值给video标签的src属性,所以打开页面后,video标签其实已经赋上第一集的播放地址了。然后我们点击封面图的播放按钮,执行点击事件的函数,最终调用video对象的play()方法,从而正常播放视频。所以这一步,没有请求接口拿播放地址的操作。

2.打开播放页,点击封面图的播放按钮,播放后,再切点击第二集
前面的操作是一样的,来看看点击第二集的操作过程,其实比较类似,点击后需要先发鉴权请求拿到第二集的播放url,然后将url地址赋值到video的src属性,最终调用video的play()方法播放视频。到现在为止,一切还都正常, 符合我们的预期,接下来看看第三种操作。

3. 打开播放页,直接点击第二集
这一次,我们没有先播放第一集,而是直接想播放第二集视频,结果就GG了,播放器一直显示loading浮层。对于loading浮层的显示逻辑,应该是点击切换剧集就会显示,然后待拿到播放url,赋值src属性,调用play()播放视频后就应该隐藏的,实现方式就是监听播放器的playing事件,一般播放器发送playing事件就表示视频第一帧已经播放了。所以,loading浮层不消失的原因应该是播放器没有触发playing事件,或者说视频没有正常播放。

我们对比一下操作场景1和场景3的区别,场景1在点击按钮时,video标签的src属性已经赋上,而场景3点击剧集,需要调用一个后台接口拿到播放链接,然后赋值到src,调用play(),所以两者的区别应该是多了一次请求鉴权的过程,这个请求需要一定的时间,所以我们猜想,是不是这个时间太长导致。

为了模拟以上几种场景,写了一个简单的页面进行模拟。demo非常简单,就是一个播放器,加几个基本的按钮,可以实现视频的播放、暂停与切换。



基本代码如下:

<!DOCTYPE html>
<html>
<head>
    <title>video兼容性测试</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
</head>
<script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
<script type="text/javascript" src="//static.qiyi.com/js/html5/js/lib/lib.2.0.1.min.js?sea1.2.min.js"></script>
<style>
    #num {
      width: 100%;
      height: 100%;
      text-align: center;
      font-size: 48px;
      font-weight: bold;
    }
</style>
<body>
<video id="video" src="http://qamp.qiyi.domain/static/03.mp4" width="100%" height="100%"
    controls   poster="http://qamp.qiyi.domain/static/03.jpg">
    不支持video标签显示这段文字
</video>
<div>
    <button type="button" onclick="play()"> 播放 </button>
    <button type="button" onclick="pause()"> 暂停 </button>
    <button type="button" id="video1" onclick="playVideo(this.id)"> 第一集 </button>
    <button type="button" id="video2" onclick="playVideo(this.id)"> 第二集 </button>
    <button type="button" id="video3" onclick="playVideo(this.id)"> 第三集 </button>
    <img src="http://qamp.qiyi.domain/static/loading.jpg" alt="" style="display:none">
    <div id="num"></div>
</div>
<script>
    albumData = {
        'video1': {
            'src' : 'http://qamp.qiyi.domain/static/01.mp4',
            'img' : 'http://qamp.qiyi.domain/static/01.jpg'
        },
        'video2': {
            'src' : 'http://qamp.qiyi.domain/static/02.mp4',
            'img' : 'http://qamp.qiyi.domain/static/02.jpg'
        },
        'video3': {
            'src' : 'http://qamp.qiyi.domain/static/03.mp4',
            'img' : 'http://qamp.qiyi.domain/static/03.jpg'
        }
    }
    function sleep(d){
        for(var t = Date.now();Date.now() - t <= d*1000;){};
    }
    function play() {
        $('video')[0].play()
    }
    function pause() {
        $('video')[0].pause()
    }
    function playVideo(tvid) {
        sleep(1.51)
        video = $('video')[0]
        video.src = albumData[tvid]['src']
        video.poster = albumData[tvid]['img']
        //setTimeout(() => video.play(),0)
        video.play()
        $('#num').text('playing');
    }
</script>
</body>
</html>

最后发现部分移动端浏览器对于视频的播放有如下限制条件:

  • 从触发事件开始,到调用video对象的play()方法,时间上不能超过1000ms
  • setInterval函数里,只有第一次就调用play()才能成功,原因不明
为什么手机端浏览器会有这个限制

这大概要从手机流量说起,我记得在上大学那会,2008年左右,那时候还是PC时代,智能机还不怎么流行,流量也是非常昂贵,5块钱大概才30M,所以为了用户着想,浏览器禁止了autoplay和js的加载播放,防止流量偷跑的现象,而且在移动流量下播放视频,即使是手动触发视频播放,浏览器也会再次弹窗让用户确认,确认后才放行操作。但随着流量越来越便宜,苹果在IOS11版本取消了该限制。
以下摘自苹果的文档:

    In Safari on iOS (for all devices, including iPad), where the user may
be on a cellular network and be charged per data unit, preload and
autoplay are disabled. No data is loaded until the user initiates it.
This means the JavaScript play() and load() methods are also inactive
until the user initiates playback, unless the play() or load() method
is triggered by user action. In other words, a user-initiated Play
button works, but an onLoad="play()" event does not.

android4.2也引入了与ios类似的方案,由mediaPlaybackRequiresUserAction参数控制,默认是YES,所以必须由用户触发的行为,调用play()方法才有效,而onload事件是没法播放的,这也就是为什么手机浏览器无法通过js控制自动播放。
详细文档参考:http://fqk.io/issues-of-audio-video-in-webview/

时间上的限制

为了确定具体的时间限制。在demo代码中有一个sleep函数,可以控制从点击到调play()方法的时间,在各个浏览器上实验后,统计如下:

  • Android端浏览器:
    QQ浏览器:60+s(暂不知道上限)
    chrome/百度/手百/360极速: 1000ms
  • IOS端浏览器:
    QQ浏览器:60+s(暂不知道上限)
    ios 10以下的safari/chrome/百度/手百: 1000ms
    ios11以上safari/chrome/百度/手百:无限制

于是统计了下爱奇艺电视剧播放页,切换剧集时,从点击到开播广告,大概是1200ms左右,所以超过了1s的时间限制,导致play()被浏览器视为非用户行为,所以无法播放。

注:如果成功播放视频后,之后的操作就没有时间限制了,所以当进入页面后先播放第一集,然后再切第二集,就不会一直loading了

关于在setInterval函数里调用play()

发现这个问题纯属巧合,至今也没明白为什么在setInterval函数里调用play()会失败。

在写demo的时候,本想用setInterval实现一个倒计时的功能,setInterval会每隔一定时间调用某个固定函数intervalFunc,最后发现只有在第一次调用的intervalFunc函数里执行video.play()才有效,后面再调用的都无法播放视频,即使设置的时间很短也不行,代码如下:

    function playVideo(tvid) {
        var count = 20  //单位ms
        var intervalTime = 10  //每隔多久执行一次
        $('#num').text(count);
        var inter = setInterval(function intervalFunc() {
            count = count - intervalTime ;
            $('#num').text(count);
            if (count == 0) {
                //只有第一次就走到这分支才能播放
                clearInterval(inter);
                video = $('#video')[0]
                video.play()
                video.pause()
                video.src = albumData[tvid]['src']
                video.poster = albumData[tvid]['img']
                //setTimeout(() => video.play(),0)
                video.play()
                $('#num').text('playing');
            }
        }, intervalTime);
    }

上面代码大概意思是从20ms开始倒计时,每隔10ms递减一次,每次减10ms,等到为0时,执行play(),但是失败了,20ms如果是放在同步执行的代码里,是没有问题的。
猜想可能是因为后面执行的intervalFunc被认为是非用户触发的,所以被play请求被拦截了。
在看H5站源代码时候,里面发送请求的代码都用了promise异步写法,于是改造了一下demo,如下:

    function getSrc(tvid) {
        sleep(1.9)
        return new Promise(function (resolve, reject) {
            console.log(albumData[tvid])
            resolve(albumData[tvid])
        })
    }
    function playVideo(tvid){
        getSrc(tvid).then((data)=>{
            video = $('video')[0]
            video.src = data['src']
            video.poster = data['img']
            video.play()
        })
    }

改完后在小米6测试,发现用promise的写法是没问题的,结果一致,只有sleep超过1s才会出现问题。
然后在stackflow发现这样一篇文章,play-request-was-interrupted,里面说

image.png

大概意思就是,绑定点击事件的函数,不要使用async异步写法,否则会失去之后允许视频播放的用户行为token,也就是会被浏览器认定为非人为操作导致无法播放。
于是我将点击函数改为async异步模式,代码如下:

   function getSrc(tvid) {
        sleep(3.99)
        return new Promise(function (resolve, reject) {
            console.log(albumData[tvid])
            resolve(albumData[tvid])
        })
    }
    async function playVideo(tvid){
        await getSrc(tvid).then((data)=>{
            video = $('video')[0]
            video.src = data['src']
            video.poster = data['img']
            video.play()
            $('#num').text('playing');
        })
    }

在小米6上测试,发现使用async也是没问题的,只有在超过1s才会播放失败。

如何解决视频自动播放的限制

如果产品非得要求做到自动播放,该如何做呢?答案是在点击后立即调用play()方法,如果video原本有src的话,会播放之前视频,所以要立马调用pause()方法,但有可能会报Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause(),这个错倒不要紧,无关紧要,参考play() request was interrupted,要暂停视频,也可以先将video的src清空,这样也是可以的。
代码如下:

    function getSrc(tvid) {
        sleep(3.99)
        return new Promise(function (resolve, reject) {
            console.log(albumData[tvid])
            resolve(albumData[tvid])
        })
    }
    async function playVideo(tvid){
        //立即调用play
        try{
            $('video')[0].play()
            $('video')[0].pause()  
        } catch(e){
            console.log(e)
        }
        await getSrc(tvid).then((data)=>{
            video = $('video')[0]
            video.src = data['src']
            video.poster = data['img']
            video.play()
            $('#num').text('playing');
        })
    }

真正解决手机视频自动播放问题

http://levisft.beats-digital.com
http://campaign.wandoujia.com/market/ifly/index.html?ch_src_share=pp_aty_wechatshare
https://yq.aliyun.com/mk/01/index.php
http://nigg.treedom.cn/?dskid=ccc003
http://m.creatby.com/v2/manage/book/bcz2jo/
http://h5.flyfinger.com/2016/tencentfun/
https://lieyu.qq.com/cp/a20170213ane/index-wx.html?
http://jzsg.lxustudio.cn/

说说音频的自动播放问题

目前很多H5活动页面都会带有背景音乐,但音频也有类似的问题,具体的表现是在大多数安卓机上是可以自动播放,但是在ios上会有限制,那该如何解决呢?

1. 方法一:监听touchstart事件,用户触摸屏幕后会自动播放

document.addEventListener('touchstart', function(){ 
    audio.play();
}, false);

方法二:依赖微信的ready事件进行,可以解决ios微信的问题

document.addEventListener("WeixinJSBridgeReady", function () {
    audio.play();
}, false);
参考文献:

https://developers.google.com/web/updates/2017/06/play-request-was-interrupted
https://segmentfault.com/a/1190000007864808

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容