Android 系统播放器的一些使用注意事项

公司做投屏项目的,需要播放各种客户端推送的直播点播视频,根据做播放器的一些经验,这里总结一些在应用层使用播放器的注意事项和一些优化方法。
PS:个人经验之谈,不具备权威性

播放器的使用过程中常能见到以下的几种问题:

  • 黑屏有声音
  • 播放失败
  • 播控状态保护
  • ANR
  • 倍速播放的问题

一、黑屏有声音

黑屏有声音是一种见的比较多的问题,能导致这种问题的原因很多。大致有以下几类:

  • 播放器调用异常
  • 播放器抢占
  • 播放器底层创建或解码异常
  • 播放器能力不足
  • 对Android标准api的支持不友好

1、播放器调用异常

对Android播放器的使用要小心翼翼,调用要小心小心再小心,一旦有一丢丢的疏忽,立马就会黑脸给你看。

下面的播放器调用问题会导致黑屏:

1) surface有异常

surface对播放器来说是一个很重要的东西,不管在应用层你是用SurfaceView、GLSurfaceView还是TextureView,最终都要生成一个surface给到播放器。如果这个surface有异常,就会导致播放器解码的数据无法正确渲染,可能会出现黑屏。

在应用层我们要保证这个surface是可用的并且是干净的,也就是说我们需要做到以下几点:

  • 需要在surface完全创建好之后才能调用播放器的setSurface方法
  • 在surface被销毁后,要停止播放器
  • surface不复用,播放一个视频,创建一个新的surface
2)视频源不可用

当视频源不可用的时候,一般的设备上,播放器会回调onError,但是在部分设备上会黑屏,不会回调onError。碰到这种设备的时候,尽量不要使用系统播放器,能避开系统播放器就避开。在这种设备可以考虑使用第三方播放器,ijk、vlc等。如果避不开,也要做一些处理,假如视频源是本地视频,可以先检查文件存不存在,size是否为0,若这个视频是从服务器下载的,可以校验md5是否正确;假如这个视频源是在线视频,那么需要添加一个加载超时检测机制,比如说30秒内如果还未加载到视频,就可以认为是播放失败。

3) start和seek

要在合适的时候调用start,在onPrepare、onSeekComplete、onInfo的MEDIA_INFO_BUFFERING_END消息中及时的调用MediaPlayer的start方法,部分设备上会因为在onPrepare中没有调用mediaPlayer.start导致视频黑屏有声音。

在播放直播视频的时候,在部分设备上调用MediaPlayer的seek也会出现黑屏,那么在seek之前要检查当前视频是否是直播视频,当前是根据视频的时长来判断是否为直播的,一般情况下直播的视频时长是0,在部分设备上播放器可能会返回-1,除此之外,还有一些直播平台的视频是2秒,这些在做直播视频判断的时候都要考虑到。

4)SurfaceView设置 setZOrderMediaOverlay(true)

在海信电视上如果使用SurfaceView可以设置此属性

2、播放器抢占

一般的一个Android设备只有一个硬件解码器,若在解码器使用期间再次创建一个新的解码器就会出现异常,导致视频播放黑屏。所以在创建播放器的时候要保证当前没有正在使用的播放器实例。若当前正在播放视频,此时用户从手机上投屏了一个新的视频,那么需要等待前一个新的播放器完全释放之后才能创建新的播放器。这就是播放器的抢占问题,程序自身内的播放器抢占可以等待前一个视频播放器完全释放之后再创建新的播发器,如果抢占的是第三方app的播放器,那就需要有一种方式通知第三方关闭播放器,这种就需要app开发者与第三方app沟通好,提出一种双方认可的方式来完成这个操作。

3、播放器底层创建或解码异常

播放器出现异常不可怕,可怕的是出现异常还不给应用层回调onError,下面两段log分别是创建播放器异常和播放器解码异常,不回调onError消息,然后就黑屏了,上层代码完全无感知

Video: h264 (avc1 / 0x31637661), yuv420p, 720x1272, 1749 kb/s
mime=video/avc, profile=100, level=31
MS_OMX_H264DEC: Error Code: 3001007 0 0 0
.......
11913 10-11 17:58:28.639 2322 9297 D SurfaceUtils: set up nativeWindow 0xd1956008 for 1280x720, color 0x32315659, rotation 0, usage 0x40002930
11914 10-11 17:58:28.640 2322 9297 E OMXNodeInstance: setParameter(0xd18f8bc0:MS.AVC.Decoder, ParamPortDefinition(0x2000001)) ERROR: BadParameter(0x80001005)
11915 10-11 17:58:28.640 2322 9297 W ACodec : [OMX.MS.AVC.Decoder] setting nBufferCountActual to 6 failed: -22
11916 10-11 17:58:28.640 2322 9297 E OMXNodeInstance: setParameter(0xd18f8bc0:MS.AVC.Decoder, ParamPortDefinition(0x2000001)) ERROR: BadParameter(0x80001005)
11917 10-11 17:58:28.640 2322 9297 W ACodec : [OMX.MS.AVC.Decoder] setting nBufferCountActual to 5 failed: -22

碰到这样的情况,有资源的可以将这种情况反馈到设备厂商或芯片厂商帮忙排查问题,同时在应用层需要针对这些设备机型或视频源做一些特殊处理,可以尝试切换SurfaceView、GLSurfaceView、TextureView,同时可以考虑使用第三方播放器。

4、播放器能力不足

