@Override public void enable() { fatalError = null; formatEvaluator.enable(); if (manifestFetcher != null) { manifestFetcher.enable(); } DashSegmentIndex segmentIndex = representationHolders.get(formats[0].id).representation.getIndex(); if (segmentIndex == null) { seekRange = new TimeRange(TimeRange.TYPE_SNAPSHOT, 0, currentManifest.duration * 1000); notifySeekRangeChanged(seekRange); } else { long nowUs = getNowUs(); updateAvailableSegmentBounds(segmentIndex, nowUs); updateSeekRange(segmentIndex, nowUs); } }
private TimeRange getAvailableRange(long nowUnixTimeUs) { PeriodHolder firstPeriod = periodHolders.valueAt(0); PeriodHolder lastPeriod = periodHolders.valueAt(periodHolders.size() - 1); if (!currentManifest.dynamic || lastPeriod.isIndexExplicit()) { return new StaticTimeRange(firstPeriod.getAvailableStartTimeUs(), lastPeriod.getAvailableEndTimeUs()); } long minStartPositionUs = firstPeriod.getAvailableStartTimeUs(); long maxEndPositionUs = lastPeriod.isIndexUnbounded() ? Long.MAX_VALUE : lastPeriod.getAvailableEndTimeUs(); long elapsedRealtimeAtZeroUs = (systemClock.elapsedRealtime() * 1000) - (nowUnixTimeUs - (currentManifest.availabilityStartTime * 1000)); long timeShiftBufferDepthUs = currentManifest.timeShiftBufferDepth == -1 ? -1 : currentManifest.timeShiftBufferDepth * 1000; return new DynamicTimeRange(minStartPositionUs, maxEndPositionUs, elapsedRealtimeAtZeroUs, timeShiftBufferDepthUs, systemClock); }
private void updateSeekRange(DashSegmentIndex segmentIndex, long nowUs) { long earliestSeekPosition = segmentIndex.getTimeUs(firstAvailableSegmentNum); long latestSeekPosition = segmentIndex.getTimeUs(lastAvailableSegmentNum) + segmentIndex.getDurationUs(lastAvailableSegmentNum); if (currentManifest.dynamic) { long liveEdgeTimestampUs; if (segmentIndex.getLastSegmentNum() == DashSegmentIndex.INDEX_UNBOUNDED) { liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000; } else { liveEdgeTimestampUs = segmentIndex.getTimeUs(segmentIndex.getLastSegmentNum()) + segmentIndex.getDurationUs(segmentIndex.getLastSegmentNum()); if (!segmentIndex.isExplicit()) { // Some segments defined by the index may not be available yet. Bound the calculated live // edge based on the elapsed time since the manifest became available. liveEdgeTimestampUs = Math.min(liveEdgeTimestampUs, nowUs - currentManifest.availabilityStartTime * 1000); } } // it's possible that the live edge latency actually puts our latest position before // the earliest position in the case of a DVR-like stream that's just starting up, so // in that case just return the earliest position instead latestSeekPosition = Math.max(earliestSeekPosition, liveEdgeTimestampUs - liveEdgeLatencyUs); } TimeRange newSeekRange = new TimeRange(TimeRange.TYPE_SNAPSHOT, earliestSeekPosition, latestSeekPosition); if (seekRange == null || !seekRange.equals(newSeekRange)) { seekRange = newSeekRange; notifySeekRangeChanged(seekRange); } }
private void notifySeekRangeChanged(final TimeRange seekRange) { if (eventHandler != null && eventListener != null) { eventHandler.post(new Runnable() { @Override public void run() { eventListener.onSeekRangeChanged(seekRange); } }); } }
private void notifyAvailableRangeChanged(final TimeRange seekRange) { if (eventHandler != null && eventListener != null) { eventHandler.post(new Runnable() { @Override public void run() { eventListener.onAvailableRangeChanged(eventSourceId, seekRange); } }); } }
public void testGetAvailableRangeOnVod() { DashChunkSource chunkSource = new DashChunkSource(buildVodMpd(), DefaultDashTrackSelector.newVideoInstance(null, false, false), null, null); chunkSource.prepare(); chunkSource.enable(0); TimeRange availableRange = chunkSource.getAvailableRange(); checkAvailableRange(availableRange, 0, VOD_DURATION_MS * 1000); long[] seekRangeValuesMs = availableRange.getCurrentBoundsMs(null); assertEquals(0, seekRangeValuesMs[0]); assertEquals(VOD_DURATION_MS, seekRangeValuesMs[1]); }
public void testGetAvailableRangeOnMultiPeriodVod() { DashChunkSource chunkSource = new DashChunkSource(buildMultiPeriodVodMpd(), DefaultDashTrackSelector.newVideoInstance(null, false, false), null, null); chunkSource.prepare(); chunkSource.enable(0); TimeRange availableRange = chunkSource.getAvailableRange(); checkAvailableRange(availableRange, 0, MULTI_PERIOD_VOD_DURATION_MS * 1000); }
private static void checkLiveEdgeConsistency(DashChunkSource chunkSource, List<MediaChunk> queue, ChunkOperationHolder out, long seekPositionMs, long availableRangeStartMs, long availableRangeEndMs, long chunkStartTimeMs, long chunkEndTimeMs) { chunkSource.getChunkOperation(queue, seekPositionMs * 1000, out); TimeRange availableRange = chunkSource.getAvailableRange(); checkAvailableRange(availableRange, availableRangeStartMs * 1000, availableRangeEndMs * 1000); if (chunkStartTimeMs < availableRangeEndMs) { assertNotNull(out.chunk); assertEquals(chunkStartTimeMs * 1000, ((MediaChunk) out.chunk).startTimeUs); assertEquals(chunkEndTimeMs * 1000, ((MediaChunk) out.chunk).endTimeUs); } else { assertNull(out.chunk); } }
/** DashChunkSource.EventListener */ @Override public void onAvailableRangeChanged(int sourceId, TimeRange timeRange) { if (infoListener != null) { infoListener.onAvailableRangeChanged(timeRange); } }
public void testGetSeekRangeOnVod() { DashChunkSource chunkSource = new DashChunkSource(generateVodMpd(), AdaptationSet.TYPE_VIDEO, null, null, mock(FormatEvaluator.class)); chunkSource.enable(); TimeRange seekRange = chunkSource.getSeekRange(); checkSeekRange(seekRange, 0, VOD_DURATION_MS * 1000); long[] seekRangeValuesMs = seekRange.getCurrentBoundsMs(null); assertEquals(0, seekRangeValuesMs[0]); assertEquals(VOD_DURATION_MS, seekRangeValuesMs[1]); }
private void checkLiveEdgeLatency(DashChunkSource chunkSource, List<MediaChunk> queue, ChunkOperationHolder out, long seekPositionMs, long seekRangeStartMs, long seekRangeEndMs, long chunkStartTimeMs, long chunkEndTimeMs) { chunkSource.getChunkOperation(queue, seekPositionMs * 1000, 0, out); TimeRange seekRange = chunkSource.getSeekRange(); assertNotNull(out.chunk); checkSeekRange(seekRange, seekRangeStartMs * 1000, seekRangeEndMs * 1000); assertEquals(chunkStartTimeMs * 1000, ((MediaChunk) out.chunk).startTimeUs); assertEquals(chunkEndTimeMs * 1000, ((MediaChunk) out.chunk).endTimeUs); }
@Override public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) { if (infoListener != null) { infoListener.onAvailableRangeChanged(sourceId, availableRange); } }
@Override public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) { availableRangeValuesUs = availableRange.getCurrentBoundsUs(availableRangeValuesUs); Log.d(TAG, "availableRange [" + availableRange.isStatic() + ", " + availableRangeValuesUs[0] + ", " + availableRangeValuesUs[1] + "]"); }
@Override public void onAvailableRangeChanged(TimeRange availableRange) { availableRangeValuesUs = availableRange.getCurrentBoundsUs(availableRangeValuesUs); Log.d(TAG, "availableRange [" + availableRange.isStatic() + ", " + availableRangeValuesUs[0] + ", " + availableRangeValuesUs[1] + "]"); }
@Override public void onAvailableRangeChanged(TimeRange availableRange) { if (infoListener != null) { infoListener.onAvailableRangeChanged(availableRange); } }
TimeRange getSeekRange() { return seekRange; }
@Override public void onSeekRangeChanged(TimeRange seekRange) { if (infoListener != null) { infoListener.onSeekRangeChanged(seekRange); } }
TimeRange getAvailableRange() { return availableRange; }
private void processManifest(MediaPresentationDescription manifest) { // Remove old periods. Period firstPeriod = manifest.getPeriod(0); while (periodHolders.size() > 0 && periodHolders.valueAt(0).startTimeUs < firstPeriod.startMs * 1000) { PeriodHolder periodHolder = periodHolders.valueAt(0); // TODO: Use periodHolders.removeAt(0) if the minimum API level is ever increased to 11. periodHolders.remove(periodHolder.localIndex); } // After discarding old periods, we should never have more periods than listed in the new // manifest. That would mean that a previously announced period is no longer advertised. If // this condition occurs, assume that we are hitting a manifest server that is out of sync and // behind, discard this manifest, and try again later. if (periodHolders.size() > manifest.getPeriodCount()) { return; } // Update existing periods. Only the first and last periods can change. try { int periodHolderCount = periodHolders.size(); if (periodHolderCount > 0) { periodHolders.valueAt(0).updatePeriod(manifest, 0, enabledTrack); if (periodHolderCount > 1) { int lastIndex = periodHolderCount - 1; periodHolders.valueAt(lastIndex).updatePeriod(manifest, lastIndex, enabledTrack); } } } catch (BehindLiveWindowException e) { fatalError = e; return; } // Add new periods. for (int i = periodHolders.size(); i < manifest.getPeriodCount(); i++) { PeriodHolder holder = new PeriodHolder(nextPeriodHolderIndex, manifest, i, enabledTrack); periodHolders.put(nextPeriodHolderIndex, holder); nextPeriodHolderIndex++; } // Update the available range. TimeRange newAvailableRange = getAvailableRange(getNowUnixTimeUs()); if (availableRange == null || !availableRange.equals(newAvailableRange)) { availableRange = newAvailableRange; notifyAvailableRangeChanged(availableRange); } currentManifest = manifest; }
public void testGetAvailableRangeOnLiveWithTimeline() { MediaPresentationDescription mpd = buildLiveMpdWithTimeline(LIVE_DURATION_MS, 0); DashChunkSource chunkSource = buildDashChunkSource(mpd); TimeRange availableRange = chunkSource.getAvailableRange(); checkAvailableRange(availableRange, 0, LIVE_DURATION_MS * 1000); }
public void testGetSeekRangeOnMultiPeriodLiveWithTimeline() { MediaPresentationDescription mpd = buildMultiPeriodLiveMpdWithTimeline(); DashChunkSource chunkSource = buildDashChunkSource(mpd); TimeRange availableRange = chunkSource.getAvailableRange(); checkAvailableRange(availableRange, 0, MULTI_PERIOD_LIVE_DURATION_MS * 1000); }
private static void checkAvailableRange(TimeRange seekRange, long startTimeUs, long endTimeUs) { long[] seekRangeValuesUs = seekRange.getCurrentBoundsUs(null); assertEquals(startTimeUs, seekRangeValuesUs[0]); assertEquals(endTimeUs, seekRangeValuesUs[1]); }
@Override public void onSeekRangeChanged(TimeRange seekRange) { seekRangeValuesUs = seekRange.getCurrentBoundsUs(seekRangeValuesUs); Log.d(TAG, "seekRange [ " + seekRange.type + ", " + seekRangeValuesUs[0] + ", " + seekRangeValuesUs[1] + "]"); }