ExoPlayer架构详解与源码分析(5)——MediaSource

系列文章目录

ExoPlayer架构详解与源码分析(1)------前言

ExoPlayer架构详解与源码分析(2)------Player

ExoPlayer架构详解与源码分析(3)------Timeline

ExoPlayer架构详解与源码分析(4)------整体架构

ExoPlayer架构详解与源码分析(5)------MediaSource


前言

上篇说完整体架构,这里开始分析其中的各个组件,先从MediaSource看起,继续拿运载火箭做对比,MediaSource在整个运载火箭中的角色就类似于燃料系统,确保火箭顺利升空,燃料系统是其中重要的一环,需要能在运行过程从持续稳定的提供燃料。ExoPlayer也一样,为了保证能够持续的渲染出媒体内容,就得保证MediaSource持续稳定提供需要的数据。

MediaSource

继续扩充下我们的版图 MediaSource定了媒体信息以及提供媒体数据给播放器,主要有2个职责:

  • 为播放器提供定义其媒体时序结构的Timeline,并在媒体时序结构发生变化时提供新的Timeline。初始化是提供一个PlaceholdTimeline,当prepareSource 完成时一般就能获取到真实的Timeline,然后调用传递给prepareSource 的MediaSourceCallers 上的onSourceInfoRefreshed 来更新这些新的Timeline。
  • 为其Timeline中的Period提供 MediaPeriod 实例。 MediaPeriods是通过调用createPeriod获得的,并为播放器提供加载和读取媒体的方式。

应用代码不应该直接调用MediaSource 里的方法,而应该让ExoPlayer在合适的时间调用。 MediaSource实例可以重复使用,但只能同时用于一个 ExoPlayer 实例。 不同MediaSource 方法只能在应用程序线程或内部播放线程其中一个上调用。每个方法文档上都明确了可以调用的线程。

看下几个重要的方法定义

  • getInitialTimeline主线程调用,当真实Timeline未知时立即返回初始PlaceholderTimeline,或者为返回null 让播放器创建初始Timeline。
  • getMediaItem主线程调用,返回当前的MediaItem,可以看到MediaSource里也可能保存了MediaItem。
  • prepareSource内部播放线程调用,注册 MediaSourceCaller,主要用来为播放器提供一个回调,获取最新的Timeline。另外,在播放某些播放资源需要先获取真实的媒体源时,这里会提前解析媒体资源(如播放HLS时这个时候会去获取解析M3U8文件),prepareSource完成后会立即刷新Timeline。
java 复制代码
  void prepareSource(
      MediaSourceCaller caller,
      @Nullable TransferListener mediaTransferListener,
      PlayerId playerId);
      
  interface MediaSourceCaller {
    void onSourceInfoRefreshed(MediaSource source, Timeline timeline);
  }
  • createPeriod在内部播放线程调用,返回一个由periodId区分的新的MediaPerods对象,只能在真实的源已经准备好后再调用,也就是上面的prepareSource确保源已经准备完成,参数id就是MediaPerods唯一区分,startPositionUs想要播放的开始位置,allocator是一个缓存分配器这个后面讲MediaPerods会提到,MediaPerods创建完成后也会perpare,完成后一般就可以获取媒体的基本数据,如时长、轨道等,这个时候会反过来通知MediaSource刷新Timeline。
java 复制代码
  MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs);

MeidiaSource大致工作流程就是创建时初始化出一个Timeline,然后prepareSource准备数据源,之后createPeriod创建Period,然后讲工作交给Period,整个过程都在刷新Timeline。

MediaSource的实现

BaseMediaSource

MediaSource虚函数实现,主要用于多个MediaSourceEventListener的处理分发,触发多个MediaSourceCaller的onSourceInfoRefreshed,还保存上一次的Timeline。

CompositeMediaSource

由多个子MediaSource组成的复合MediaSource,将所有方法调用转发给各个子的MediaSource

WrappingMediaSource

继承自CompositeMediaSource,实现只包含了一个子MediaSource的MediaSource 。

MaskingMediaSource

一个MediaSource ,主要作用是当实际媒体结果未知时,用一个PlaceholderTimeline来表示Timeline ,当获取实际的媒体结构时采用实际的Timeline替换PlaceholderTimeline。

