EXOPlayer簡要學習及應用

本想着上效果圖的,可惜圖片太大了不允許上傳參觀移步GitHub

EXOPlayer是Google官方開源的一種播放器官方介紹 ,能夠支持DASH, SmoothStreaming 和 HLS,可惜不能支持Adobe的rtsp、rtmp(有時間我會把B站開源播放器放上來IjkPlayer,那才叫功能強大且易上手這是後話。畢竟EXOPlayer是Google的親兒子,我也是先應用的它而後轉去ijkPlayer的)。至於以上內容如有內容不明白的可以自行查詢不是我這裏的重點,都是些視頻相關概念,我還是着重應用。

我項目應用的EXOPlayer版本爲2.0.0,此時此刻最新的版本爲2.5.1。不是我不想用最新的,而是在我剛開始接觸看2.5.1源碼的時候,項目中分了多個Module且功能都比較獨立,應用的時候必須引用多個模塊,想短時間利用來開發難度很大,需要了解的內部邏輯很多,demo估計還沒看完就懵了。
這裏寫圖片描述
相反2.0.0版本只需要

compile 'com.google.android.exoplayer:exoplayer:r2.0.0'

我所抽取的類就兩個PlayerActivity(播放界面)和EventLogger(日誌打印),應用的時候一個Intent過去就解決了。

以下是乾貨
Intent:

    private void goActivity(String url) {
        Intent intent = new Intent(MainActivity.this, PlayerActivity.class);
        intent.setData(Uri.parse(url)); //傳入視頻地址  在PlayerActivity內會根據後綴的不同區分不同的資源
        intent.setAction(PlayerActivity.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //ShareElement 效果
            startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, imageView, "shareImage").toBundle());
        } else {
            startActivity(intent);
        }
    }

PlayerActivity的控件SimpleExoPlayerView:SimpleExoPlayerView extends FrameLayout可以理解爲播放控件,裏面包含了播放控制控件PlaybackControlView。

  public SimpleExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    ...
    //控件初始化邏輯
    ...

    //這裏纔是關鍵 其實我們看到的視頻界面 無非是TextureView 或SurfaceView 這裏以下有邏輯解釋
    View view = useTextureView ? new TextureView(context) : new SurfaceView(context);
    ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT);
    view.setLayoutParams(params);
    surfaceView = view;
    layout.addView(surfaceView, 0);
  }

控件初始化完成,需要輸入player

  public void setPlayer(SimpleExoPlayer player) {
    ...
    //重置操作
    ...

    this.player = player;

    if (player != null) {//我們看到的就是這兩個控件
      if (surfaceView instanceof TextureView) {
        player.setVideoTextureView((TextureView) surfaceView);
      } else if (surfaceView instanceof SurfaceView) {
        player.setVideoSurfaceView((SurfaceView) surfaceView);
      }
      player.setVideoListener(componentListener);
      player.addListener(componentListener);
      player.setTextOutput(componentListener);
    }
    setUseController(useController);
  }

  public void setUseController(boolean useController) {
    this.useController = useController;
    //controller 是控制控件 播放暫停之類
    if (useController) {
    //這種設計方式比較好 不是給player設置各種控件 而是讓控件去監聽變化  鬆耦合思想
      controller.setPlayer(player);
    } else {
      controller.hide();
      controller.setPlayer(null);
    }
  }

