当前位置:   article > 正文

ExoPlayer之PlayerView源码分析

playerview

项目从MediaPlayer迁移到ExoPlayer也有一段时间了.之前看过VideoView的源码,就是对MediaPlayer的一层封装.网上对该类的分析太多了,所以不多赘述了.

当前ExoPlayer已经更新到2.8.1,在此之前,简单播放视频使用SimpleExoPlayerView, 类似于内置的VideoView.不过当前该类已经废弃,改为PlayerView.所以我们基于ExoPlayer V2.8.1对该类进行分析.

public class PlayerView extends FrameLayout {

  private static final int SURFACE_TYPE_NONE = 0;
  private static final int SURFACE_TYPE_SURFACE_VIEW = 1;
  private static final int SURFACE_TYPE_TEXTURE_VIEW = 2;

  **private final AspectRatioFrameLayout contentFrame;**
  private final View shutterView;
  **private final View surfaceView;**
  private final ImageView artworkView;
  private final SubtitleView subtitleView;
  private final @Nullable View bufferingView;
  private final @Nullable TextView errorMessageView;
  **private final PlayerControlView controller;**
  **private final ComponentListener componentListener;**
  private final FrameLayout overlayFrameLayout;

  **private Player player;**
  private boolean useController;
  private boolean useArtwork;
  private Bitmap defaultArtwork;
  private boolean showBuffering;
  private boolean keepContentOnPlayerReset;
  private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
  private @Nullable CharSequence customErrorMessage;
  private int controllerShowTimeoutMs;
  private boolean controllerAutoShow;
  private boolean controllerHideDuringAds;
  private boolean controllerHideOnTouch;
  private int textureViewRotation;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

通过成员变量可以知道,该类主要的有5个成员变量.分别是AspectRatioFrameLayout, surfaceView, PlayerControlView,
ComponentListener, Player;

1.首先是AspectRatioFrameLayout.
可以查看到源码

/**
   * Sets the aspect ratio that this view should satisfy.
   * 设置视频宽高比例,然后重新布局
   * @param widthHeightRatio The width to height ratio.
   */
  public void setAspectRatio(float widthHeightRatio) {
    if (this.videoAspectRatio != widthHeightRatio) {
      this.videoAspectRatio = widthHeightRatio;
      requestLayout();
    }
  }


@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (videoAspectRatio <= 0) {
      // Aspect ratio not set.
      return;
    }

    int width = getMeasuredWidth();
    int height = getMeasuredHeight();
    float viewAspectRatio = (float) width / height;
    float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
    //宽高比和目标的宽高比小于0.01f,不用刷新,主要是有一些视频,可能默认是1280 x 720 这样,但是某些机型获取到的回是1280 x 719这样的奇怪宽高
    if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) {
      // We're within the allowed tolerance.
      aspectRatioUpdateDispatcher.scheduleUpdate(videoAspectRatio, viewAspectRatio, false);
      return;
    }

    switch (resizeMode) {
      case RESIZE_MODE_FIXED_WIDTH:
        height = (int) (width / videoAspectRatio);
        break;
      case RESIZE_MODE_FIXED_HEIGHT:
        width = (int) (height * videoAspectRatio);
        break;
      case RESIZE_MODE_ZOOM:
        if (aspectDeformation > 0) {
          width = (int) (height * videoAspectRatio);
        } else {
          height = (int) (width / videoAspectRatio);
        }
        break;
      case RESIZE_MODE_FIT:
        if (aspectDeformation > 0) {
          height = (int) (width / videoAspectRatio);
        } else {
          width = (int) (height * videoAspectRatio);
        }
        break;
      case RESIZE_MODE_FILL:
      default:
        // Ignore target aspect ratio
        break;
    }
    aspectRatioUpdateDispatcher.scheduleUpdate(videoAspectRatio, viewAspectRatio, true);
    super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

2.surfaceView
我们可以看到

