美文网首页
基于ExoPlayer搭建Android网络电视应用

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

作者: Lurky | 来源:发表于2019-02-01 11:11 被阅读0次

           在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系统的日益成熟,视频流的播放难点会由播放器本身转移到网络、服务器能力。

    相关文章

      网友评论

          本文标题:基于ExoPlayer搭建Android网络电视应用

          本文链接:https://www.haomeiwen.com/subject/ftnrsqtx.html