系列文章目录


前言

书接上回,持续贴下上文的蜘蛛网

ExoPlayer架构详解与源码剖析(13)——TeeDataSource和CacheDataSource

铺垫了那么多的根底,本篇终于能够来剖析下CacheDataSource,上篇要点讲完了图的Cache的下半部分,而将Cache和CacheDataSource关联起来的是一个叫TeeDataSource特殊DataSource。

TeeDataSource

这是一个3通阀门相同的DataSource,有一个输入端2个输出端,也能够理解为在正常的输入和输出端中间又接了一个输出端,类似于下图

ExoPlayer架构详解与源码剖析(13)——TeeDataSource和CacheDataSource

upstream也是一个DataSource,一般是上游的原始数据源,如OkHttpDataSource获取的网络源,dataSink参照上篇ExoPlayer架构详解与源码剖析(12)——Cache,这些参数在TeeDataSource初始化时设置的。 看下代码完结

  @Override
  //翻开数据源
  public long open(DataSpec dataSpec) throws IOException {
    bytesRemaining = upstream.open(dataSpec);//翻开上游数据源
    if (bytesRemaining == 0) {
      return 0;
    }
    if (dataSpec.length == C.LENGTH_UNSET && bytesRemaining != C.LENGTH_UNSET) {
      // 依据实践长度裁剪dataSpec,确保传给dataSink长度正确
      dataSpec = dataSpec.subrange(0, bytesRemaining);
    }
    dataSinkNeedsClosing = true;
    dataSink.open(dataSpec);//dataSink翻开流,首要便是经过cache startFile翻开缓存文件获取到流
    return bytesRemaining;
  }
  @Override
  //读取数据
  @Override
  public int read(byte[] buffer, int offset, int length) throws IOException {
    if (bytesRemaining == 0) {
      return C.RESULT_END_OF_INPUT;
    }
    //从上游读取数据
    int bytesRead = upstream.read(buffer, offset, length);
    if (bytesRead > 0) {
      // 在数据回来前,先经过dataSink将数据写入到上面翻开的缓存文件流里
      dataSink.write(buffer, offset, bytesRead);
      if (bytesRemaining != C.LENGTH_UNSET) {
        bytesRemaining -= bytesRead;
      }
    }
    //输出数据
    return bytesRead;
  }

能够看到TeeDataSource完结很简单,TeeDataSource便是为了数据边读边缓存而完结的,只要是读取过的数据都会缓存到文件里,至于在什么时分运用TeeDataSource,这就要讲到本篇的要点CacheDataSource了。

CacheDataSource

读取和写入Cache的DataSource 。假如或许的话,恳求会从缓存中获取。当数据未缓存时,会从上游DataSource恳求数据并将其写入缓存。 CacheDataSource,首要包括3个DataSource

  • upstreamDataSource 上游原始数据的源,假如此刻播映的是一个网络的URL文件,这个upstreamDataSource或许便是OkHttpDataSource,无需缓存或者当时现已在缓存时,就会运用此源。
  • cacheWriteDataSource 缓存写入源,默许的是TeeDataSource,经过它在数据读取时缓存到文件,当时资源未缓存且需求缓存时,就会运用此源。
  • cacheReadDataSource 缓存数据读取源,当时播映的数据现已被缓存时,就会经过这个源将缓存的数据读取出来,正常数据都是缓存在本地文件中的所以这儿一般都是FileDataSource,当时资源现已查询到缓存时,就会运用此源。

上面几个源的联系能够在源码的openNextSource办法中看出来。

接下来看源码完结:

private CacheDataSource(
      Cache cache,
      @Nullable DataSource upstreamDataSource,
      DataSource cacheReadDataSource,
      @Nullable DataSink cacheWriteDataSink,
      @Nullable CacheKeyFactory cacheKeyFactory,
      @Flags int flags,
      @Nullable PriorityTaskManager upstreamPriorityTaskManager,
      int upstreamPriority,
      @Nullable EventListener eventListener) {
    this.cache = cache;
    //用于读取缓存的DataSource,只要是缓存在文件里这儿一般都是FileDataSource
    this.cacheReadDataSource = cacheReadDataSource;
    //cacheKeyFactory 用于生成CacheContent的资源key,默许便是取DataSpec.key,没有则获取播映文件的URL
    this.cacheKeyFactory = cacheKeyFactory != null ? cacheKeyFactory : CacheKeyFactory.DEFAULT;
    //是否在缓存时堵塞
    this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0;
    this.ignoreCacheOnError = (flags & FLAG_IGNORE_CACHE_ON_ERROR) != 0;
    this.ignoreCacheForUnsetLengthRequests =
        (flags & FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS) != 0;
    if (upstreamDataSource != null) {
      if (upstreamPriorityTaskManager != null) {
        upstreamDataSource =
            new PriorityDataSource(
                upstreamDataSource, upstreamPriorityTaskManager, upstreamPriority);
      }
      this.upstreamDataSource = upstreamDataSource;
      this.cacheWriteDataSource =
          cacheWriteDataSink != null//创立TeeDataSource作为cacheWriteDataSource 
              ? new TeeDataSource(upstreamDataSource, cacheWriteDataSink)
              : null;
    } else {
      this.upstreamDataSource = PlaceholderDataSource.INSTANCE;
      this.cacheWriteDataSource = null;
    }
    this.eventListener = eventListener;
  }
  @Override
  public long open(DataSpec dataSpec) throws IOException {
    try {
      String key = cacheKeyFactory.buildCacheKey(dataSpec);//依据dataSpec获取资源的key,一般为URL
      DataSpec requestDataSpec = dataSpec.buildUpon().setKey(key).build();//DataSpec设置key
      this.requestDataSpec = requestDataSpec;
      //假如之前有缓存,这儿会经过当时的key查询索引中的资源,获取缓存里的终究跳转的实在URL
      actualUri = getRedirectedUriOrDefault(cache, key, /* defaultUri= */ requestDataSpec.uri);
      readPosition = dataSpec.position;//更新当时的读取方位
      int reason = shouldIgnoreCacheForRequest(dataSpec);
      currentRequestIgnoresCache = reason != CACHE_NOT_IGNORED;
      if (currentRequestIgnoresCache) {//是否疏忽缓存
        notifyCacheIgnored(reason);
      }
      if (currentRequestIgnoresCache) {
        bytesRemaining = C.LENGTH_UNSET;
      } else {
        //获取保存在索引文件中的资源Metadata中的总长度
        bytesRemaining = ContentMetadata.getContentLength(cache.getContentMetadata(key));
        if (bytesRemaining != C.LENGTH_UNSET) {
          bytesRemaining -= dataSpec.position;//总长度-当时方位=剩下数据长度
          if (bytesRemaining < 0) {//假如为负数阐明当时方位现已超出资源的最大长度
            throw new DataSourceException(
                PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE);
          }
        }
      }
      if (dataSpec.length != C.LENGTH_UNSET) {
        bytesRemaining =//依据dataSpec.length更新bytesRemaining 
            bytesRemaining == C.LENGTH_UNSET
                ? dataSpec.length
                : min(bytesRemaining, dataSpec.length);
      }
      if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) {
        openNextSource(requestDataSpec, false);//要点看这个办法
      }
      return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : bytesRemaining;
    } catch (Throwable e) {
      handleBeforeThrow(e);
      throw e;
    }
  }
  private void openNextSource(DataSpec requestDataSpec, boolean checkCache) throws IOException {
    @Nullable CacheSpan nextSpan;
    String key = castNonNull(requestDataSpec.key);
    if (currentRequestIgnoresCache) {//假如疏忽缓存,后面直接翻开上游DataSource
      nextSpan = null;
    } else if (blockOnCache) {//一般下载器才会设置为true
    //需求堵塞获取调用startReadWrite,获取占位的HoleSpan,这儿确定了当时资源的剩下所有数据段
    //后续DataSkin会将HoleSpan确定的区域,切分出多个文件关于多个CacheSpan添加到索引中
      try {
        nextSpan = cache.startReadWrite(key, readPosition, bytesRemaining);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new InterruptedIOException();
      }
    } else {//无堵塞获取,正常播映首要经过startReadWriteNonBlocking获取
      nextSpan = cache.startReadWriteNonBlocking(key, readPosition, bytesRemaining);
    }
    DataSpec nextDataSpec;//下一个DataSpec 
    DataSource nextDataSource;//下一个DataSource 
    if (nextSpan == null) {
      //nextSpan == null2种或许,第一种是疏忽缓存,第二种由于startReadWriteNonBlocking获取到了一个空的nextSpan,阐明当时方位的缓存被确定了,或许正在写入缓存
      //这个时分播映器是不能等待数据缓存完结的(下载器能够),会直接翻开上游的数据源获取数据,确保正常播映
      nextDataSource = upstreamDataSource;//OkHttpDataSource
      nextDataSpec =
          requestDataSpec.buildUpon().setPosition(readPosition).setLength(bytesRemaining).build();
    } else if (nextSpan.isCached) {
      // 当时position数据现已被缓存了
      //直接获取缓存的文件运用FileDataSource翻开源获取数据
      Uri fileUri = Uri.fromFile(castNonNull(nextSpan.file));
      long filePositionOffset = nextSpan.position;
      long positionInFile = readPosition - filePositionOffset;
      long length = nextSpan.length - positionInFile;
      if (bytesRemaining != C.LENGTH_UNSET) {
        length = min(length, bytesRemaining);
      }
      nextDataSpec =
          requestDataSpec
              .buildUpon()
              .setUri(fileUri)
              .setUriPositionOffset(filePositionOffset)
              .setPosition(positionInFile)
              .setLength(length)
              .build();
      nextDataSource = cacheReadDataSource;//FileDataSource
    } else {
      // 数据没有缓存,且没有堵塞,阐明当时是第一次获取这块数据,且没有其他现场正在获取
      //这个时分翻开TeeDataSource,运用其中的DataSink在数据读取时缓存到文件中
      long length;
      if (nextSpan.isOpenEnded()) {
        length = bytesRemaining;
      } else {
        length = nextSpan.length;
        if (bytesRemaining != C.LENGTH_UNSET) {
          length = min(length, bytesRemaining);
        }
      }
      nextDataSpec =
          requestDataSpec.buildUpon().setPosition(readPosition).setLength(length).build();
      if (cacheWriteDataSource != null) {
        nextDataSource = cacheWriteDataSource;//TeeDataSource
      } else {
        nextDataSource = upstreamDataSource;
        cache.releaseHoleSpan(nextSpan);
        nextSpan = null;
      }
    }
    //假如上面的某些状况导致了一开端一向疏忽缓存直接从上游获取数据,
    //这个时分在读取超过MIN_READ_BEFORE_CHECKING_CACHE(100kb)的数据时,会再次调用openNextSource
    //检测此刻的缓存是否可用,是否需求切换数据源
    checkCachePosition =
        !currentRequestIgnoresCache && nextDataSource == upstreamDataSource
            ? readPosition + MIN_READ_BEFORE_CHECKING_CACHE
            : Long.MAX_VALUE;
    if (checkCache) {
      //确保前次的DataSource为upstreamDataSource
      Assertions.checkState(isBypassingCache());
      if (nextDataSource == upstreamDataSource) {
        // 假如本次获取到的还是upstreamDataSource,持续运用本来的upstreamDataSource,不做任何操作直接回来
        return;
      }
      //到这儿阐明本次获取的数据源现已不是upstreamDataSource,需求封闭当时的数据源,切换到本次新获取的数据源
      // 封闭上一个源,预备翻开下一个源
      try {
        closeCurrentSource();
      } catch (Throwable e) {
        if (castNonNull(nextSpan).isHoleSpan()) {
          // 异常后一定要释放确定的数据段
          cache.releaseHoleSpan(nextSpan);
        }
        throw e;
      }
    }
    if (nextSpan != null && nextSpan.isHoleSpan()) {
      //当时为待写入数据,这儿记录,在写入完结需求cache.releaseHoleSpan
      currentHoleSpan = nextSpan;
    }
    currentDataSource = nextDataSource;//更新当时数据源
    currentDataSpec = nextDataSpec;
    currentDataSourceBytesRead = 0;
    //调用上面获取的DataSource翻开数据源
    long resolvedLength = nextDataSource.open(nextDataSpec);
    // 更新数据的总长度和实在的URI到数据源的Metadata里,终究会将Metadata记录到索引文件中
    ContentMetadataMutations mutations = new ContentMetadataMutations();
    if (nextDataSpec.length == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) {
      bytesRemaining = resolvedLength;
      ContentMetadataMutations.setContentLength(mutations, readPosition + bytesRemaining);
    }
    if (isReadingFromUpstream()) {
      actualUri = nextDataSource.getUri();
      boolean isRedirected = !requestDataSpec.uri.equals(actualUri);
      ContentMetadataMutations.setRedirectedUri(mutations, isRedirected ? actualUri : null);
    }
    if (isWritingToCache()) {//当时运用的是TeeDataSource,阐明需求写入缓存
      cache.applyContentMetadataMutations(key, mutations);
    }
  }
  private void closeCurrentSource() throws IOException {
    if (currentDataSource == null) {
      return;
    }
    try {
    //封闭当时数据源
      currentDataSource.close();
    } finally {
      currentDataSpec = null;
      currentDataSource = null;
      if (currentHoleSpan != null) {
      //释放HoleSpan
        cache.releaseHoleSpan(currentHoleSpan);
        currentHoleSpan = null;
      }
    }
  }
  @Override
  //读取数据
  public int read(byte[] buffer, int offset, int length) throws IOException {
    if (length == 0) {
      return 0;
    }
    if (bytesRemaining == 0) {
      return C.RESULT_END_OF_INPUT;
    }
    DataSpec requestDataSpec = checkNotNull(this.requestDataSpec);
    DataSpec currentDataSpec = checkNotNull(this.currentDataSpec);
    try {
      if (readPosition >= checkCachePosition) {//判别当时是否需求切换数据源
        openNextSource(requestDataSpec, true);
      }
      //运用当时的DataSource读取数据
      int bytesRead = checkNotNull(currentDataSource).read(buffer, offset, length);
      if (bytesRead != C.RESULT_END_OF_INPUT) {//没有完毕
        if (isReadingFromCache()) {
          totalCachedBytesRead += bytesRead;
        }
        readPosition += bytesRead;
        currentDataSourceBytesRead += bytesRead;
        if (bytesRemaining != C.LENGTH_UNSET) {
          bytesRemaining -= bytesRead;
        }
      } else if (isReadingFromUpstream()
          && (currentDataSpec.length == C.LENGTH_UNSET
              || currentDataSourceBytesRead < currentDataSpec.length)) {
        // 现已冲上游数据源读取到了完毕方位,而currentDataSpec并没有约束长度,这个时分的readPosition就相当于完毕方位,将它作为长度更新到资源的Metadata中
        setNoBytesRemainingAndMaybeStoreLength(castNonNull(requestDataSpec.key));
      } else if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) {
        //尽管读取到完毕方位,但还有剩下,如多个缓存文件的状况,尝试再次翻开数据源读取下个缓存的文件
        closeCurrentSource();
        openNextSource(requestDataSpec, false);
        return read(buffer, offset, length);
      }
      return bytesRead;
    } catch (Throwable e) {
      handleBeforeThrow(e);
      throw e;
    }
  }