// Create a surface view and insert it into the content frame, if there is one.
    if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) {
      ViewGroup.LayoutParams params =
          new ViewGroup.LayoutParams(
              ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
      surfaceView =
          surfaceType == SURFACE_TYPE_TEXTURE_VIEW
              ? new TextureView(context)
              : new SurfaceView(context);
      surfaceView.setLayoutParams(params);
      contentFrame.addView(surfaceView, 0);
    } else {
      surfaceView = null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

surfaceview在该类里面有两种类型,一种是Surfaceview(GLSurfaceView);一种是TextureView.默认情况下,会使用TextureView.主要是因为TextureView可以做一些矩阵变化之类的操作,而SurfaceView不具备类似的操作.这样的好处在于播放视频衔接的地方,会有

@Override
    public void onVideoSizeChanged(
        int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
      if (contentFrame == null) {
        return;
      }
      float videoAspectRatio =
          (height == 0 || width == 0) ? 1 : (width * pixelWidthHeightRatio) / height;

      if (surfaceView instanceof TextureView) {
        // Try to apply rotation transformation when our surface is a TextureView.
        if (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270) {
          // We will apply a rotation 90/270 degree to the output texture of the TextureView.
          // In this case, the output video's width and height will be swapped.
          videoAspectRatio = 1 / videoAspectRatio;
        }
        if (textureViewRotation != 0) {
          surfaceView.removeOnLayoutChangeListener(this);
        }
        textureViewRotation = unappliedRotationDegrees;
        if (textureViewRotation != 0) {
          // The texture view's dimensions might be changed after layout step.
          // So add an OnLayoutChangeListener to apply rotation after layout step.
          surfaceView.addOnLayoutChangeListener(this);
        }
        applyTextureViewRotation((TextureView) surfaceView, textureViewRotation);
      }

      contentFrame.setAspectRatio(videoAspectRatio);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

我们知道,ExoPlayer支持视频无缝衔接,这也是当初项目之所以选择ExoPlayer的一个原因.在安卓5.0以上的机器,ExoPlayer在解码的时候,会设置视频的角度,那么解码出来的数据已经旋转好角度,我们无需自己再进行翻转.但是对于5.0一下的机器.我们可以看到,对于TextureView.在视频衔接的时候,会默认对视频进行翻转,所以我们无需适配视频的角度问题.但是对于surfaceview,我们就需要自己处理了.更加具体的解释可以看一下issus:

https://github.com/google/ExoPlayer/issues/91

3.PlayerControlView
这只是一个简单的布局,用于控制视频的播放,暂停,快进之类. 我们可以通过在xml的局部文件配置exo_controller.修改控制器的局部文件,但是对应的id不能修改

<declare-styleable name="PlayerControlView">
    <attr name="show_timeout"/>   //控制器展示时间
    <attr name="rewind_increment"/>   //回退时间
    <attr name="fastforward_increment"/>   //快进时间
    <attr name="repeat_toggle_modes"/>   //播放模式(循环播放/播放单个视频/播放到结尾结束)
    <attr name="show_shuffle_button"/>    //是否需要自摸
    <attr name="controller_layout_id"/>    
  </declare-styleable>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4.ComponentListener
我们主要关注以下

private final class ComponentListener extends Player.DefaultEventListener
      implements TimeBar.OnScrubListener, OnClickListener {

    //开始滑动进度条
    @Override
    public void onScrubStart(TimeBar timeBar, long position) {
      removeCallbacks(hideAction);
      scrubbing = true;
    }

    //滑动进度条
    @Override
    public void onScrubMove(TimeBar timeBar, long position) {
      if (positionView != null) {
        positionView.setText(Util.getStringForTime(formatBuilder, formatter, position));
      }
    }

    //停止滑动进度条
    @Override
    public void onScrubStop(TimeBar timeBar, long position, boolean canceled) {
      scrubbing = false;
      if (!canceled && player != null) {
        seekToTimeBarPosition(position);
      }
      hideAfterTimeout();
    }

    /**视频状态改变的回调,分别有一下几种状态
        //还未初始化的状态
        case Player.STATE_IDLE:
        //初始化结束,在缓冲,还不能直接播放
        case Player.STATE_BUFFERING:
        //准备好了,可以直接播放
        case Player.STATE_READY:
        //播放到视频流结尾
        case Player.STATE_ENDED:
    **/
    @Override
    public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
      updatePlayPauseButton();
      updateProgress();
    }

    /**
    播放模式(循环播放/播放单个视频/播放到结尾结束)改变
    **/
    @Override
    public void onRepeatModeChanged(int repeatMode) {
      updateRepeatModeButton();
      updateNavigation();
    }

    //字幕是否可以改变
    @Override
    public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
      updateShuffleButton();
      updateNavigation();
    }

    /**
    视频的播放位置发生改变的回调
    //Seek adjustment due to being unable to seek to the requested position or because the seek was permitted to be inexact.
            case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
                break;
            //Seek within the current period or to another period.
            case DISCONTINUITY_REASON_SEEK:
                break;
            //Discontinuity to or from an ad within one period in the timeline.
            case DISCONTINUITY_REASON_AD_INSERTION:
                break;
            //Discontinuity introduced internally by the source.
            case DISCONTINUITY_REASON_INTERNAL:
                break;
            //Automatic playback transition from one period in the timeline to the next.
            case DISCONTINUITY_REASON_PERIOD_TRANSITION:
            DISCONTINUITY_REASON_PERIOD_TRANSITION是在视频连播的状态下,从A视频播放到B视频的时候的回调,这里我们可以处理一些自己的业务.其他状态可以根据自身需要去处理
    **/
    @Override
    public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
      updateNavigation();
      updateProgress();
    }

    //时间轴改变
    @Override
    public void onTimelineChanged(
        Timeline timeline, Object manifest, @Player.TimelineChangeReason int reason) {
      updateNavigation();
      updateTimeBarMode();
      updateProgress();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

5.Player
Player自然是指我们外部传进去的ExoPlayer了

分析完,我们会发现,其实PlayerView的逻辑挺简单,即是包装了一层自适应layout的播放器,外部通过设置视频的展示模式(Fit, Full之类)控制视频的展示大小.对应的通过设置PlayerControlView即达到修改ui的效果.但是局限性也比较大.如果需要自身拓展视频的播放的话(例如,添加滤镜之类),不能直接用该类.但是其中的设计思想我们可以借鉴.

最后,欢迎大家给我的项目点一波star,或者关注我一下

https://github.com/RuijiePan

https://github.com/RuijiePan/FileManager

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/295754
推荐阅读
相关标签
  

闽ICP备14008679号