ExoPlayer 浅析

ExoPlayer is an application level media player for Android. It provides an alternative to Android’s MediaPlayer API for playing audio and video both locally and over the Internet. ExoPlayer supports features not currently supported by Android’s MediaPlayer API, including DASH and SmoothStreaming adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize and extend, and can be updated through Play Store application updates.

首先看看ExoPlayer类之间的继承关系,对这个框架有一个大致的印象


基本类图(不完整).png

ExoPlayer被定义为Interface,然后又几个内部类:Factory,Listener,其中,Factory负责初始化ExoPlayer的操作,其关键代码如下:

public static ExoPlayer newInstance(int rendererCount, int minBufferMs, int minRebufferMs) {  return new ExoPlayerImpl(rendererCount, minBufferMs, minRebufferMs);}

Listener则负责向外界回调ExoPlayer状态变化和错误信息。

ExoPlayer有一个子类:ExoPlayerImpl,它继承了ExoPlayer的所有方法,并且负责接收转发外界传递的消息,为什么是转发,不是接收呢?因为真正干活的不是ExoPlayerImpl,而是另外一个隐藏类,ExoPlayerImplInternal,几乎所有的操作都是在ExoPlayerImplInternal中完成的。

Start

我们看一个官方的使用Demo:

// 1. Instantiate the player.
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT);
// 2. Construct renderers.
MediaCodecVideoTrackRenderer videoRenderer = ...
MediaCodecAudioTrackRenderer audioRenderer = ...
// 3. Inject the renderers through prepare.
player.prepare(videoRenderer, audioRenderer);
// 4. Pass the surface to the video renderer.
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
// 5. Start playback.
player.setPlayWhenReady(true);
...
player.release(); 
// Don’t forget to release when done!

我们下面的探索过程都是按照这个Demo一步一步进行的

1.Instantiate the player.

首先,用户调用ExoPlayer.Factory.newInstance(...)方法得到ExoPlayerImpl的实例,这个过程中,我们看看做了什么:

public ExoPlayerImpl(int rendererCount, int minBufferMs, int minRebufferMs) {
    Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
    //首先初始化一些状态
    this.playWhenReady = false;
    this.playbackState = STATE_IDLE;
    //ExoPlayer的Listener是通过andListener(listener:Listener)方法添加的,所以需要一个数组去记录所有的Listener
    this.listeners = new CopyOnWriteArraySet<>();
    //初始化轨道格式数组
    this.trackFormats = new MediaFormat[rendererCount][];
    //选中的轨道索引
    this.selectedTrackIndices = new int[rendererCount];
    //初始化一个Handler,并将收到的消息传递给ExoPlayerImpl的handleEvent()方法处理
    eventHandler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
        ExoPlayerImpl.this.handleEvent(msg);
      }
    };
    //初始化ExoPlayerImplInternal
    internalPlayer = new ExoPlayerImplInternal(eventHandler, playWhenReady, selectedTrackIndices,
    minBufferMs, minRebufferMs);
}

然后我们继续看ExoPlayerImplInternal的构造方法:

public ExoPlayerImplInternal(Handler eventHandler, boolean playWhenReady,int[] selectedTrackIndices, int minBufferMs, int minRebufferMs) {
    //接受从ExoPlayerImpl传递进来的Handler
    this.eventHandler = eventHandler;
    //初始化
    this.playWhenReady = playWhenReady;
    this.minBufferUs = minBufferMs * 1000L;
    this.minRebufferUs = minRebufferMs * 1000L;
    //拷贝
    this.selectedTrackIndices = Arrays.copyOf(selectedTrackIndices, selectedTrackIndices.length);
    this.state = ExoPlayer.STATE_IDLE;
    this.durationUs = TrackRenderer.UNKNOWN_TIME_US;
    this.bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US;
    //初始化StandaloneMediaClock类,它是一个时钟类,原理是通过获取手机启动时间进行差值计算
    standaloneMediaClock = new StandaloneMediaClock();
    //初始化一个自增Integer
    pendingSeekCount = new AtomicInteger();
    enabledRenderers = new ArrayList<TrackRenderer>(selectedTrackIndices.length);
    trackFormats = new MediaFormat[selectedTrackIndices.length][];
    // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
    // not normally change to this priority" is incorrect.
    //初始化和启动一个HandlerThread
    internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler",Process.THREAD_PRIORITY_AUDIO);
    internalPlaybackThread.start();
    //为HandlerThread添加一个Handler
    handler = new Handler(internalPlaybackThread.getLooper(), this);
}

至此,ExoPlayerImpl和ExoPlayerImplInternal两个类的状态都被初始化,启动一个Process.THREAD_PRIORITY_AUDIO的线程,准备接受任务。

