基于ExoPlayer搭建Android网络电视应用

2019-02-01  本文已影响0人  Lurky

       在Android开发直播应用时,大家都会首先想到找一个开源的第三方播放器框架,只是这些开源框架的更新维护都几乎停滞了,为什么呢?Android已经发展了10多年了,官方的播放器已经非常强大,可以支持当前的主流播放需求。ExoPlayer就是Google推出的官方Android媒体播放组件,具体可以参考:https://developer.android.com/guide/topics/media/exoplayer,Demo源码在:https://github.com/google/ExoPlayer

       现在我们来基于ExoPlayer搭建一个Android网络电视应用:

1. 集成ExoPlayer:

Android集成步骤,参考:https://github.com/google/ExoPlayer

自定义播放器控件:

public class ExoPlayerLayout extends RelativeLayout {

    //播放器相关定义

    private PlayerView mPlayerView;

    private DataSource.Factory mDataSourceFactory;

    private DefaultBandwidthMeter mDefaultBandwidthMeter;

    private SimpleExoPlayer mPlayer;

    private MediaSource mMediaSource;

    protected String mUserAgent;

    private Cache mDownloadCache;

    private boolean mNewPlayFlag = false;

    private long mPlayerSuspendStart = 0; //卡顿开始时间点

    private static final String TAG = "ExoPlayerLayout";

    //节目列表相关定义

    private TVProgramBean mTVProgramBean = null;

    private String mTVProgramUrl = "";

    private Handler mEventHandler;

    public ExoPlayerLayout(Context context) {

        super(context);

    }

    public ExoPlayerLayout(Context context, AttributeSet attrs) {

        super(context, attrs);

    }

    public ExoPlayerLayout(Context context, AttributeSet attrs, int defStyleAttr) {

        super(context, attrs, defStyleAttr);

    }

    public boolean initView() {

        Log.i("ExoPlayerLayout",

                "initView:");

        //播放器控件初始化

        mPlayerView = new PlayerView(this.getContext());

        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(this.getWidth(), this.getHeight());

        mPlayerView.setControllerAutoShow(false);

        mPlayerView.setUseController(false);

        mPlayerView.setFocusable(false);

        this.addView(mPlayerView, RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);

        //事件处理Handler初始化

        mEventHandler = new Handler(Looper.getMainLooper());

        //数据初始化

        mUserAgent = Util.getUserAgent(this.getContext(), "LoveTV");

        mDataSourceFactory = buildDataSourceFactory();

        return true;

    }

    //开始节目播放

    public void startProgram(TVProgramBean tvProgramBean) {

        mNewPlayFlag = true;

        palyTV(tvProgramBean, "");

    }

    //播放节目

    private void palyTV(TVProgramBean tvProgramBean, String defalutTVProgramUrl) {

        mTVProgramBean = tvProgramBean;

        if (null != mPlayer) {

            if (null != mPlayerView) {

                mPlayerView.onPause();

                this.removeView(mPlayerView);

                mPlayerView = new PlayerView(this.getContext());

                RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(this.getWidth(), this.getHeight());

                mPlayerView.setControllerAutoShow(false);

                mPlayerView.setUseController(false);

                mPlayerView.setFocusable(false);

                this.addView(mPlayerView, RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);

            }

            mPlayer.release();

            mPlayer = null;

        }

        mPlayer =

                ExoPlayerFactory.newSimpleInstance(

              /* context= */ this.getContext());

        //设置播放器事件监听

        mPlayer.addListener(new PlayerEventListener());

        mPlayer.setPlayWhenReady(true);

        mPlayerView.setPlayer(mPlayer);

        mTVProgramUrl = defalutTVProgramUrl;

        if (!TextUtils.isEmpty(tvProgramBean.getSrcFHDUrl())) {

            mTVProgramUrl = tvProgramBean.getSrcFHDUrl();

        } else if (!TextUtils.isEmpty(tvProgramBean.getSrcHDUrl())) {

            mTVProgramUrl = tvProgramBean.getSrcHDUrl();

        } else if (!TextUtils.isEmpty(tvProgramBean.getSrcUrl())) {

            mTVProgramUrl = tvProgramBean.getSrcUrl();

        }

        mMediaSource = buildMediaSource(Uri.parse(mTVProgramUrl), null);

        mPlayer.prepare(mMediaSource, true, false);

        startLoading();

    }

    private MediaSource buildMediaSource(Uri uri) {

        return buildMediaSource(uri, null);

    }

    @SuppressWarnings("unchecked")

    private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {

        @C.ContentType int type = Util.inferContentType(uri, overrideExtension);

        List<StreamKey> keysList = new ArrayList<>();

        switch (type) {

            case C.TYPE_DASH:

                return new DashMediaSource.Factory(mDataSourceFactory)

                        .setManifestParser(

                                new FilteringManifestParser<>(new DashManifestParser(), keysList))

                        .createMediaSource(uri);

            case C.TYPE_SS:

                return new SsMediaSource.Factory(mDataSourceFactory)

                        .setManifestParser(

                                new FilteringManifestParser<>(new SsManifestParser(), keysList))

                        .createMediaSource(uri);

            case C.TYPE_HLS:

                return new HlsMediaSource.Factory(mDataSourceFactory)

                        .setPlaylistParserFactory(

                                new DefaultHlsPlaylistParserFactory(keysList))

                        .createMediaSource(uri);

            case C.TYPE_OTHER:

                return new ExtractorMediaSource.Factory(mDataSourceFactory).createMediaSource(uri);

            default: {

                throw new IllegalStateException("Unsupported type: " + type);

            }

        }

    }

