ExoPlayer架构详解与源码分析(14)——ProgressiveMediaPeriod

系列文章目录

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
ExoPlayer架构详解与源码分析(11)------DataSource
ExoPlayer架构详解与源码分析(12)------Cache
ExoPlayer架构详解与源码分析(13)------TeeDataSource和CacheDataSource
ExoPlayer架构详解与源码分析(14)------ProgressiveMediaPeriod


文章目录


前言

中途间隔了一段时间,之前写了那么多铺垫,终于看到ProgressiveMediaPeriod实现部分了

ProgressiveMediaPeriod

有了之前的那些铺垫,这里直接看源码了

java 复制代码
  @Override
  public void prepare(Callback callback, long positionUs) {
    this.callback = callback;
    loadCondition.open();//保证继续加载的开关打开,Loader不阻塞
    startLoading();
  }
  
  private void startLoading() {
    ExtractingLoadable loadable =//创建loadable 共Loader加载
        new ExtractingLoadable(//extractorOutput监听,也就是Loader加载过程中会回调ProgressiveMediaPeriod的track,endTracks
            uri, dataSource, progressiveMediaExtractor, /* extractorOutput= */ this, loadCondition);
    if (prepared) {//如果已经准备完成
      Assertions.checkState(isPendingReset());
      if (durationUs != C.TIME_UNSET && pendingResetPositionUs > durationUs) {
      //当前定位位置已经超过总时长,直接加载结束
        loadingFinished = true;
        pendingResetPositionUs = C.TIME_UNSET;
        return;
      }
      //通过seekMap查找出当前时间对于的数据位置
      loadable.setLoadPosition(
          checkNotNull(seekMap).getSeekPoints(pendingResetPositionUs).first.position,
          pendingResetPositionUs);
      for (SampleQueue sampleQueue : sampleQueues) {
        //将所有的轨道开始时间同步
        sampleQueue.setStartTimeUs(pendingResetPositionUs);
      }
      pendingResetPositionUs = C.TIME_UNSET;
    }
    //获取开始加载时所有轨道已经提前的数据块总数,后面通过当前的数据块总数和开始的数量对比,可以判断出是否加载了新的数据
    extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
    long elapsedRealtimeMs =
        loader.startLoading(//Loader开始加载,具体过程参照Loader部分的文章,此时加载状态的回调this,也就是加载完成后会调用ProgressiveMediaPeriod  onLoadCompleted,seekMap
            loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(dataType));
    DataSpec dataSpec = loadable.dataSpec;
    //触发监听
    mediaSourceEventDispatcher.loadStarted(
        new LoadEventInfo(loadable.loadTaskId, dataSpec, elapsedRealtimeMs),
        C.DATA_TYPE_MEDIA,
        C.TRACK_TYPE_UNKNOWN,
        /* trackFormat= */ null,
        C.SELECTION_REASON_UNKNOWN,
        /* trackSelectionData= */ null,
        /* mediaStartTimeUs= */ loadable.seekTimeUs,
        durationUs);
  }

  @Override
  //Loader加载时如果解析器需要输出Sample数据会先回调track,获取TrackOutput
  public TrackOutput track(int id, int type) {
    return prepareTrackOutput(new TrackId(id, /* isIcyTrack= */ false));
  }
  //构建TrackOutput
  private TrackOutput prepareTrackOutput(TrackId id) {
    int trackCount = sampleQueues.length;
    for (int i = 0; i < trackCount; i++) {
      //查询当前的sampleQueue是否已创建直接返回
      if (id.equals(sampleQueueTrackIds[i])) {
        return sampleQueues[i];
      }
    }
    SampleQueue trackOutput =
        //创建新的SampleQueue对应一个新的轨道,这里传入了缓存分配器allocator
        SampleQueue.createWithDrm(allocator, drmSessionManager, drmEventDispatcher);
    trackOutput.setUpstreamFormatChangeListener(this);//设置Format改变的监听
    @NullableType
    TrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1);
    sampleQueueTrackIds[trackCount] = id;
    this.sampleQueueTrackIds = Util.castNonNullTypeArray(sampleQueueTrackIds);
    //更新全局的SampleQueue数组
    @NullableType SampleQueue[] sampleQueues = Arrays.copyOf(this.sampleQueues, trackCount + 1);
    sampleQueues[trackCount] = trackOutput;
    this.sampleQueues = Util.castNonNullTypeArray(sampleQueues);
    return trackOutput;
  }

  @Override
  //当解析器已经将所有轨道解析出来时会调用此方法,参照H264Reader部分
  public void endTracks() {
    sampleQueuesBuilt = true;
    //当前方法在解析的子线程中回调,需要将方法方法当前ProgressiveMediaPeriod的线程中执行maybeFinishPrepare
    handler.post(maybeFinishPrepareRunnable);
  }
  
  private void maybeFinishPrepare() {
    if (released || prepared || !sampleQueuesBuilt || seekMap == null) {
      return;
    }
    //确保所有轨道均已解析
    for (SampleQueue sampleQueue : sampleQueues) {
      if (sampleQueue.getUpstreamFormat() == null) {
        return;
      }
    }
    //阻塞住loader的解析,防止继续更新sampleQueues
    loadCondition.close();
    //创建TrackGroup,trackState 供后续的selectTracks使用
    int trackCount = sampleQueues.length;
    TrackGroup[] trackArray = new TrackGroup[trackCount];
    boolean[] trackIsAudioVideoFlags = new boolean[trackCount];
    for (int i = 0; i < trackCount; i++) {
      Format trackFormat = checkNotNull(sampleQueues[i].getUpstreamFormat());
      @Nullable String mimeType = trackFormat.sampleMimeType;
      boolean isAudio = MimeTypes.isAudio(mimeType);
      boolean isAudioVideo = isAudio || MimeTypes.isVideo(mimeType);
      trackIsAudioVideoFlags[i] = isAudioVideo;
      haveAudioVideoTracks |= isAudioVideo;
      ...
      trackFormat = trackFormat.copyWithCryptoType(drmSessionManager.getCryptoType(trackFormat));
      trackArray[i] = new TrackGroup(/* id= */ Integer.toString(i), trackFormat);
    }
    trackState = new TrackState(new TrackGroupArray(trackArray), trackIsAudioVideoFlags);
    prepared = true;//标记准备完成
    checkNotNull(callback).onPrepared(this);//通知上层prepared ,上层接下就会去调用ProgressiveMediaPeriod.selectTracks获取轨道信息
  }
  
  @Override
  //获取轨道
  public long selectTracks(
      @NullableType ExoTrackSelection[] selections,//TrackSelector 部分会说到
      boolean[] mayRetainStreamFlags,
      @NullableType SampleStream[] streams,//Renderer部分会说的
      boolean[] streamResetFlags,
      long positionUs) {
    assertPrepared();//确保已经准备完成
    TrackGroupArray tracks = trackState.tracks;
    boolean[] trackEnabledStates = trackState.trackEnabledStates;
    int oldEnabledTrackCount = enabledTrackCount;
    // 去除原来mayRetainStreamFlags标记的无需保留的轨道
    for (int i = 0; i < selections.length; i++) {
      if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
        int track = ((SampleStreamImpl) streams[i]).track;
        Assertions.checkState(trackEnabledStates[track]);
        enabledTrackCount--;
        trackEnabledStates[track] = false;
        streams[i] = null;
      }
    }
    //如果是第一次selectTracks,而且positionUs 位置又不为0,或者之前一次selectTracks禁用了所有的轨道,这2种情况就需要Seek
    boolean seekRequired = seenFirstTrackSelection ? oldEnabledTrackCount == 0 : positionUs != 0;
    // 开始通过selections选择新的轨道
    for (int i = 0; i < selections.length; i++) {
      if (streams[i] == null && selections[i] != null) {
        ExoTrackSelection selection = selections[i];
        Assertions.checkState(selection.length() == 1);
        Assertions.checkState(selection.getIndexInTrackGroup(0) == 0);
        int track = tracks.indexOf(selection.getTrackGroup());
        Assertions.checkState(!trackEnabledStates[track]);
        enabledTrackCount++;
        trackEnabledStates[track] = true;
        streams[i] = new SampleStreamImpl(track);//向入参中赋值
        streamResetFlags[i] = true;
        if (!seekRequired) {
          SampleQueue sampleQueue = sampleQueues[track];
          seekRequired =
              !sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ true)
                  && sampleQueue.getReadIndex() != 0;
        }
      }
    }
    if (enabledTrackCount == 0) {
      pendingDeferredRetry = false;
      notifyDiscontinuity = false;
      if (loader.isLoading()) {
        // Discard as much as we can synchronously.
        for (SampleQueue sampleQueue : sampleQueues) {
          sampleQueue.discardToEnd();
        }
        loader.cancelLoading();
      } else {
        for (SampleQueue sampleQueue : sampleQueues) {
          sampleQueue.reset();
        }
      }
    } else if (seekRequired) {
      positionUs = seekToUs(positionUs);
      // We'll need to reset renderers consuming from all streams due to the seek.
      for (int i = 0; i < streams.length; i++) {
        if (streams[i] != null) {
          streamResetFlags[i] = true;
        }
      }
    }
    seenFirstTrackSelection = true;
    return positionUs;
  }
  @Override
  //解析器解析出SeekMap时回调
  public void seekMap(SeekMap seekMap) {
    handler.post(() -> setSeekMap(seekMap));
  }
  @Override
  public boolean continueLoading(long playbackPositionUs) {
    if (loadingFinished//加载完成,出错等情况返回false
        || loader.hasFatalError()
        || pendingDeferredRetry
        || (prepared && enabledTrackCount == 0)) {
      return false;
    }
    boolean continuedLoading = loadCondition.open();
    if (!loader.isLoading()) {//当前没有正在加载
      startLoading();//再次startLoading
      continuedLoading = true;
    }
    return continuedLoading;
  }
  @Override
  //Loader加载完成后回调
  public void onLoadCompleted(
      ExtractingLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {
    if (durationUs == C.TIME_UNSET && seekMap != null) {//此时durationUs 未知,seekMap 已经有了
      boolean isSeekable = seekMap.isSeekable();
      long largestQueuedTimestampUs =//查询所有轨道中时间戳最大值
          getLargestQueuedTimestampUs(/* includeDisabledTracks= */ true);
      durationUs =//将最大值+10毫秒作为当前媒体的时长
          largestQueuedTimestampUs == Long.MIN_VALUE
              ? 0
              : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
      //此时durationUs 等信息已知可以通过MediaSource更新一把TimeLine
      listener.onSourceInfoRefreshed(durationUs, isSeekable, isLive);
    }
    StatsDataSource dataSource = loadable.dataSource;
    //StatsDataSource可以缓存Uri和ResponseHeader,获取这些值构建LoadEventInfo 
    LoadEventInfo loadEventInfo =
        new LoadEventInfo(
            loadable.loadTaskId,
            loadable.dataSpec,
            dataSource.getLastOpenedUri(),
            dataSource.getLastResponseHeaders(),
            elapsedRealtimeMs,
            loadDurationMs,
            dataSource.getBytesRead());
    loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId);
    //触发监听
    mediaSourceEventDispatcher.loadCompleted(
        loadEventInfo,
        C.DATA_TYPE_MEDIA,
        C.TRACK_TYPE_UNKNOWN,
        /* trackFormat= */ null,
        C.SELECTION_REASON_UNKNOWN,
        /* trackSelectionData= */ null,
        /* mediaStartTimeUs= */ loadable.seekTimeUs,
        durationUs);
    loadingFinished = true;//此时的loadingFinished 已为true,没设置为false前是无法continueLoading的
    //请求上层继续加载,是否继续加载由上层决定,如果上层同意继续加载会调用ProgressiveMediaPeriod的continueLoading
    checkNotNull(callback).onContinueLoadingRequested(this);
  }
  