PlayerActivity的基本思路:
onCreate 初始化控件;
onNewIntent 釋放原有player設置新數據
onStart onResume 初始化initializePlayer()
onPause onStop 釋放releasePlayer()
關鍵就是以上幾個方法,源碼中冗長的代碼是一些回調和抽離方法

    private void initializePlayer() {
        Intent intent = getIntent();
        if (player == null) {
            //數字證書相關  如果需求不需要的話可以移除
            boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
            UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
                    ? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
            DrmSessionManager drmSessionManager = null;
            if (drmSchemeUuid != null) {
                String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
                String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES);
                Map<String, String> keyRequestProperties;
                if (keyRequestPropertiesArray == null || keyRequestPropertiesArray.length < 2) {
                    keyRequestProperties = null;
                } else {
                    keyRequestProperties = new HashMap<>();
                    for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
                        keyRequestProperties.put(keyRequestPropertiesArray[i],
                                keyRequestPropertiesArray[i + 1]);
                    }
                }
                try {
                    drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmLicenseUrl,
                            keyRequestProperties);
                } catch (UnsupportedDrmException e) {
                    int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
                            : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
                            ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
                    showToast(errorStringId);
                    return;
                }
            }
            //數字證書相關

            eventLogger = new EventLogger();//日誌打印
            TrackSelection.Factory videoTrackSelectionFactory =
                    new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER);
            trackSelector = new DefaultTrackSelector(mainHandler, videoTrackSelectionFactory);
            trackSelector.addListener(this);
            trackSelector.addListener(eventLogger);
            player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(),
                    drmSessionManager, preferExtensionDecoders);
            player.addListener(this);
            player.addListener(eventLogger);
            player.setAudioDebugListener(eventLogger);
            player.setVideoDebugListener(eventLogger);
            player.setId3Output(eventLogger);
            simpleExoPlayerView.setPlayer(player);
            if (shouldRestorePosition) {
                if (playerPosition == C.TIME_UNSET) {
                    player.seekToDefaultPosition(playerWindow);
                } else {
                    player.seekTo(playerWindow, playerPosition);
                }
            }
            player.setPlayWhenReady(shouldAutoPlay);
            playerNeedsSource = true;
        }
        if (playerNeedsSource) {
            String action = intent.getAction();
            Uri[] uris;
            String[] extensions;
            if (ACTION_VIEW.equals(action)) {// 多個資源的話會無縫連接播放
                uris = new Uri[]{intent.getData()};
                extensions = new String[]{intent.getStringExtra(EXTENSION_EXTRA)};
            } else if (ACTION_VIEW_LIST.equals(action)) {
                String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA);
                uris = new Uri[uriStrings.length];
                for (int i = 0; i < uriStrings.length; i++) {
                    uris[i] = Uri.parse(uriStrings[i]);
                }
                extensions = intent.getStringArrayExtra(EXTENSION_LIST_EXTRA);
                if (extensions == null) {
                    extensions = new String[uriStrings.length];
                }
            } else {
                showToast(getString(R.string.unexpected_intent_action, action));
                return;
            }
            if (Util.maybeRequestReadExternalStoragePermission(this, uris)) {// 外部存儲權限
                // The player will be reinitialized if the permission is granted.
                return;
            }
            MediaSource[] mediaSources = new MediaSource[uris.length];  //將URL組裝成資源類
            for (int i = 0; i < uris.length; i++) {
                mediaSources[i] = buildMediaSource(uris[i], extensions[i]);
            }
            MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
                    : new ConcatenatingMediaSource(mediaSources);
            player.prepare(mediaSource, !shouldRestorePosition);
            playerNeedsSource = false;
        }
    }

    //根據後綴區分資源類型
    private MediaSource buildMediaSource(Uri uri, String overrideExtension) { 
        int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
                : uri.getLastPathSegment());
        switch (type) {
            case Util.TYPE_SS:
                return new SsMediaSource(uri, buildDataSourceFactory(false),
                        new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
            case Util.TYPE_DASH:
                return new DashMediaSource(uri, buildDataSourceFactory(false),
                        new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
            case Util.TYPE_HLS:
                return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
            case Util.TYPE_OTHER:
                return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
                        mainHandler, eventLogger);
            default: {
                throw new IllegalStateException("Unsupported type: " + type);
            }
        }
    }

    private void releasePlayer() {
        if (player != null) {
            shouldAutoPlay = player.getPlayWhenReady();
            shouldRestorePosition = false;
            Timeline timeline = player.getCurrentTimeline();
            if (timeline != null) {
                playerWindow = player.getCurrentWindowIndex();
                Timeline.Window window = timeline.getWindow(playerWindow, new Timeline.Window());
                if (!window.isDynamic) {
                    shouldRestorePosition = true;
                    playerPosition = window.isSeekable ? player.getCurrentPosition() : C.TIME_UNSET; //保存播放位置
                }
            }
            player.release();
            player = null;
            trackSelector = null;
            eventLogger = null;
        }
    }

簡要介紹只能做到這種地步了,如果要自己應用或者學習的話多動手多改源碼纔是正道,不要妄想看一兩篇文章就想搞懂。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章