2.Construct renderers.

ExoPlayer被初始化后,用户需要调用ExoPlayer.prepare(...)进行准备工作:

public void prepare(TrackRenderer... renderers);
TrackRenderer和它的孩子们

我们看到,prepare形参是TrackRenderer数组,那么这个TrackRenderer是个什么东东呢?

ExoPlayer的媒体组件,都是通过注入的方式实现的,而TrackRenderer就是媒体组件的基类。

public abstract class TrackRenderer implements ExoPlayerComponent {}

从源码看,TrackRenderer是个抽象类,继承自ExoPlayerComponent,只有一个属性:

private int state;

大部分方法都是围绕state实现的,剩下的都是抽象方法,TrackRenderer类用来维护state,而具体的工作需要子类去实现,而做法比较巧妙,如TrackRenderer的prepare()方法:

//prepare方法维护state属性的状态,具体的执行则是调用doPrepare()方法
final int prepare(long positionUs) throws ExoPlaybackException {
    Assertions.checkState(state == STATE_UNPREPARED);
    state = doPrepare(positionUs) ? STATE_PREPARED : STATE_UNPREPARED;
    return state;
}

//抽象方法,由子类实现
protected abstract boolean doPrepare(long positionUs) throws ExoPlaybackException;

再看看ExoPlayerComponent

public interface ExoPlayerComponent {
    void handleMessage(int messageType, Object message) throws     ExoPlaybackException;
}

从名字看,它是一个组件,用于在播放线程接受消息,所有实现它的类都可以在播放线程接受消息,所以TrackRenderer可以接收来自其他线程的消息。

那我们看TrackRenderer有哪些子类


TrackRenderer.png

从类图来看,TrackRenderer有很多子类,其中,SampleSourceTrackRenderer比较重要,我们看一下官方文档对这个类的介绍:


SampleSourceTrackRenderer.png
SampleSource and SampleSourceReader

TrackRenderer的实例,渲染来从SampleSource采集的样本,SampleSource是什么呢,从名字看应该是样本源:


SampleSource.png

媒体样本源,SampleSource一般暴漏一个或多个轨道,轨道的个数和每个轨道的格式可以通过 SampleSource.SampleSourceReader.getTrackCount()和SampleSource.SampleSourceReader.getFormat(int)得到。

再回头看SampleSourceTrackRenderer的构造方法:

public SampleSourceTrackRenderer(SampleSource... sources) {
    this.sources = new SampleSourceReader[sources.length];
    for (int i = 0; i < sources.length; i++) {
      this.sources[i] = sources[i].register();
    }
}

接受一个或者多个SampleSource数组,然后调用了SampleSource的register()方法

/**
   * A consumer of samples should call this method to register themselves and gain access to the
   * source through the returned {@link SampleSourceReader}.
   * <p>
   * {@link SampleSourceReader#release()} should be called on the returned object when access is no
   * longer required.
   *
   * @return A {@link SampleSourceReader} that provides access to the source.
   */
public SampleSourceReader register();

从官方介绍来看,消费者(获取样本的类,这里是指SampleSourceTrackRenderer)通过调用register()方法来获得对媒体样本读取的能力。

register()方法返回SampleSourceReader类:

/**
   *An interface providing read access to a {@link SampleSource}.   
   */
public interface SampleSourceReader

是一个接口,定义了一些访问媒体样本的方法,以下列举一些重要的方法,详细可以去com.google.android.exoplayer.SampleSource.SampleSourceReader类查看:

  • prepare(long positionUS):boolean
  • getTrackCount():int
  • getFormat(int track):MediaFormat
  • enable(int track,long position)
  • disable(int track)
  • readData(int track,long positionUs,MediaFormatHolder formatHolder,SampleHolder sampleHolder):int
  • seekToUs(long positionUs)
  • release()

继续看SampleSourceTrackRenderer的构造方法:

this.sources[i] = sources[i].register();

SampleSourceTrackRenderer中定义一个全局变量,存储所有的SampleSourceReader,方便其他方法访问SampleSource中的资源。

到这里,ExoPlayer的框架结构就比较清晰了,TrackRenderer负责渲染由SampleSource提供的媒体样本。

3. Inject the renderers through prepare.

player.prepare(videoRenderer, audioRenderer);

前两步分别初始化ExoPlayer、TrackRenderer和SampleSource,并将SampleSource注入到TrackRenderer,但是直到现在,TrackRenderer都没有和ExoPlayer产生关系,客官们是不是等的不耐烦了

出处:http://www.jianshu.com/p/4dede867739d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值