CacheDataSource能够处理3种状态的数据翻开读取:

  1. 正在缓存 当时资源无需缓存,或者当时资源正在被其他线程缓存时,此刻即便调用了startReadWriteNonBlocking会回来null,表明当时资源未缓存完结,且被确定。此刻播映器为了确保正常播映会直接运用upstreamDataSource来获取数据,可是每逢获取了100kb的数据时就会,再次去startReadWriteNonBlocking判别当时部分资源是否现已缓存,假如获取到已缓存或者未缓存,此刻会切换数据源,运用cacheWriteDataSource或者cacheReadDataSource来获取数据。一起不再判读是否切换数据源。

  2. 未缓存 当时资源没有缓存,此刻调用startReadWriteNonBlocking回来一个HoleSpan,一起还会确定这段数据,方法其他线程缓存。此刻播映器会运用cacheWriteDataSource也便是TeeDataSource获取数据。翻开TeeDataSource一起会运用CacheDataSink翻开缓存的文件句柄用于后续的写入,CacheDataSink内部经过SimpleCache。startFile获取文件路径。当开端读取数据的时分,TeeDataSource会将upstreamDataSource中读取的数据先经过CacheDataSink写入到上面获取的文件里,注意假如CacheDataSink设置了分段存储,这个进程会屡次startFile,commitFile,将整个资源分红多个缓存文件保存。最终封闭源时CacheDataSink会调用SimpleCache commitFile,创立一个对应于缓存的文件CacheSpan,首要存入内存的CachedContentIndex,最终将内存的CachedContentIndex同步到文件体系的索引文件。

    ExoPlayer架构详解与源码剖析(13)——TeeDataSource和CacheDataSource

  3. 已缓存 当时资源现已缓存,此刻调用startReadWriteNonBlocking回来一个已缓存的CacheSpan,isCached=true。CacheSpan中包括了缓存文件的路径,这种状况直接运用cacheReadDataSource 也便是FileDataSource去获取缓存文件的数据。

    ExoPlayer架构详解与源码剖析(13)——TeeDataSource和CacheDataSource

总结

到这儿整个DataSource部分就悉数完毕了,下一篇方案经过ProgressiveMediaPeriod,把SampleQueue,Loder和DataSource贯穿,面向全体讲下他们之间怎么和谐运作的。


版权声明

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

转载请注明出处

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