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作者山雨楼原创文章

转载请注明出处

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

相关推荐
南山二毛2 小时前
机器人控制器开发(导航算法——导航栈关联坐标系)
人工智能·架构·机器人
只因在人海中多看了你一眼3 小时前
B.50.10.10-微服务与电商应用
微服务·云原生·架构
喂完待续4 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
Lei活在当下5 小时前
【业务场景架构实战】1. 多模块 Hilt 使用原则和环境搭建
性能优化·架构·客户端
音视频牛哥6 小时前
打造一款高稳定、低延迟、跨平台RTSP播放器的技术实践
音视频·rtsp播放器·rtsp player·rtsp播放器录像·rtsp h.265·rtsp hevc·rtsp播放器h.265
水印云6 小时前
2025精选5款AI视频转文字工具,高效转录秒变文字!
人工智能·音视频
菊风 Juphoon7 小时前
13问详解VoLTE视频客服:菊风带你从基础到应用,厘清所有疑惑
音视频
歪歪1008 小时前
Qt Creator 打包应用程序时经常会遇到各种问题
开发语言·c++·qt·架构·编辑器
用户0273851840268 小时前
[Android]RecycleView的item用法
android
前行的小黑炭9 小时前
Android :为APK注入“脂肪”,论Android垃圾代码在安全加固中的作用
android·kotlin