4k视频、h265视频、mpeg4视频,对播放器本身有要求,若芯片不支持解码这些格式,也会出现播放黑屏的现象。对于h265视频、mpeg4视频可以尝试使用第三方播放器的软解播放,对于4k视频,给用户弹个toast提示吧,不支持是真没办法。

5、对Android标准api的支持不友好

部分设备上设置循环播放导致黑屏,通常情况下是设置循环播放属性之后,第二轮播放的时候会黑屏,可能是因为用这个属性的app不多,部分厂商的播放器不太关注这里。在这些设备上就要把使用循环播放的功能给禁掉,这种配置可以在程序里面根据机型做个判断,也可以在后台服务器下发,这两种方式最好结合一起使用。

二、播放失败

导致播放失败的原因有很多,有些是视频源异常,有些是播放器异常。在播放器异常的时候,我们就可以尝试使用第三方播放器再播放一次,通常情况下是可以播放成功的。比如说有些设备不支持flv格式的视频播放,切换到ijk播放器就可以播放成功。

在播放器的选择上,默认情况下,我们会优先选择系统播放器,我们相信厂商自身会比我们更在意他们自己的播放器能力,而且有能力的厂商一般都有专门的播放器团队,出现问题也能通过他们自身的资源去找芯片厂商处理。

关于第三方播放器,基于自身的见识,推荐ijk或vlc,我们项目是使用ijk作为备用播放器,从上线几年的情况来看,还是挺好的。国内有一个大的电视机厂商播放器系统底层是使用vlc来做的,从我个人的体验来看,也是很好的。

还有一类播放失败是由于在做了不合法的播放器操作导致的,比如在prepared状态下调用了pause,有些设备上的播放器可能回调onError,这种情况下我们要做好播控状态保护机制。

三、播控状态保护

播控.png

从上图可以看出,播放器的播控操作对当前的播放状态是有要求的,比如上面提到的在prepared状态下调用了pause,有些设备上的播放器可能回调onError,从上图可以看出pause在播放器started之后才能调用。关于如何实现你自己的播控状态保护,可以参考Android源码VideoView中的播控状态保护。

public class VideoView extends SurfaceView
        implements MediaPlayerControl, SubtitleController.Anchor {
    private static final String TAG = "VideoView";

    // all possible internal states
    private static final int STATE_ERROR = -1;
    private static final int STATE_IDLE = 0;
    private static final int STATE_PREPARING = 1;
    private static final int STATE_PREPARED = 2;
    private static final int STATE_PLAYING = 3;
    private static final int STATE_PAUSED = 4;
    private static final int STATE_PLAYBACK_COMPLETED = 5;

    ...

    // mCurrentState is a VideoView object's current state.
    // mTargetState is the state that a method caller intends to reach.
    // For instance, regardless the VideoView object's current state,
    // calling pause() intends to bring the object to a target state
    // of STATE_PAUSED.
    private int mCurrentState = STATE_IDLE;
    private int mTargetState = STATE_IDLE;

    ...

    @Override
    public void start() {
        if (isInPlaybackState()) {
            mMediaPlayer.start();
            mCurrentState = STATE_PLAYING;
        }
        mTargetState = STATE_PLAYING;
    }

    @Override
    public void pause() {
        if (isInPlaybackState()) {
            if (mMediaPlayer.isPlaying()) {
                mMediaPlayer.pause();
                mCurrentState = STATE_PAUSED;
            }
        }
        mTargetState = STATE_PAUSED;
    }
    
    private boolean isInPlaybackState() {
        return (mMediaPlayer != null &&
                mCurrentState != STATE_ERROR &&
                mCurrentState != STATE_IDLE &&
                mCurrentState != STATE_PREPARING);
    }
}

VideoView的mCurrentState记录当前播放器的状态,在做pause和start操作的时候,会先检查播放器的状态。

添加播控状态保护,一定程度上也可以减少播放器抛出IllegalStateException、减少播放器ANR

    /**
     * Pauses playback. Call start() to resume.
     *
     * @throws IllegalStateException if the internal player engine has not been
     * initialized.
     */
    public void pause() throws IllegalStateException {
        stayAwake(false);
        _pause();
    }

    private native void _pause() throws IllegalStateException;

另外这里提一下,在调用MediaPlayer这些方法的时候,尽量添加try{}catch(){},怎么着也不能崩溃,是不?

四、ANR

如果你的播控状态保护做好了,仍然发生anr,那么请联系厂商解决,一般情况下不建议自行处理。比如在有些设备上,播放器在onPrepare回调之前调用release,一定会出现anr,开始我们做了处理,若果当前视频未加载成功,只调用stop,不调用release,这个anr问题确实解决了。但是在后来的测试抢占逻辑的时候出现了很多视频黑屏,厂商反馈说是由于前一个视频未调用release导致的,最后我们将代码回退,厂商处理的那个问题。如果联系不到厂商,只能自行处理,也请处理之后仔细全面的做一次播放器的全用例测试,避免引起更严重的问题。

五、倍速播放的问题

现在国内水剧太多,很多人都用倍速播放看剧,这里简单提下,在调用Android标准倍速播放接口的时候,会发现大部分的TV、盒子设备的标准接口是无效的,甚至调用会导致崩溃,那么在TV、盒子上实现倍速播放建议使用第三方播放器来做。
标准播放器接口的无效的情况还有其它的,例如setDataSource(String path, Map<String, String> headers),在很多设备上也无效,所以在TV、盒子上使用系统播放器还要仔细的测试设备的兼容性。

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

推荐阅读更多精彩内容