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); }
/** * 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); }