    public void releaseProgram() {

        if (null != mPlayerView) {

            mPlayerView.onPause();

            this.removeView(mPlayerView);

        }

        if (mPlayer != null) {

            mPlayer.release();

            mPlayer = null;

            mMediaSource = null;

        }

    }

    /**

    * Returns a {@link DataSource.Factory}.

    */

    public DataSource.Factory buildDataSourceFactory() {

        //设置带宽监测

        mDefaultBandwidthMeter = new DefaultBandwidthMeter();

        DefaultDataSourceFactory upstreamFactory = new DefaultDataSourceFactory(this.getContext(),

                mUserAgent, mDefaultBandwidthMeter);

        return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache());

    }

    private static CacheDataSourceFactory buildReadOnlyCacheDataSource(

            DefaultDataSourceFactory upstreamFactory, Cache cache) {

        return new CacheDataSourceFactory(

                cache,

                upstreamFactory,

                new FileDataSourceFactory(),

        /* cacheWriteDataSinkFactory= */ null,

                CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,

        /* eventListener= */ null);

    }

    private synchronized Cache getDownloadCache() {

        //设置下载缓存

        Log.i("ExoPlayerLayout",

                "getDownloadCache:" + mDownloadCache);

        if (mDownloadCache == null) {

            File downloadContentDirectory = new File(this.getContext().getFilesDir().toString()

                    + "/ExoPlayer/");

            mDownloadCache = new SimpleCache(downloadContentDirectory, new NoOpCacheEvictor());

        }

        return mDownloadCache;

    }

    private class PlayerEventListener implements Player.EventListener {

        @Override

        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {

            switch (playbackState) {

                case Player.STATE_IDLE:

                case Player.STATE_BUFFERING:

                    startLoading();

                    break;

                case Player.STATE_READY:

                    finishLoading();

                    break;

                case Player.STATE_ENDED:

                    break;

            }

            Log.i("PlayerEventListener",

                    "onPlayerStateChanged:" + playWhenReady + "," + playbackState);

        }

        @Override

        public void onPlayerError(ExoPlaybackException error) {

            String errStr = error.getCause().getMessage();

            Log.d(TAG, mTVProgramUrl);

            Log.d(TAG, error.getLocalizedMessage());

        }

    }

    private void startLoading() {

        try {

            /*增加加載gif动画效果*/

        } catch (Throwable throwable) {

            throwable.printStackTrace();

        }

    }

    private void finishLoading() {

        try {

        } catch (Throwable throwable) {

            throwable.printStackTrace();

        }

    }

}

2. 接入网络电视直播源:

电视的节目源是.m3u8的HLS流,可以在网上找到,如:

"CCTV1-综合",

"http://223.110.245.159/ott.js.chinamobile.com/PLTV/3/224/3221225530/index.m3u8"

3. 监测直播流的下载速度:

网上有很多直播流的下载速率计算方法,但我们只要官方的方法即可:

DefaultBandwidthMeter:ExoPlayer的官方带宽统计类,我们只要调用mDefaultBandwidthMeter.getBitrateEstimate(),即可获取网络的下载速率。

4. 监测播放器的卡顿、视频源错误:

ExoPlayer的Player.EventListener,是官方的播放异常检测类,我们只要重载相关方法即可完成相关事件监测:

private class PlayerEventListener implements Player.EventListener {

        @Override

        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {

            switch (playbackState) {

                case Player.STATE_IDLE:

                case Player.STATE_BUFFERING:

                    startLoading();

                    break;

                case Player.STATE_READY:

                    finishLoading();

                    break;

                case Player.STATE_ENDED:

                    break;

            }

            Log.i("PlayerEventListener",

                    "onPlayerStateChanged:" + playWhenReady + "," + playbackState);

        }

        @Override

        public void onPlayerError(ExoPlaybackException error) {

            String errStr = error.getCause().getMessage();

            Log.d(TAG, mTVProgramUrl);

            Log.d(TAG, error.getLocalizedMessage());

        }

    }

onPlayerStateChanged:可以用来检测播放器的卡顿问题,只有当播放器处于Player.STATE_READY状态,视频流才处于播放中; Player.STATE_IDLE/Player.STATE_BUFFERING状态,表示播放器处于等待状态。

onPlayerError:可以监测播放过程中的源错误,如:找不到源的404错误,源格式错误等。

总结:

1. ExoPlayer是个优秀的官方播放器方案,可以实现绝大部分的播放需求;

2. 基于系统级的播放器,可以大大减小第三方播放器的库文件大小,同时减小APK的包尺寸;

3. 随着Android系统的日益成熟,视频流的播放难点会由播放器本身转移到网络、服务器能力。

上一篇下一篇

猜你喜欢

热点阅读