这里再总结下ProgressiveMediaPeriod执行过程:

  1. 首先调用prepare 方法启动Loader去获取资源的轨道信息,此处参考ExoPlayer架构详解与源码分析(8)------Loader
  2. Loader中的解析器获取到信息后回回调endTrack通知ProgressiveMediaPeriod prepare完成
  3. ProgressiveMediaPeriod 会进一步通知上层此时MeidaSource已经准备完成,上层再Renderer(四大组件之一)绘制前会通过ExoPlayer的另一大组件TrackSelector (后面会说到),调用ProgressiveMediaPeriod 的selectTracks 选择轨道数据,也就将SampleQueue中的数据提供出去,此处参考ExoPlayer架构详解与源码分析(7)------SampleQueue
  4. 同时Loader从打开到当前加载数据量超过1M(默认值)时就会阻塞当前的Loader,然后询问上层是否继续加载
  5. 上层主要是通过后面要将LoadControl判断,如果上层决定继续加载更多数据,就会调用ProgressiveMediaPeriod continueLoading解开阻塞的锁继续加载。因为我们知道数据是加载到内存中的,如果无限制的加载肯定是不行的,需要有节制的加载和释放数据。而LoadControl就负责这块工作后面会讲到。
  6. 而上面过程当需要释放已经播放的内存时就会调用discardBuffer方法释放Sample中的内存
  7. 当所有的数据加载完成的时候会调用onLoadCompleted将loadingFinished 标记为true

