Example #1
0
 private static String buildTrackName(MediaFormat format) {
   if (format.adaptive) {
     return "auto";
   }
   String trackName;
   if (MimeTypes.isVideo(format.mimeType)) {
     trackName =
         joinWithSeparator(
             joinWithSeparator(buildResolutionString(format), buildBitrateString(format)),
             buildTrackIdString(format));
   } else if (MimeTypes.isAudio(format.mimeType)) {
     trackName =
         joinWithSeparator(
             joinWithSeparator(
                 joinWithSeparator(buildLanguageString(format), buildAudioPropertyString(format)),
                 buildBitrateString(format)),
             buildTrackIdString(format));
   } else {
     trackName =
         joinWithSeparator(
             joinWithSeparator(buildLanguageString(format), buildBitrateString(format)),
             buildTrackIdString(format));
   }
   return trackName.length() == 0 ? "unknown" : trackName;
 }
 @Override
 protected boolean handlesTrack(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
     throws DecoderQueryException {
   String mimeType = mediaFormat.mimeType;
   return MimeTypes.isVideo(mimeType)
       && (MimeTypes.VIDEO_UNKNOWN.equals(mimeType)
           || mediaCodecSelector.getDecoderInfo(mimeType, false) != null);
 }
Example #3
0
  /**
   * Updates the provided {@link ChunkOperationHolder} to contain the next operation that should be
   * performed by the calling {@link HlsSampleSource}.
   *
   * @param previousTsChunk The previously loaded chunk that the next chunk should follow.
   * @param playbackPositionUs The current playback position. If previousTsChunk is null then this
   *     parameter is the position from which playback is expected to start (or restart) and hence
   *     should be interpreted as a seek position.
   * @param out The holder to populate with the result. {@link ChunkOperationHolder#queueSize} is
   *     unused.
   */
  public void getChunkOperation(
      TsChunk previousTsChunk,
      long seekPositionUs,
      long playbackPositionUs,
      ChunkOperationHolder out) {
    int nextVariantIndex;
    boolean switchingVariantSpliced;
    if (adaptiveMode == ADAPTIVE_MODE_NONE) {
      nextVariantIndex = selectedVariantIndex;
      switchingVariantSpliced = false;
    } else {
      nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs);
      switchingVariantSpliced =
          previousTsChunk != null
              && !variants[nextVariantIndex].format.equals(previousTsChunk.format)
              && adaptiveMode == ADAPTIVE_MODE_SPLICE;
    }

    HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex];
    if (mediaPlaylist == null) {
      // We don't have the media playlist for the next variant. Request it now.
      out.chunk = newMediaPlaylistChunk(nextVariantIndex);
      return;
    }

    selectedVariantIndex = nextVariantIndex;
    int chunkMediaSequence = 0;
    if (live) {
      if (previousTsChunk == null) {
        if (seekPositionUs == 0) {
          chunkMediaSequence = getLiveStartChunkMediaSequence(nextVariantIndex);
        } else {
          chunkMediaSequence =
              Util.binarySearchFloor(mediaPlaylist.segments, seekPositionUs, true, true)
                  + mediaPlaylist.mediaSequence;
        }
      } else {
        chunkMediaSequence =
            switchingVariantSpliced ? previousTsChunk.chunkIndex : previousTsChunk.chunkIndex + 1;
        if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
          fatalError = new BehindLiveWindowException();
          return;
        }
      }
    } else {
      // Not live.
      if (previousTsChunk == null) {
        chunkMediaSequence =
            Util.binarySearchFloor(mediaPlaylist.segments, playbackPositionUs, true, true)
                + mediaPlaylist.mediaSequence;
      } else {
        chunkMediaSequence =
            switchingVariantSpliced ? previousTsChunk.chunkIndex : previousTsChunk.chunkIndex + 1;
      }
    }

    int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence;
    if (chunkIndex >= mediaPlaylist.segments.size()) {
      if (!mediaPlaylist.live) {
        out.endOfStream = true;
      } else if (shouldRerequestLiveMediaPlaylist(nextVariantIndex)) {
        out.chunk = newMediaPlaylistChunk(nextVariantIndex);
      }
      return;
    }

    HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
    Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);

    // Check if encryption is specified.
    if (segment.isEncrypted) {
      Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
      if (!keyUri.equals(encryptionKeyUri)) {
        // Encryption is specified and the key has changed.
        out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex);
        return;
      }
      if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
        setEncryptionData(keyUri, segment.encryptionIV, encryptionKey);
      }
    } else {
      clearEncryptionData();
    }

    // Configure the data source and spec for the chunk.
    DataSpec dataSpec =
        new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, null);

    // Compute start and end times, and the sequence number of the next chunk.
    long startTimeUs;
    if (live) {
      if (previousTsChunk == null) {
        startTimeUs = segment.startTimeUs;
      } else if (switchingVariantSpliced) {
        startTimeUs = previousTsChunk.startTimeUs;
      } else {
        startTimeUs = previousTsChunk.endTimeUs;
      }
    } else /* Not live */ {
      startTimeUs = segment.startTimeUs;
    }
    long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND);
    int trigger = Chunk.TRIGGER_UNSPECIFIED;
    Format format = variants[selectedVariantIndex].format;

    // Configure the extractor that will read the chunk.
    HlsExtractorWrapper extractorWrapper;
    String lastPathSegment = chunkUri.getLastPathSegment();
    if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) {
      // TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner
      // identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3
      // case below.
      Extractor extractor = new AdtsExtractor(startTimeUs);
      extractorWrapper =
          new HlsExtractorWrapper(
              trigger,
              format,
              startTimeUs,
              extractor,
              switchingVariantSpliced,
              MediaFormat.NO_VALUE,
              MediaFormat.NO_VALUE);
    } else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
      Extractor extractor = new Mp3Extractor(startTimeUs);
      extractorWrapper =
          new HlsExtractorWrapper(
              trigger,
              format,
              startTimeUs,
              extractor,
              switchingVariantSpliced,
              MediaFormat.NO_VALUE,
              MediaFormat.NO_VALUE);
    } else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
        || lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
      PtsTimestampAdjuster timestampAdjuster =
          timestampAdjusterProvider.getAdjuster(
              isMaster, segment.discontinuitySequenceNumber, startTimeUs);
      if (timestampAdjuster == null) {
        // The master source has yet to instantiate an adjuster for the discontinuity sequence.
        // TODO: There's probably an edge case if the master starts playback at a chunk belonging to
        // a discontinuity sequence greater than the one that this source is trying to start at.
        return;
      }
      Extractor extractor = new WebvttExtractor(timestampAdjuster);
      extractorWrapper =
          new HlsExtractorWrapper(
              trigger,
              format,
              startTimeUs,
              extractor,
              switchingVariantSpliced,
              MediaFormat.NO_VALUE,
              MediaFormat.NO_VALUE);
    } else if (previousTsChunk == null
        || previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
        || !format.equals(previousTsChunk.format)) {
      // MPEG-2 TS segments, but we need a new extractor.
      PtsTimestampAdjuster timestampAdjuster =
          timestampAdjusterProvider.getAdjuster(
              isMaster, segment.discontinuitySequenceNumber, startTimeUs);
      if (timestampAdjuster == null) {
        // The master source has yet to instantiate an adjuster for the discontinuity sequence.
        return;
      }
      int workaroundFlags = 0;
      String codecs = format.codecs;
      if (!TextUtils.isEmpty(codecs)) {
        // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
        // exist. If we know from the codec attribute that they don't exist, then we can explicitly
        // ignore them even if they're declared.
        if (MimeTypes.getAudioMediaMimeType(codecs) != MimeTypes.AUDIO_AAC) {
          workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_AAC_STREAM;
        }
        if (MimeTypes.getVideoMediaMimeType(codecs) != MimeTypes.VIDEO_H264) {
          workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM;
        }
      }
      Extractor extractor = new TsExtractor(timestampAdjuster, workaroundFlags);
      ExposedTrack selectedTrack = tracks.get(selectedTrackIndex);
      extractorWrapper =
          new HlsExtractorWrapper(
              trigger,
              format,
              startTimeUs,
              extractor,
              switchingVariantSpliced,
              selectedTrack.adaptiveMaxWidth,
              selectedTrack.adaptiveMaxHeight);
    } else {
      // MPEG-2 TS segments, and we need to continue using the same extractor.
      extractorWrapper = previousTsChunk.extractorWrapper;
    }

    out.chunk =
        new TsChunk(
            dataSource,
            dataSpec,
            trigger,
            format,
            startTimeUs,
            endTimeUs,
            chunkMediaSequence,
            segment.discontinuitySequenceNumber,
            extractorWrapper,
            encryptionKey,
            encryptionIv);
  }