public CachedRegionTracker(Cache cache, String cacheKey, ChunkIndex chunkIndex) { this.cache = cache; this.cacheKey = cacheKey; this.chunkIndex = chunkIndex; this.regions = new TreeSet<>(); this.lookupRegion = new Region(0, 0); synchronized (this) { NavigableSet<CacheSpan> cacheSpans = cache.addListener(cacheKey, this); if (cacheSpans != null) { // Merge the spans into regions. mergeSpan is more efficient when merging from high to low, // which is why a descending iterator is used here. Iterator<CacheSpan> spanIterator = cacheSpans.descendingIterator(); while (spanIterator.hasNext()) { CacheSpan span = spanIterator.next(); mergeSpan(span); } } } }
@Override public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof InitializationChunk) { InitializationChunk initializationChunk = (InitializationChunk) chunk; RepresentationHolder representationHolder = representationHolders[trackSelection.indexOf(initializationChunk.trackFormat)]; // The null check avoids overwriting an index obtained from the manifest with one obtained // from the stream. If the manifest defines an index then the stream shouldn't, but in cases // where it does we should ignore it. if (representationHolder.segmentIndex == null) { SeekMap seekMap = representationHolder.extractorWrapper.getSeekMap(); if (seekMap != null) { representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap); } } } }
@Override public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof InitializationChunk) { InitializationChunk initializationChunk = (InitializationChunk) chunk; RepresentationHolder representationHolder = representationHolders[trackSelection.indexOf(initializationChunk.trackFormat)]; Format sampleFormat = initializationChunk.getSampleFormat(); if (sampleFormat != null) { representationHolder.setSampleFormat(sampleFormat); } // The null check avoids overwriting an index obtained from the manifest with one obtained // from the stream. If the manifest defines an index then the stream shouldn't, but in cases // where it does we should ignore it. if (representationHolder.segmentIndex == null) { SeekMap seekMap = initializationChunk.getSeekMap(); if (seekMap != null) { representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap, initializationChunk.dataSpec.uri.toString()); } } } }
private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserException { if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(leaf); } else if (leaf.type == Atom.TYPE_sidx) { Pair<Long, ChunkIndex> result = parseSidx(leaf.data, inputPosition); segmentIndexEarliestPresentationTimeUs = result.first; extractorOutput.seekMap(result.second); haveOutputSeekMap = true; } else if (leaf.type == Atom.TYPE_emsg) { onEmsgLeafAtomRead(leaf.data); } }
/** * Builds a {@link SeekMap} from the recently gathered Cues information. * * @return The built {@link SeekMap}. The returned {@link SeekMap} may be unseekable if cues * information was missing or incomplete. */ private SeekMap buildSeekMap() { if (segmentContentPosition == C.POSITION_UNSET || durationUs == C.TIME_UNSET || cueTimesUs == null || cueTimesUs.size() == 0 || cueClusterPositions == null || cueClusterPositions.size() != cueTimesUs.size()) { // Cues information is missing or incomplete. cueTimesUs = null; cueClusterPositions = null; return new SeekMap.Unseekable(durationUs); } int cuePointsSize = cueTimesUs.size(); int[] sizes = new int[cuePointsSize]; long[] offsets = new long[cuePointsSize]; long[] durationsUs = new long[cuePointsSize]; long[] timesUs = new long[cuePointsSize]; for (int i = 0; i < cuePointsSize; i++) { timesUs[i] = cueTimesUs.get(i); offsets[i] = segmentContentPosition + cueClusterPositions.get(i); } for (int i = 0; i < cuePointsSize - 1; i++) { sizes[i] = (int) (offsets[i + 1] - offsets[i]); durationsUs[i] = timesUs[i + 1] - timesUs[i]; } sizes[cuePointsSize - 1] = (int) (segmentContentPosition + segmentContentSize - offsets[cuePointsSize - 1]); durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1]; cueTimesUs = null; cueClusterPositions = null; return new ChunkIndex(sizes, offsets, durationsUs, timesUs); }
private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserException { if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(leaf); } else if (leaf.type == Atom.TYPE_sidx) { ChunkIndex segmentIndex = parseSidx(leaf.data, inputPosition); extractorOutput.seekMap(segmentIndex); haveOutputSeekMap = true; } }
/** * Returns DashSegmentIndex for given representation. */ private DashSegmentIndex getSegmentIndex(DataSource dataSource, DashManifest manifest, RepresentationKey key) throws IOException, InterruptedException { AdaptationSet adaptationSet = manifest.getPeriod(key.periodIndex).adaptationSets.get( key.adaptationSetIndex); Representation representation = adaptationSet.representations.get(key.representationIndex); DashSegmentIndex index = representation.getIndex(); if (index != null) { return index; } ChunkIndex seekMap = DashUtil.loadChunkIndex(dataSource, adaptationSet.type, representation); return seekMap == null ? null : new DashWrappingSegmentIndex(seekMap); }
/** * Parses a sidx atom (defined in 14496-12). * * @param atom The atom data. * @param inputPosition The input position of the first byte after the atom. * @return A pair consisting of the earliest presentation time in microseconds, and the parsed * {@link ChunkIndex}. */ private static Pair<Long, ChunkIndex> parseSidx(ParsableByteArray atom, long inputPosition) throws ParserException { atom.setPosition(Atom.HEADER_SIZE); int fullAtom = atom.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); atom.skipBytes(4); long timescale = atom.readUnsignedInt(); long earliestPresentationTime; long offset = inputPosition; if (version == 0) { earliestPresentationTime = atom.readUnsignedInt(); offset += atom.readUnsignedInt(); } else { earliestPresentationTime = atom.readUnsignedLongToLong(); offset += atom.readUnsignedLongToLong(); } long earliestPresentationTimeUs = Util.scaleLargeTimestamp(earliestPresentationTime, C.MICROS_PER_SECOND, timescale); atom.skipBytes(2); int referenceCount = atom.readUnsignedShort(); int[] sizes = new int[referenceCount]; long[] offsets = new long[referenceCount]; long[] durationsUs = new long[referenceCount]; long[] timesUs = new long[referenceCount]; long time = earliestPresentationTime; long timeUs = earliestPresentationTimeUs; for (int i = 0; i < referenceCount; i++) { int firstInt = atom.readInt(); int type = 0x80000000 & firstInt; if (type != 0) { throw new ParserException("Unhandled indirect reference"); } long referenceDuration = atom.readUnsignedInt(); sizes[i] = 0x7FFFFFFF & firstInt; offsets[i] = offset; // Calculate time and duration values such that any rounding errors are consistent. i.e. That // timesUs[i] + durationsUs[i] == timesUs[i + 1]. timesUs[i] = timeUs; time += referenceDuration; timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); durationsUs[i] = timeUs - timesUs[i]; atom.skipBytes(4); offset += sizes[i]; } return Pair.create(earliestPresentationTimeUs, new ChunkIndex(sizes, offsets, durationsUs, timesUs)); }
/** * @param chunkIndex The {@link ChunkIndex} to wrap. */ public DashWrappingSegmentIndex(ChunkIndex chunkIndex) { this.chunkIndex = chunkIndex; }
/** * Parses a sidx atom (defined in 14496-12). */ private static ChunkIndex parseSidx(ParsableByteArray atom, long inputPosition) throws ParserException { atom.setPosition(Atom.HEADER_SIZE); int fullAtom = atom.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); atom.skipBytes(4); long timescale = atom.readUnsignedInt(); long earliestPresentationTime; long offset = inputPosition; if (version == 0) { earliestPresentationTime = atom.readUnsignedInt(); offset += atom.readUnsignedInt(); } else { earliestPresentationTime = atom.readUnsignedLongToLong(); offset += atom.readUnsignedLongToLong(); } atom.skipBytes(2); int referenceCount = atom.readUnsignedShort(); int[] sizes = new int[referenceCount]; long[] offsets = new long[referenceCount]; long[] durationsUs = new long[referenceCount]; long[] timesUs = new long[referenceCount]; long time = earliestPresentationTime; long timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); for (int i = 0; i < referenceCount; i++) { int firstInt = atom.readInt(); int type = 0x80000000 & firstInt; if (type != 0) { throw new ParserException("Unhandled indirect reference"); } long referenceDuration = atom.readUnsignedInt(); sizes[i] = 0x7FFFFFFF & firstInt; offsets[i] = offset; // Calculate time and duration values such that any rounding errors are consistent. i.e. That // timesUs[i] + durationsUs[i] == timesUs[i + 1]. timesUs[i] = timeUs; time += referenceDuration; timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); durationsUs[i] = timeUs - timesUs[i]; atom.skipBytes(4); offset += sizes[i]; } return new ChunkIndex(sizes, offsets, durationsUs, timesUs); }
/** * @param chunkIndex The {@link ChunkIndex} to wrap. * @param uri The URI where the data is located. */ public DashWrappingSegmentIndex(ChunkIndex chunkIndex, String uri) { this.chunkIndex = chunkIndex; this.uri = uri; }
/** * Loads initialization and index data for the {@code representation} and returns the {@link * ChunkIndex}. * * @param dataSource The source from which the data should be loaded. * @param representation The representation which initialization chunk belongs to. * @return {@link ChunkIndex} of the given representation. * @throws IOException Thrown when there is an error while loading. * @throws InterruptedException Thrown if the thread was interrupted. */ public static ChunkIndex loadChunkIndex(DataSource dataSource, Representation representation) throws IOException, InterruptedException { ChunkExtractorWrapper extractorWrapper = loadInitializationData(dataSource, representation, true); return extractorWrapper == null ? null : (ChunkIndex) extractorWrapper.getSeekMap(); }
/** * Loads initialization and index data for the {@code representation} and returns the {@link * ChunkIndex}. * * @param dataSource The source from which the data should be loaded. * @param trackType The type of the representation. Typically one of the * {@link com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants. * @param representation The representation which initialization chunk belongs to. * @return The {@link ChunkIndex} of the given representation, or null if no initialization or * index data exists. * @throws IOException Thrown when there is an error while loading. * @throws InterruptedException Thrown if the thread was interrupted. */ public static ChunkIndex loadChunkIndex(DataSource dataSource, int trackType, Representation representation) throws IOException, InterruptedException { ChunkExtractorWrapper extractorWrapper = loadInitializationData(dataSource, trackType, representation, true); return extractorWrapper == null ? null : (ChunkIndex) extractorWrapper.getSeekMap(); }