总结

到这里Exoplayer中最复杂的组件MediaSource就全部分析完毕了,之前说了MediaSource在整个运载火箭中的角色就类似于燃料系统,确保火箭顺利升空,在运行过程中持续稳定的为火箭提供燃料。这些燃料提供给谁了呢,或者说是被谁消耗了呢。这就是下面要讲的火箭的核心发动机Renderers,也是Exoplayer四大组件中另一个重要的角色。


版权声明 ©

本文为CSDN作者山雨楼原创文章

转载请注明出处

原创不易,觉得有用的话,收藏转发点赞支持

相关推荐
Coffeeee2 小时前
如何使用Glide和Coil加载WebP动图
android·kotlin·glide
Kstheme2 小时前
把任何 GitHub 仓库变成系统设计课:这个开源项目做到了
架构
Kapaseker3 小时前
5 分钟搞懂 Kotlin DSL
android·kotlin
禅思院3 小时前
路由性能高可用架构实战方案
前端·架构·前端框架
恋猫de小郭3 小时前
AI Agent 开发究竟是啥?如何用 AI 开发 Agent ?深入浅出给你一套概念
android·前端·ai编程
黄林晴4 小时前
Android 17 正式发布!target 37 一大批旧代码直接不能用了
android
Carson带你学Android4 小时前
Android 17 正式发布:AI 终于成了系统能力
android·前端·ai编程
三少爷的鞋4 小时前
当 UseCase 开始长期监听,它可能已经不是 UseCase 了
android
恋猫de小郭17 小时前
Android 限制侧载新进展,谷歌联合国内厂商推验证计划
android·前端·flutter
恋猫de小郭17 小时前
解读 Android 17 全新内存限制,有没有“豁免”后门?
android·前端·flutter