private MediaSource buildMediaSource(Uri uri, String overrideExtension) { int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension : uri.getLastPathSegment()); switch (type) { case C.TYPE_SS: return new SsMediaSource(uri, buildDataSourceFactory(false), new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); case C.TYPE_DASH: return new DashMediaSource(uri, buildDataSourceFactory(false), new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); case C.TYPE_HLS: return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null); case C.TYPE_OTHER: return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), mainHandler, null); default: { throw new IllegalStateException("Unsupported type: " + type); } } }
@Override public VideoProgressUpdate getContentProgress() { if (player == null) { return lastContentProgress; } else if (pendingContentPositionMs != C.TIME_UNSET) { sentPendingContentPositionMs = true; return new VideoProgressUpdate(pendingContentPositionMs, contentDurationMs); } else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) { long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs; long fakePositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs; return new VideoProgressUpdate(fakePositionMs, contentDurationMs); } else if (playingAd || contentDurationMs == C.TIME_UNSET) { return VideoProgressUpdate.VIDEO_TIME_NOT_READY; } else { return new VideoProgressUpdate(player.getCurrentPosition(), contentDurationMs); } }
@Override public int getIndexOfPeriod(Object uid) { if (!(uid instanceof Pair)) { return C.INDEX_UNSET; } Pair<?, ?> sourceIndexAndPeriodId = (Pair<?, ?>) uid; if (!(sourceIndexAndPeriodId.first instanceof Integer)) { return C.INDEX_UNSET; } int sourceIndex = (Integer) sourceIndexAndPeriodId.first; Object periodId = sourceIndexAndPeriodId.second; if (sourceIndex < 0 || sourceIndex >= timelines.length) { return C.INDEX_UNSET; } int periodIndexInSource = timelines[sourceIndex].getIndexOfPeriod(periodId); return periodIndexInSource == C.INDEX_UNSET ? C.INDEX_UNSET : getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource; }
/** * Returns the {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified mime type. * {@link C#TRACK_TYPE_UNKNOWN} if the mime type is not known or the mapping cannot be * established. * * @param mimeType The mimeType. * @return The {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified mime type. */ public static int getTrackType(String mimeType) { if (TextUtils.isEmpty(mimeType)) { return C.TRACK_TYPE_UNKNOWN; } else if (isAudio(mimeType)) { return C.TRACK_TYPE_AUDIO; } else if (isVideo(mimeType)) { return C.TRACK_TYPE_VIDEO; } else if (isText(mimeType) || APPLICATION_CEA608.equals(mimeType) || APPLICATION_CEA708.equals(mimeType) || APPLICATION_MP4CEA608.equals(mimeType) || APPLICATION_SUBRIP.equals(mimeType) || APPLICATION_TTML.equals(mimeType) || APPLICATION_TX3G.equals(mimeType) || APPLICATION_MP4VTT.equals(mimeType) || APPLICATION_RAWCC.equals(mimeType) || APPLICATION_VOBSUB.equals(mimeType) || APPLICATION_PGS.equals(mimeType) || APPLICATION_DVBSUBS.equals(mimeType)) { return C.TRACK_TYPE_TEXT; } else if (APPLICATION_ID3.equals(mimeType) || APPLICATION_EMSG.equals(mimeType) || APPLICATION_SCTE35.equals(mimeType) || APPLICATION_CAMERA_MOTION.equals(mimeType)) { return C.TRACK_TYPE_METADATA; } else { return C.TRACK_TYPE_UNKNOWN; } }
@Override public long open(DataSpec dataSpec) throws FileDataSourceException { try { uri = dataSpec.uri; file = new RandomAccessFile(dataSpec.uri.getPath(), "r"); boolean ass = isEncrypted(file); if (ass) { file.seek(dataSpec.position + length); } bytesRemaining = dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position : dataSpec.length; if (bytesRemaining < 0) { throw new EOFException(); } } catch (IOException e) { throw new FileDataSourceException(e); } opened = true; if (listener != null) { listener.onTransferStart(this, dataSpec); } return bytesRemaining; }
@Override public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) { if (pendingDiscontinuity) { return C.RESULT_NOTHING_READ; } if (sentEos) { buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); return C.RESULT_BUFFER_READ; } int result = stream.readData(formatHolder, buffer, requireFormat); // TODO: Clear gapless playback metadata if a format was read (if applicable). if (endUs != C.TIME_END_OF_SOURCE && ((result == C.RESULT_BUFFER_READ && buffer.timeUs >= endUs) || (result == C.RESULT_NOTHING_READ && mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE))) { buffer.clear(); buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); sentEos = true; return C.RESULT_BUFFER_READ; } if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream()) { buffer.timeUs -= startUs; } return result; }
/** * Splits an array of NAL units. * <p> * If the input consists of NAL start code delimited units, then the returned array consists of * the split NAL units, each of which is still prefixed with the NAL start code. For any other * input, null is returned. * * @param data An array of data. * @return The individual NAL units, or null if the input did not consist of NAL start code * delimited units. */ public static byte[][] splitNalUnits(byte[] data) { if (!isNalStartCode(data, 0)) { // data does not consist of NAL start code delimited units. return null; } List<Integer> starts = new ArrayList<>(); int nalUnitIndex = 0; do { starts.add(nalUnitIndex); nalUnitIndex = findNalStartCode(data, nalUnitIndex + NAL_START_CODE.length); } while (nalUnitIndex != C.INDEX_UNSET); byte[][] split = new byte[starts.size()][]; for (int i = 0; i < starts.size(); i++) { int startIndex = starts.get(i); int endIndex = i < starts.size() - 1 ? starts.get(i + 1) : data.length; byte[] nal = new byte[endIndex - startIndex]; System.arraycopy(data, startIndex, nal, 0, nal.length); split[i] = nal; } return split; }
/** * @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists. * @param variants The available variants. * @param dataSourceFactory An {@link HlsDataSourceFactory} to create {@link DataSource}s for the * chunks. * @param timestampAdjusterProvider A provider of {@link TimestampAdjuster} instances. If * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the * same provider. * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption * information is available in the master playlist. */ public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants, HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider, List<Format> muxedCaptionFormats) { this.playlistTracker = playlistTracker; this.variants = variants; this.timestampAdjusterProvider = timestampAdjusterProvider; this.muxedCaptionFormats = muxedCaptionFormats; Format[] variantFormats = new Format[variants.length]; int[] initialTrackSelection = new int[variants.length]; for (int i = 0; i < variants.length; i++) { variantFormats[i] = variants[i].format; initialTrackSelection[i] = i; } mediaDataSource = dataSourceFactory.createDataSource(C.DATA_TYPE_MEDIA); encryptionDataSource = dataSourceFactory.createDataSource(C.DATA_TYPE_DRM); trackGroup = new TrackGroup(variantFormats); trackSelection = new InitializationTrackSelection(trackGroup, initialTrackSelection); }
public long getBufferedPositionUs() { if (loadingFinished) { return C.TIME_END_OF_SOURCE; } else if (isPendingReset()) { return pendingResetPositionUs; } else { long bufferedPositionUs = lastSeekPositionUs; HlsMediaChunk lastMediaChunk = mediaChunks.getLast(); HlsMediaChunk lastCompletedMediaChunk = lastMediaChunk.isLoadCompleted() ? lastMediaChunk : mediaChunks.size() > 1 ? mediaChunks.get(mediaChunks.size() - 2) : null; if (lastCompletedMediaChunk != null) { bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); } int sampleQueueCount = sampleQueues.size(); for (int i = 0; i < sampleQueueCount; i++) { bufferedPositionUs = Math.max(bufferedPositionUs, sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); } return bufferedPositionUs; } }
@Override public Window getWindow(int windowIndex, Window window, boolean setIds, long defaultPositionProjectionUs) { Assertions.checkIndex(windowIndex, 0, 1); Object id = setIds ? ID : null; long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs; if (isDynamic) { windowDefaultStartPositionUs += defaultPositionProjectionUs; if (windowDefaultStartPositionUs > windowDurationUs) { // The projection takes us beyond the end of the live window. windowDefaultStartPositionUs = C.TIME_UNSET; } } return window.set(id, C.TIME_UNSET, C.TIME_UNSET, isSeekable, isDynamic, windowDefaultStartPositionUs, windowDurationUs, 0, 0, windowPositionInPeriodUs); }
private void configureRetry(ExtractingLoadable loadable) { if (length != C.LENGTH_UNSET || (seekMap != null && seekMap.getDurationUs() != C.TIME_UNSET)) { // We're playing an on-demand stream. Resume the current loadable, which will // request data starting from the point it left off. } else { // We're playing a stream of unknown length and duration. Assume it's live, and // therefore that the data at the uri is a continuously shifting window of the latest // available media. For this case there's no way to continue loading from where a // previous load finished, so it's necessary to load from the start whenever commencing // a new load. lastSeekPositionUs = 0; notifyReset = prepared; int trackCount = sampleQueues.size(); for (int i = 0; i < trackCount; i++) { sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]); } loadable.setLoadPosition(0, 0); } }
/** * Returns the index of the track that contains the earliest current sample, or * {@link C#INDEX_UNSET} if no samples remain. */ private int getTrackIndexOfEarliestCurrentSample() { int earliestSampleTrackIndex = C.INDEX_UNSET; long earliestSampleOffset = Long.MAX_VALUE; for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) { Mp4Track track = tracks[trackIndex]; int sampleIndex = track.sampleIndex; if (sampleIndex == track.sampleTable.sampleCount) { continue; } long trackSampleOffset = track.sampleTable.offsets[sampleIndex]; if (trackSampleOffset < earliestSampleOffset) { earliestSampleOffset = trackSampleOffset; earliestSampleTrackIndex = trackIndex; } } return earliestSampleTrackIndex; }
/** * update the resume position of the main movice * * @param controller */ public static void updateMovieResumePosition(PlayerUIController controller) { if (controller == null) { return; } SimpleExoPlayer moviePlayer = controller.getContentPlayer(); if (moviePlayer != null && moviePlayer.getPlaybackState() != ExoPlayer.STATE_IDLE) { int resumeWindow = moviePlayer.getCurrentWindowIndex(); long resumePosition = moviePlayer.isCurrentWindowSeekable() ? Math.max(0, moviePlayer.getCurrentPosition()) : C.TIME_UNSET; controller.setMovieResumeInfo(resumeWindow, resumePosition); } }
@Override public long getPosition(long timeUs) { long earliestSamplePosition = Long.MAX_VALUE; for (Mp4Track track : tracks) { TrackSampleTable sampleTable = track.sampleTable; int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs); if (sampleIndex == C.INDEX_UNSET) { // Handle the case where the requested time is before the first synchronization sample. sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); } long offset = sampleTable.offsets[sampleIndex]; if (offset < earliestSamplePosition) { earliestSamplePosition = offset; } } return earliestSamplePosition; }
/** * @param flags Flags that control the extractor's behavior. * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. * @param sideloadedTrack Sideloaded track information, in the case that the extractor * will not receive a moov box in the input data. */ public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster, Track sideloadedTrack) { this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); this.timestampAdjuster = timestampAdjuster; this.sideloadedTrack = sideloadedTrack; atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalPrefix = new ParsableByteArray(5); nalBuffer = new ParsableByteArray(); encryptionSignalByte = new ParsableByteArray(1); extendedTypeScratch = new byte[16]; containerAtoms = new Stack<>(); pendingMetadataSampleInfos = new LinkedList<>(); trackBundles = new SparseArray<>(); durationUs = C.TIME_UNSET; segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; enterReadingAtomHeaderState(); }
@Override protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) throws ExoPlaybackException { Assertions.checkState(rendererCapabilities[VIDEO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_VIDEO); Assertions.checkState(rendererCapabilities[AUDIO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_AUDIO); Assertions.checkState(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].length == 1); Assertions.checkState(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].length == 1); TrackSelection[] selections = new TrackSelection[rendererCapabilities.length]; selections[VIDEO_RENDERER_INDEX] = new RandomTrackSelection( rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), getVideoTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds, canIncludeAdditionalVideoFormats), 0 /* seed */); selections[AUDIO_RENDERER_INDEX] = new FixedTrackSelection( rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), getTrackIndex(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), audioFormatId)); includedAdditionalVideoFormats = selections[VIDEO_RENDERER_INDEX].length() > videoFormatIds.length; return selections; }
/** * @param context A context. * @param mediaCodecSelector A decoder selector. * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer * can attempt to seamlessly join an ongoing playback. * @param drmSessionManager For use with encrypted content. May be null if support for encrypted * content is not required. * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. * For example a media file may start with a short clear region so as to allow playback to * begin in parallel with key acquisition. This parameter specifies whether the renderer is * permitted to play clear regions of encrypted media files before {@code drmSessionManager} * has obtained the keys necessary to decrypt encrypted regions of the media. * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. */ public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys, Handler eventHandler, VideoRendererEventListener eventListener, int maxDroppedFramesToNotify) { super(C.TRACK_TYPE_VIDEO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context); eventDispatcher = new EventDispatcher(eventHandler, eventListener); deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround(); joiningDeadlineMs = C.TIME_UNSET; currentWidth = Format.NO_VALUE; currentHeight = Format.NO_VALUE; currentPixelWidthHeightRatio = Format.NO_VALUE; pendingPixelWidthHeightRatio = Format.NO_VALUE; scalingMode = C.VIDEO_SCALING_MODE_DEFAULT; clearReportedVideoSize(); }
@Override public int onLoadError(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error) { boolean isFatal = error instanceof ParserException; eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded(), error, isFatal); if (isFatal) { return Loader.DONT_RETRY_FATAL; } boolean shouldRetry = true; if (ChunkedTrackBlacklistUtil.shouldBlacklist(error)) { blacklistUntilMs = SystemClock.elapsedRealtime() + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS; notifyPlaylistBlacklisting(playlistUrl, ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS); shouldRetry = primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl(); } return shouldRetry ? Loader.RETRY : Loader.DONT_RETRY; }
@Override public final void load() throws IOException, InterruptedException { try { dataSource.open(dataSpec); limit = 0; int bytesRead = 0; while (bytesRead != C.RESULT_END_OF_INPUT && !loadCanceled) { maybeExpandData(); bytesRead = dataSource.read(data, limit, READ_GRANULARITY); if (bytesRead != -1) { limit += bytesRead; } } if (!loadCanceled) { consume(data, limit); } } finally { Util.closeQuietly(dataSource); } }
@Override public boolean continueLoading(long positionUs) { boolean madeProgress = false; boolean madeProgressThisIteration; do { madeProgressThisIteration = false; long nextLoadPositionUs = getNextLoadPositionUs(); if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { break; } for (SequenceableLoader loader : loaders) { if (loader.getNextLoadPositionUs() == nextLoadPositionUs) { madeProgressThisIteration |= loader.continueLoading(positionUs); } } madeProgress |= madeProgressThisIteration; } while (madeProgressThisIteration); return madeProgress; }
@Override public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) throws UnhandledFormatException { if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT && encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) { throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); } if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount && this.encoding == encoding) { return false; } this.sampleRateHz = sampleRateHz; this.channelCount = channelCount; this.encoding = encoding; if (encoding == C.ENCODING_PCM_16BIT) { buffer = EMPTY_BUFFER; } return true; }
private void updateForCurrentTrackSelections() { if (player == null) { return; } TrackSelectionArray selections = player.getCurrentTrackSelections(); for (int i = 0; i < selections.length; i++) { if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) { // Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in // onRenderedFirstFrame(). return; } } // Video disabled so the shutter must be closed. shutterView.setVisibility(VISIBLE); }
@Override public long getNextLoadPositionUs() { if (isPendingReset()) { return pendingResetPositionUs; } else { return loadingFinished ? C.TIME_END_OF_SOURCE : mediaChunks.getLast().endTimeUs; } }
@Override public int read(byte[] buffer, int offset, int readLength) throws IOException { if (readLength == 0) { return 0; } if (bytesRemaining == 0) { return C.RESULT_END_OF_INPUT; } try { int bytesRead = currentDataSource.read(buffer, offset, readLength); if (bytesRead >= 0) { if (currentDataSource == cacheReadDataSource) { totalCachedBytesRead += bytesRead; } readPosition += bytesRead; if (bytesRemaining != C.LENGTH_UNSET) { bytesRemaining -= bytesRead; } } else { if (currentRequestUnbounded) { // We only do unbounded requests to upstream and only when we don't know the actual stream // length. So we reached the end of stream. setContentLength(readPosition); bytesRemaining = 0; } closeCurrentSource(); if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) { if (openNextSource(false)) { return read(buffer, offset, readLength); } } } return bytesRead; } catch (IOException e) { handleBeforeThrow(e); throw e; } }
private void checkForContentComplete() { if (contentDurationMs != C.TIME_UNSET && pendingContentPositionMs == C.TIME_UNSET && player.getContentPosition() + END_OF_CONTENT_POSITION_THRESHOLD_MS >= contentDurationMs && !sentContentComplete) { adsLoader.contentComplete(); if (DEBUG) { Log.d(TAG, "adsLoader.contentComplete"); } sentContentComplete = true; } }
private static long[] getAdGroupTimesUs(List<Float> cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. return new long[] {0}; } int count = cuePoints.size(); long[] adGroupTimesUs = new long[count]; for (int i = 0; i < count; i++) { double cuePoint = cuePoints.get(i); adGroupTimesUs[i] = cuePoint == -1.0 ? C.TIME_END_OF_SOURCE : (long) (C.MICROS_PER_SECOND * cuePoint); } return adGroupTimesUs; }
@Override public void updateSelectedTrack(long bufferedDurationUs) { long nowMs = SystemClock.elapsedRealtime(); // Get the current and ideal selections. int currentSelectedIndex = selectedIndex; Format currentFormat = getSelectedFormat(); int idealSelectedIndex = determineIdealSelectedIndex(nowMs); Format idealFormat = getFormat(idealSelectedIndex); // Assume we can switch to the ideal selection. selectedIndex = idealSelectedIndex; // Revert back to the current selection if conditions are not suitable for switching. if (currentFormat != null && !isBlacklisted(selectedIndex, nowMs)) { if (idealFormat.bitrate > currentFormat.bitrate && bufferedDurationUs < minDurationForQualityIncreaseUs) { // The ideal track is a higher quality, but we have insufficient buffer to safely switch // up. Defer switching up for now. selectedIndex = currentSelectedIndex; } else if (idealFormat.bitrate < currentFormat.bitrate && bufferedDurationUs >= maxDurationForQualityDecreaseUs) { // The ideal track is a lower quality, but we have sufficient buffer to defer switching // down for now. selectedIndex = currentSelectedIndex; } } // If we adapted, update the trigger. if (selectedIndex != currentSelectedIndex) { reason = C.SELECTION_REASON_ADAPTIVE; } }
/** * Infer content type int. * * @param fileName the file name * @return the int */ @C.ContentType public static int inferContentType(String fileName) { fileName = Util.toLowerInvariant(fileName); if (fileName.matches(".*m3u8.*")) { return C.TYPE_HLS; } else if (fileName.matches(".*mpd.*")) { return C.TYPE_DASH; } else if (fileName.matches(".*\\.ism(l)?(/manifest(\\(.+\\))?)?")) { return C.TYPE_SS; } else { return C.TYPE_OTHER; } }
/** * Constructs a new {@link Mp3Extractor}. * * @param flags Flags that control the extractor's behavior. * @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or * {@link C#TIME_UNSET} if forcing is not required. */ public Mp3Extractor(@Flags int flags, long forcedFirstSampleTimestampUs) { this.flags = flags; this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs; scratch = new ParsableByteArray(SCRATCH_LENGTH); synchronizedHeader = new MpegAudioHeader(); gaplessInfoHolder = new GaplessInfoHolder(); basisTimeUs = C.TIME_UNSET; }
public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment, int bitsPerSample, @C.PcmEncoding int encoding) { this.numChannels = numChannels; this.sampleRateHz = sampleRateHz; this.averageBytesPerSecond = averageBytesPerSecond; this.blockAlignment = blockAlignment; this.bitsPerSample = bitsPerSample; this.encoding = encoding; }
@Override public void onLoadCompleted(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs, long loadDurationMs) { HlsPlaylist result = loadable.getResult(); if (result instanceof HlsMediaPlaylist) { processLoadedPlaylist((HlsMediaPlaylist) result); eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); } else { onLoadError(loadable, elapsedRealtimeMs, loadDurationMs, new ParserException("Loaded playlist has unexpected type.")); } }
public boolean moveNext() { if (++index == length) { return false; } offset = chunkOffsetsAreLongs ? chunkOffsets.readUnsignedLongToLong() : chunkOffsets.readUnsignedInt(); if (index == nextSamplesPerChunkChangeIndex) { numSamples = stsc.readUnsignedIntToInt(); stsc.skipBytes(4); // Skip sample_description_index nextSamplesPerChunkChangeIndex = --remainingSamplesPerChunkChanges > 0 ? (stsc.readUnsignedIntToInt() - 1) : C.INDEX_UNSET; } return true; }
/** * Hides the controller. */ public void hide() { if (isVisible()) { setVisibility(GONE); if (visibilityListener != null) { visibilityListener.onVisibilityChange(getVisibility()); } removeCallbacks(updateProgressAction); removeCallbacks(hideAction); hideAtMs = C.TIME_UNSET; } }
/** * Consumes the unescaped content of an SEI NAL unit, writing the content of any CEA-608 messages * as samples to all of the provided outputs. * * @param presentationTimeUs The presentation time in microseconds for any samples. * @param seiBuffer The unescaped SEI NAL unit data, excluding the NAL unit start code and type. * @param outputs The outputs to which any samples should be written. */ public static void consume(long presentationTimeUs, ParsableByteArray seiBuffer, TrackOutput[] outputs) { while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { int payloadType = readNon255TerminatedValue(seiBuffer); int payloadSize = readNon255TerminatedValue(seiBuffer); // Process the payload. if (payloadSize == -1 || payloadSize > seiBuffer.bytesLeft()) { // This might occur if we're trying to read an encrypted SEI NAL unit. Log.w(TAG, "Skipping remainder of malformed SEI NAL unit."); seiBuffer.setPosition(seiBuffer.limit()); } else if (isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) { // Ignore country_code (1) + provider_code (2) + user_identifier (4) // + user_data_type_code (1). seiBuffer.skipBytes(8); // Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1). int ccCount = seiBuffer.readUnsignedByte() & 0x1F; // Ignore em_data (1) seiBuffer.skipBytes(1); // Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2) // + cc_data_1 (8) + cc_data_2 (8). int sampleLength = ccCount * 3; int sampleStartPosition = seiBuffer.getPosition(); for (TrackOutput output : outputs) { seiBuffer.setPosition(sampleStartPosition); output.sampleData(seiBuffer, sampleLength); output.sampleMetadata(presentationTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null); } // Ignore trailing information in SEI, if any. seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3)); } else { seiBuffer.skipBytes(payloadSize); } } }
@Override public int getIndexOfPeriod(Object uid) { if (!(uid instanceof Pair)) { return C.INDEX_UNSET; } Pair<?, ?> loopCountAndChildUid = (Pair<?, ?>) uid; if (!(loopCountAndChildUid.first instanceof Integer)) { return C.INDEX_UNSET; } int loopCount = (Integer) loopCountAndChildUid.first; int periodIndexOffset = loopCount * childPeriodCount; return childTimeline.getIndexOfPeriod(loopCountAndChildUid.second) + periodIndexOffset; }
@Override public void handleMessage(int messageType, Object message) throws ExoPlaybackException { if (messageType == C.MSG_SET_SURFACE) { setSurface((Surface) message); } else if (messageType == C.MSG_SET_SCALING_MODE) { scalingMode = (Integer) message; MediaCodec codec = getCodec(); if (codec != null) { setVideoScalingMode(codec, scalingMode); } } else { super.handleMessage(messageType, message); } }
@Override public void onAttachedToWindow() { super.onAttachedToWindow(); isAttachedToWindow = true; if (hideAtMs != C.TIME_UNSET) { long delayMs = hideAtMs - SystemClock.uptimeMillis(); if (delayMs <= 0) { hide(); } else { postDelayed(hideAction, delayMs); } } updateAll(); }
@Override public final int indexOf(int indexInTrackGroup) { for (int i = 0; i < length; i++) { if (tracks[i] == indexInTrackGroup) { return i; } } return C.INDEX_UNSET; }
public ChunkIterator(ParsableByteArray stsc, ParsableByteArray chunkOffsets, boolean chunkOffsetsAreLongs) { this.stsc = stsc; this.chunkOffsets = chunkOffsets; this.chunkOffsetsAreLongs = chunkOffsetsAreLongs; chunkOffsets.setPosition(Atom.FULL_HEADER_SIZE); length = chunkOffsets.readUnsignedIntToInt(); stsc.setPosition(Atom.FULL_HEADER_SIZE); remainingSamplesPerChunkChanges = stsc.readUnsignedIntToInt(); Assertions.checkState(stsc.readInt() == 1, "first_chunk must be 1"); index = C.INDEX_UNSET; }
/** * 快进 */ private void fastForward() { if (fastForwardMs <= 0) { return; } long durationMs = player.getDuration(); long seekPositionMs = player.getCurrentPosition() + fastForwardMs; if (durationMs != C.TIME_UNSET) { seekPositionMs = Math.min(seekPositionMs, durationMs); } player.seekTo(seekPositionMs); }