java 复制代码
public MaskingMediaSource(MediaSource mediaSource, boolean useLazyPreparation) {
    super(mediaSource);
    this.useLazyPreparation = useLazyPreparation && mediaSource.isSingleWindow();
    window = new Timeline.Window();
    period = new Timeline.Period();
    @Nullable Timeline initialTimeline = mediaSource.getInitialTimeline();
    if (initialTimeline != null) {
      timeline =
          MaskingTimeline.createWithRealTimeline(
              initialTimeline, /* firstWindowUid= */ null, /* firstPeriodUid= */ null);
      hasRealTimeline = true;
    } else {
      timeline = MaskingTimeline.createWithPlaceholderTimeline(mediaSource.getMediaItem());
    }
  }

ProgressiveMediaSource

继承自BaseMediaSource,主要用于渐进式媒体文件的播放,如本地或远程的单个视频文件

java 复制代码
  @Override
  //prepare
  protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
    transferListener = mediaTransferListener;
    drmSessionManager.setPlayer(
        /* playbackLooper= */ checkNotNull(Looper.myLooper()), getPlayerId());
    drmSessionManager.prepare();
    notifySourceInfoRefreshed();
  }
  
  @Override
  //ProgressiveMediaPeriod在获取到Timeline相关信息后会回调更新Timeline
  public void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) {
    // 优先实现之前的durationUs 
    durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs;
    if (!timelineIsPlaceholder
        && timelineDurationUs == durationUs
        && timelineIsSeekable == isSeekable
        && timelineIsLive == isLive) {
      // 没有发生变更
      return;
    }
    timelineDurationUs = durationUs;
    timelineIsSeekable = isSeekable;
    timelineIsLive = isLive;
    timelineIsPlaceholder = false;
    notifySourceInfoRefreshed();
  }
  
  //刷新TimeLine
  private void notifySourceInfoRefreshed() {
    Timeline timeline =
        new SinglePeriodTimeline(
            timelineDurationUs,
            timelineIsSeekable,
            /* isDynamic= */ false,
            /* useLiveConfiguration= */ timelineIsLive,
            /* manifest= */ null,
            mediaItem);
    if (timelineIsPlaceholder) {
      timeline =
          new ForwardingTimeline(timeline) {
            @Override
            public Window getWindow(
                int windowIndex, Window window, long defaultPositionProjectionUs) {
              super.getWindow(windowIndex, window, defaultPositionProjectionUs);
              window.isPlaceholder = true;
              return window;
            }

            @Override
            public Period getPeriod(int periodIndex, Period period, boolean setIds) {
              super.getPeriod(periodIndex, period, setIds);
              period.isPlaceholder = true;
              return period;
            }
          };
    }
    //触发监听
    refreshSourceInfo(timeline);
  }

总结

没了就这么多,燃料系统这么简陋的吗?当然不会,因为它把除了Timeline的管理维护之外的几乎所有的工作都交给别人来完成了,它就是下面要重点讲的MediaPeriod,MediaSource只管创建出就好了,ExoPlayer也是主要通过MediaSource关联的MediaPeriod控制媒体的加载释放等。


版权声明 © 本文为作者山雨楼原创文章 转载请注明出处 原创不易,觉得有用的话,收藏转发点赞支持

相关推荐
-优势在我1 小时前
Android TabLayout 实现随意控制item之间的间距
android·java·ui
hedalei1 小时前
android13修改系统Launcher不跟随重力感应旋转
android·launcher
Indoraptor2 小时前
Android Fence 同步框架
android
峥嵘life2 小时前
DeepSeek本地搭建 和 Android
android
叶羽西2 小时前
Android14 Camera框架中Jpeg流buffer大小的计算
android·安卓
jiasting2 小时前
Android 中 如何监控 某个磁盘有哪些进程或线程在持续的读写
android
AnalogElectronic5 小时前
问题记录,在使用android studio 构建项目时遇到的问题
android·ide·android studio
我爱松子鱼6 小时前
mysql之InnoDB Buffer Pool 深度解析与性能优化
android·mysql·性能优化
江上清风山间明月9 小时前
Flutter开发的应用页面非常多时如何高效管理路由
android·flutter·路由·页面管理·routes·ongenerateroute
子非衣12 小时前
MySQL修改JSON格式数据示例
android·mysql·json