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


    系列文章目录

    ExoPlayer架构详解与源码分析(1)——前言
    ExoPlayer架构详解与源码分析(2)——Player
    ExoPlayer架构详解与源码分析(3)——Timeline
    ExoPlayer架构详解与源码分析(4)——整体架构
    ExoPlayer架构详解与源码分析(5)——MediaSource
    ExoPlayer架构详解与源码分析(6)——MediaPeriod
    ExoPlayer架构详解与源码分析(7)——SampleQueue
    ExoPlayer架构详解与源码分析(8)——Loader
    ExoPlayer架构详解与源码分析(9)——TsExtractor
    ExoPlayer架构详解与源码分析(10)——H264Reader



    前言

    上篇说完整体架构,这里开始分析其中的各个组件,先从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。
      void prepareSource(
          MediaSourceCaller caller,
          @Nullable TransferListener mediaTransferListener,
          PlayerId playerId);
          
      interface MediaSourceCaller {
        void onSourceInfoRefreshed(MediaSource source, Timeline timeline);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • createPeriod在内部播放线程调用,返回一个由periodId区分的新的MediaPerods对象,只能在真实的源已经准备好后再调用,也就是上面的prepareSource确保源已经准备完成,参数id就是MediaPerods唯一区分,startPositionUs想要播放的开始位置,allocator是一个缓存分配器这个后面讲MediaPerods会提到,MediaPerods创建完成后也会perpare,完成后一般就可以获取媒体的基本数据,如时长、轨道等,这个时候会反过来通知MediaSource刷新Timeline。
      MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs);
    
    • 1

    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。

    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());
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ProgressiveMediaSource

    继承自BaseMediaSource,主要用于渐进式媒体文件的播放,如本地或远程的单个视频文件,ProgressiveMediaSource也是最基本的MediaSource,比较典型是后面重点讲解的对象,剩余其他几种类型的可以先做了解,后面有时间再扩充。

      @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);
      }
    
    • 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

    总结

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


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

  • 相关阅读:
    CAD中图纸比较功能怎么用
    JavaScript随手笔记---保留小数位
    C Primer Plus(6) 中文版 第5章 运算符、表达式和语句 5.4 表达式和语句
    MySQL: 锁
    Redis内存策略
    矢量图形编辑软件illustrator 2023 mac特点介绍
    「微服务 | Nginx」upstream 模块负载均衡算法详解
    扒去Spring事件监听机制的外衣,竟然是观察者模式
    编辑距离算法
    使用docker容器搭建MySQL主从复制
  • 原文地址:https://blog.csdn.net/blovecat/article/details/132070261