private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, int trigger) { RangedUri requestUri; if (initializationUri != null) { // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. requestUri = initializationUri.attemptMerge(indexUri); if (requestUri == null) { requestUri = initializationUri; } } else { requestUri = indexUri; } DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, representation.getCacheKey()); return new InitializationChunk(dataSource, dataSpec, trigger, representation.format, extractor); }
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, int manifestIndex, int trigger) { RangedUri requestUri; if (initializationUri != null) { // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. requestUri = initializationUri.attemptMerge(indexUri); if (requestUri == null) { requestUri = initializationUri; } } else { requestUri = indexUri; } DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, representation.getCacheKey()); return new InitializationChunk(dataSource, dataSpec, trigger, representation.format, extractor, manifestIndex); }
protected Chunk newMediaChunk( PeriodHolder periodHolder, RepresentationHolder representationHolder, DataSource dataSource, MediaFormat mediaFormat, ExposedTrack enabledTrack, int segmentNum, int trigger) { Representation representation = representationHolder.representation; Format format = representation.format; long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum); long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum); RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs; if (mimeTypeIsRawText(format.mimeType)) { return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, format, startTimeUs, endTimeUs, segmentNum, enabledTrack.trackFormat, null, periodHolder.localIndex); } else { boolean isMediaFormatFinal = (mediaFormat != null); return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, mediaFormat, enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight, periodHolder.drmInitData, isMediaFormatFinal, periodHolder.localIndex); } }
private void updateRepresentationIndependentProperties(long periodDurationUs, Representation arbitaryRepresentation) { DashSegmentIndex segmentIndex = arbitaryRepresentation.getIndex(); if (segmentIndex != null) { int firstSegmentNum = segmentIndex.getFirstSegmentNum(); int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs); indexIsUnbounded = lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED; indexIsExplicit = segmentIndex.isExplicit(); availableStartTimeUs = startTimeUs + segmentIndex.getTimeUs(firstSegmentNum); if (!indexIsUnbounded) { availableEndTimeUs = startTimeUs + segmentIndex.getTimeUs(lastSegmentNum) + segmentIndex.getDurationUs(lastSegmentNum, periodDurationUs); } } else { indexIsUnbounded = false; indexIsExplicit = true; availableStartTimeUs = startTimeUs; availableEndTimeUs = startTimeUs + periodDurationUs; } }
private static Representation buildSegmentTimelineRepresentation(long timelineDurationMs, long timelineStartTimeMs) { List<SegmentTimelineElement> segmentTimeline = new ArrayList<>(); List<RangedUri> mediaSegments = new ArrayList<>(); long segmentStartTimeMs = timelineStartTimeMs; long byteStart = 0; // Create all but the last segment with LIVE_SEGMENT_DURATION_MS. int segmentCount = (int) Util.ceilDivide(timelineDurationMs, LIVE_SEGMENT_DURATION_MS); for (int i = 0; i < segmentCount - 1; i++) { segmentTimeline.add(new SegmentTimelineElement(segmentStartTimeMs, LIVE_SEGMENT_DURATION_MS)); mediaSegments.add(new RangedUri("", "", byteStart, 500L)); segmentStartTimeMs += LIVE_SEGMENT_DURATION_MS; byteStart += 500; } // The final segment duration is calculated so that the total duration is timelineDurationMs. long finalSegmentDurationMs = (timelineStartTimeMs + timelineDurationMs) - segmentStartTimeMs; segmentTimeline.add(new SegmentTimelineElement(segmentStartTimeMs, finalSegmentDurationMs)); mediaSegments.add(new RangedUri("", "", byteStart, 500L)); segmentStartTimeMs += finalSegmentDurationMs; byteStart += 500; // Construct the list. MultiSegmentBase segmentBase = new SegmentList(null, 1000, 0, 0, 0, segmentTimeline, mediaSegments); return Representation.newInstance(null, 0, REGULAR_VIDEO, segmentBase); }
public void testMaxVideoDimensionsLegacy() { SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4"); Representation representation1 = Representation.newInstance(0, 0, null, 0, TALL_VIDEO, segmentBase1); SingleSegmentBase segmentBase2 = new SingleSegmentBase("https://example.com/2.mp4"); Representation representation2 = Representation.newInstance(0, 0, null, 0, WIDE_VIDEO, segmentBase2); DashChunkSource chunkSource = new DashChunkSource(null, null, representation1, representation2); MediaFormat out = MediaFormat.createVideoFormat("video/h264", 1, 1, 1, 1, null); chunkSource.getMaxVideoDimensions(out); assertEquals(WIDE_WIDTH, out.getMaxVideoWidth()); assertEquals(TALL_HEIGHT, out.getMaxVideoHeight()); }
private static Representation generateSegmentTimelineRepresentation(long segmentStartMs, long periodStartMs, long duration) { List<SegmentTimelineElement> segmentTimeline = new ArrayList<>(); List<RangedUri> mediaSegments = new ArrayList<>(); long segmentStartTimeMs = segmentStartMs; long byteStart = 0; for (int i = 0; i < (duration / LIVE_SEGMENT_DURATION_MS); i++) { segmentTimeline.add(new SegmentTimelineElement(segmentStartTimeMs, LIVE_SEGMENT_DURATION_MS)); mediaSegments.add(new RangedUri("", "", byteStart, 500L)); segmentStartTimeMs += LIVE_SEGMENT_DURATION_MS; byteStart += 500; } int startNumber = (int) ((periodStartMs + segmentStartMs) / LIVE_SEGMENT_DURATION_MS); MultiSegmentBase segmentBase = new SegmentList(null, 1000, 0, TrackRenderer.UNKNOWN_TIME_US, startNumber, TrackRenderer.UNKNOWN_TIME_US, segmentTimeline, mediaSegments); return Representation.newInstance(periodStartMs, TrackRenderer.UNKNOWN_TIME_US, null, 0, REGULAR_VIDEO, segmentBase); }
private void loadDrmInitData(Representation representation) throws IOException { Uri initFile = representation.getInitializationUri().getUri(); FileDataSource initChunkSource = new FileDataSource(); DataSpec initDataSpec = new DataSpec(initFile); int trigger = 2; ChunkExtractorWrapper extractorWrapper = new ChunkExtractorWrapper(new FragmentedMp4Extractor()); InitializationChunk chunk = new InitializationChunk(initChunkSource, initDataSpec, trigger, format, extractorWrapper); try { chunk.load(); } catch (InterruptedException e) { Log.d(TAG, "Interrupted!", e); } if (!chunk.isLoadCanceled()) { drmInitData = chunk.getDrmInitData(); } if (drmInitData != null) { DrmInitData.SchemeInitData schemeInitData = OfflineDrmManager.getWidevineInitData(drmInitData); if (schemeInitData != null) { widevineInitData = schemeInitData.data; } } }
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource, int segmentNum, int trigger) { Representation representation = representationHolder.representation; DashSegmentIndex segmentIndex = representationHolder.segmentIndex; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = startTimeUs + segmentIndex.getDurationUs(segmentNum); int absoluteSegmentNum = segmentNum + representationHolder.segmentNumShift; boolean isLastSegment = !currentManifest.dynamic && segmentNum == segmentIndex.getLastSegmentNum(); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); long sampleOffsetUs = representation.periodStartMs * 1000 - representation.presentationTimeOffsetUs; if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) { if (representationHolder.vttHeaderOffsetUs != sampleOffsetUs) { // Update the VTT header. headerBuilder.setLength(0); headerBuilder.append(C.WEBVTT_EXO_HEADER).append("=") .append(C.WEBVTT_EXO_HEADER_OFFSET).append(sampleOffsetUs) .append("\n"); representationHolder.vttHeader = headerBuilder.toString().getBytes(); representationHolder.vttHeaderOffsetUs = sampleOffsetUs; } return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, MediaFormat.createTextFormat(MimeTypes.TEXT_VTT), null, representationHolder.vttHeader); } else { return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, sampleOffsetUs, representationHolder.extractorWrapper, representationHolder.format, drmInitData, true); } }
private static MediaPresentationDescription buildManifest(List<Representation> representations) { Representation firstRepresentation = representations.get(0); AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN, representations); Period period = new Period(null, firstRepresentation.periodStartMs, firstRepresentation.periodDurationMs, Collections.singletonList(adaptationSet)); long duration = firstRepresentation.periodDurationMs - firstRepresentation.periodStartMs; return new MediaPresentationDescription(-1, duration, -1, false, -1, -1, null, null, Collections.singletonList(period)); }
private static int[] getRepresentationIndices(AdaptationSet adaptationSet, String[] representationIds, boolean canIncludeAdditionalVideoRepresentations) throws IOException { List<Representation> availableRepresentations = adaptationSet.representations; List<Integer> selectedRepresentationIndices = new ArrayList<>(); // Always select explicitly listed representations, failing if they're missing. for (int i = 0; i < representationIds.length; i++) { String representationId = representationIds[i]; boolean foundIndex = false; for (int j = 0; j < availableRepresentations.size() && !foundIndex; j++) { if (availableRepresentations.get(j).format.id.equals(representationId)) { selectedRepresentationIndices.add(j); foundIndex = true; } } if (!foundIndex) { throw new IllegalStateException("Representation " + representationId + " not found."); } } // Select additional video representations, if supported by the device. if (canIncludeAdditionalVideoRepresentations) { int[] supportedVideoRepresentationIndices = VideoFormatSelectorUtil.selectVideoFormats( availableRepresentations, null, false, true, -1, -1); for (int i = 0; i < supportedVideoRepresentationIndices.length; i++) { int representationIndex = supportedVideoRepresentationIndices[i]; if (!selectedRepresentationIndices.contains(representationIndex)) { Log.d(TAG, "Adding video format: " + availableRepresentations.get(i).format.id); selectedRepresentationIndices.add(representationIndex); } } } return Util.toArray(selectedRepresentationIndices); }
private static MediaPresentationDescription buildManifest(long durationMs, int adaptationSetType, List<Representation> representations) { AdaptationSet adaptationSet = new AdaptationSet(0, adaptationSetType, representations); Period period = new Period(null, 0, Collections.singletonList(adaptationSet)); return new MediaPresentationDescription(-1, durationMs, -1, false, -1, -1, null, null, Collections.singletonList(period)); }
public RepresentationHolder(long periodStartTimeUs, long periodDurationUs, Representation representation) { this.periodStartTimeUs = periodStartTimeUs; this.periodDurationUs = periodDurationUs; this.representation = representation; String mimeType = representation.format.mimeType; mimeTypeIsRawText = mimeTypeIsRawText(mimeType); extractorWrapper = mimeTypeIsRawText ? null : new ChunkExtractorWrapper( mimeTypeIsWebm(mimeType) ? new WebmExtractor() : new FragmentedMp4Extractor()); segmentIndex = representation.getIndex(); }
public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation) throws BehindLiveWindowException{ DashSegmentIndex oldIndex = representation.getIndex(); DashSegmentIndex newIndex = newRepresentation.getIndex(); periodDurationUs = newPeriodDurationUs; representation = newRepresentation; if (oldIndex == null) { // Segment numbers cannot shift if the index isn't defined by the manifest. return; } segmentIndex = newIndex; if (!oldIndex.isExplicit()) { // Segment numbers cannot shift if the index isn't explicit. return; } int oldIndexLastSegmentNum = oldIndex.getLastSegmentNum(periodDurationUs); long oldIndexEndTimeUs = oldIndex.getTimeUs(oldIndexLastSegmentNum) + oldIndex.getDurationUs(oldIndexLastSegmentNum, periodDurationUs); int newIndexFirstSegmentNum = newIndex.getFirstSegmentNum(); long newIndexStartTimeUs = newIndex.getTimeUs(newIndexFirstSegmentNum); if (oldIndexEndTimeUs == newIndexStartTimeUs) { // The new index continues where the old one ended, with no overlap. segmentNumShift += oldIndex.getLastSegmentNum(periodDurationUs) + 1 - newIndexFirstSegmentNum; } else if (oldIndexEndTimeUs < newIndexStartTimeUs) { // There's a gap between the old index and the new one which means we've slipped behind the // live window and can't proceed. throw new BehindLiveWindowException(); } else { // The new index overlaps with the old one. segmentNumShift += oldIndex.getSegmentNum(newIndexStartTimeUs, periodDurationUs) - newIndexFirstSegmentNum; } }
public PeriodHolder(int localIndex, MediaPresentationDescription manifest, int manifestIndex, ExposedTrack selectedTrack) { this.localIndex = localIndex; Period period = manifest.getPeriod(manifestIndex); long periodDurationUs = getPeriodDurationUs(manifest, manifestIndex); AdaptationSet adaptationSet = period.adaptationSets.get(selectedTrack.adaptationSetIndex); List<Representation> representations = adaptationSet.representations; startTimeUs = period.startMs * 1000; drmInitData = getDrmInitData(adaptationSet); if (!selectedTrack.isAdaptive()) { representationIndices = new int[] { getRepresentationIndex(representations, selectedTrack.fixedFormat.id)}; } else { representationIndices = new int[selectedTrack.adaptiveFormats.length]; for (int j = 0; j < selectedTrack.adaptiveFormats.length; j++) { representationIndices[j] = getRepresentationIndex( representations, selectedTrack.adaptiveFormats[j].id); } } representationHolders = new HashMap<>(); for (int i = 0; i < representationIndices.length; i++) { Representation representation = representations.get(representationIndices[i]); RepresentationHolder representationHolder = new RepresentationHolder(startTimeUs, periodDurationUs, representation); representationHolders.put(representation.format.id, representationHolder); } updateRepresentationIndependentProperties(periodDurationUs, representations.get(representationIndices[0])); }
public void updatePeriod(MediaPresentationDescription manifest, int manifestIndex, ExposedTrack selectedTrack) throws BehindLiveWindowException { Period period = manifest.getPeriod(manifestIndex); long periodDurationUs = getPeriodDurationUs(manifest, manifestIndex); List<Representation> representations = period.adaptationSets .get(selectedTrack.adaptationSetIndex).representations; for (int j = 0; j < representationIndices.length; j++) { Representation representation = representations.get(representationIndices[j]); representationHolders.get(representation.format.id).updateRepresentation(periodDurationUs, representation); } updateRepresentationIndependentProperties(periodDurationUs, representations.get(representationIndices[0])); }
private static int getRepresentationIndex(List<Representation> representations, String formatId) { for (int i = 0; i < representations.size(); i++) { Representation representation = representations.get(i); if (formatId.equals(representation.format.id)) { return i; } } throw new IllegalStateException("Missing format id: " + formatId); }
private static Representation buildSegmentTemplateRepresentation() { UrlTemplate initializationTemplate = null; UrlTemplate mediaTemplate = UrlTemplate.compile("$RepresentationID$/$Number$"); MultiSegmentBase segmentBase = new SegmentTemplate(null, 1000, 0, 0, LIVE_SEGMENT_DURATION_MS, null, initializationTemplate, mediaTemplate, "http://www.youtube.com"); return Representation.newInstance(null, 0, REGULAR_VIDEO, segmentBase); }
private static MediaPresentationDescription buildMpd(long durationMs, List<Representation> representations, boolean live, boolean limitTimeshiftBuffer) { AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_VIDEO, representations); Period period = new Period(null, 0, Collections.singletonList(adaptationSet)); return new MediaPresentationDescription(AVAILABILITY_START_TIME_MS, durationMs, -1, live, -1, (limitTimeshiftBuffer) ? LIVE_TIMESHIFT_BUFFER_DEPTH_MS : -1, null, null, Collections.singletonList(period)); }
private static MediaPresentationDescription buildMultiPeriodVodMpd() { List<Period> periods = new ArrayList<>(); long timeMs = 0; long periodDurationMs = VOD_DURATION_MS; for (int i = 0; i < 2; i++) { Representation representation = buildVodRepresentation(REGULAR_VIDEO); AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_VIDEO, Collections.singletonList(representation)); Period period = new Period(null, timeMs, Collections.singletonList(adaptationSet)); periods.add(period); timeMs += periodDurationMs; } return buildMultiPeriodMpd(timeMs, periods, false, false); }
private static MediaPresentationDescription buildMultiPeriodLiveMpdWithTimeline() { List<Period> periods = new ArrayList<>(); long periodStartTimeMs = 0; long periodDurationMs = LIVE_DURATION_MS; for (int i = 0; i < MULTI_PERIOD_COUNT; i++) { Representation representation = buildSegmentTimelineRepresentation(LIVE_DURATION_MS, 0); AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_VIDEO, Collections.singletonList(representation)); Period period = new Period(null, periodStartTimeMs, Collections.singletonList(adaptationSet)); periods.add(period); periodStartTimeMs += periodDurationMs; } return buildMultiPeriodMpd(periodDurationMs, periods, true, false); }
private static MediaPresentationDescription buildMultiPeriodLiveMpdWithTemplate() { List<Period> periods = new ArrayList<>(); long periodStartTimeMs = 0; long periodDurationMs = LIVE_DURATION_MS; for (int i = 0; i < MULTI_PERIOD_COUNT; i++) { Representation representation = buildSegmentTemplateRepresentation(); AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_VIDEO, Collections.singletonList(representation)); Period period = new Period(null, periodStartTimeMs, Collections.singletonList(adaptationSet)); periods.add(period); periodStartTimeMs += periodDurationMs; } return buildMultiPeriodMpd(MULTI_PERIOD_LIVE_DURATION_MS, periods, true, false); }
private DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher, MediaPresentationDescription initialManifest, int adaptationSetIndex, int[] representationIndices, DataSource dataSource, FormatEvaluator formatEvaluator, long liveEdgeLatencyUs) { this.manifestFetcher = manifestFetcher; this.currentManifest = initialManifest; this.adaptationSetIndex = adaptationSetIndex; this.representationIndices = representationIndices; this.dataSource = dataSource; this.evaluator = formatEvaluator; this.liveEdgeLatencyUs = liveEdgeLatencyUs; this.evaluation = new Evaluation(); this.headerBuilder = new StringBuilder(); psshInfo = getPsshInfo(currentManifest, adaptationSetIndex); Representation[] representations = getFilteredRepresentations(currentManifest, adaptationSetIndex, representationIndices); long periodDurationUs = (representations[0].periodDurationMs == TrackRenderer.UNKNOWN_TIME_US) ? TrackRenderer.UNKNOWN_TIME_US : representations[0].periodDurationMs * 1000; this.trackInfo = new TrackInfo(representations[0].format.mimeType, periodDurationUs); this.formats = new Format[representations.length]; this.representationHolders = new HashMap<String, RepresentationHolder>(); int maxWidth = 0; int maxHeight = 0; for (int i = 0; i < representations.length; i++) { formats[i] = representations[i].format; maxWidth = Math.max(formats[i].width, maxWidth); maxHeight = Math.max(formats[i].height, maxHeight); Extractor extractor = mimeTypeIsWebm(formats[i].mimeType) ? new WebmExtractor() : new FragmentedMp4Extractor(); representationHolders.put(formats[i].id, new RepresentationHolder(representations[i], extractor)); } this.maxWidth = maxWidth; this.maxHeight = maxHeight; Arrays.sort(formats, new DecreasingBandwidthComparator()); }
@Override public void continueBuffering(long playbackPositionUs) { if (manifestFetcher == null || !currentManifest.dynamic || fatalError != null) { return; } MediaPresentationDescription newManifest = manifestFetcher.getManifest(); if (currentManifest != newManifest && newManifest != null) { Representation[] newRepresentations = DashChunkSource.getFilteredRepresentations(newManifest, adaptationSetIndex, representationIndices); for (Representation representation : newRepresentations) { RepresentationHolder representationHolder = representationHolders.get(representation.format.id); DashSegmentIndex oldIndex = representationHolder.segmentIndex; DashSegmentIndex newIndex = representation.getIndex(); int newFirstSegmentNum = newIndex.getFirstSegmentNum(); int segmentNumShift = oldIndex.getSegmentNum(newIndex.getTimeUs(newFirstSegmentNum)) - newFirstSegmentNum; representationHolder.segmentNumShift += segmentNumShift; representationHolder.segmentIndex = newIndex; } currentManifest = newManifest; finishedCurrentManifest = false; } // TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where // minUpdatePeriod is set to 0. In such cases we shouldn't refresh unless there is explicit // signaling in the stream, according to: // http://azure.microsoft.com/blog/2014/09/13/dash-live-streaming-with-azure-media-service/ long minUpdatePeriod = currentManifest.minUpdatePeriod; if (minUpdatePeriod == 0) { minUpdatePeriod = 5000; } if (finishedCurrentManifest && (SystemClock.elapsedRealtime() > manifestFetcher.getManifestLoadTimestamp() + minUpdatePeriod)) { manifestFetcher.requestRefresh(); } }
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, Representation representation, Extractor extractor, DataSource dataSource, int trigger) { int expectedExtractorResult = Extractor.RESULT_END_OF_STREAM; long indexAnchor = 0; RangedUri requestUri; if (initializationUri != null) { // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. expectedExtractorResult |= Extractor.RESULT_READ_INIT; requestUri = initializationUri.attemptMerge(indexUri); if (requestUri != null) { expectedExtractorResult |= Extractor.RESULT_READ_INDEX; if (extractor.hasRelativeIndexOffsets()) { indexAnchor = indexUri.start + indexUri.length; } } else { requestUri = initializationUri; } } else { requestUri = indexUri; if (extractor.hasRelativeIndexOffsets()) { indexAnchor = indexUri.start + indexUri.length; } expectedExtractorResult |= Extractor.RESULT_READ_INDEX; } DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, representation.getCacheKey()); return new InitializationLoadable(dataSource, dataSpec, trigger, representation.format, extractor, expectedExtractorResult, indexAnchor); }
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource, int segmentNum, int trigger) { Representation representation = representationHolder.representation; DashSegmentIndex segmentIndex = representationHolder.segmentIndex; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = startTimeUs + segmentIndex.getDurationUs(segmentNum); boolean isLastSegment = !currentManifest.dynamic && segmentNum == segmentIndex.getLastSegmentNum(); int nextAbsoluteSegmentNum = isLastSegment ? -1 : (representationHolder.segmentNumShift + segmentNum + 1); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); long presentationTimeOffsetUs = representation.presentationTimeOffsetMs * 1000; if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) { if (representationHolder.vttHeaderOffsetUs != presentationTimeOffsetUs) { // Update the VTT header. headerBuilder.setLength(0); headerBuilder.append(WebvttParser.EXO_HEADER).append("=") .append(WebvttParser.OFFSET).append(presentationTimeOffsetUs).append("\n"); representationHolder.vttHeader = headerBuilder.toString().getBytes(); representationHolder.vttHeaderOffsetUs = presentationTimeOffsetUs; } return new SingleSampleMediaChunk(dataSource, dataSpec, representation.format, 0, startTimeUs, endTimeUs, nextAbsoluteSegmentNum, null, representationHolder.vttHeader); } else { return new Mp4MediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs, endTimeUs, nextAbsoluteSegmentNum, representationHolder.extractor, psshInfo, false, presentationTimeOffsetUs); } }
private static MediaPresentationDescription buildManifest(List<Representation> representations) { Representation firstRepresentation = representations.get(0); AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN, representations); Period period = new Period(null, firstRepresentation.periodStartMs, firstRepresentation.periodDurationMs, Collections.singletonList(adaptationSet)); long duration = firstRepresentation.periodDurationMs - firstRepresentation.periodStartMs; return new MediaPresentationDescription(-1, duration, -1, false, -1, -1, null, Collections.singletonList(period)); }
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource, int segmentNum, int trigger) { Representation representation = representationHolder.representation; DashSegmentIndex segmentIndex = representationHolder.segmentIndex; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = startTimeUs + segmentIndex.getDurationUs(segmentNum); boolean isLastSegment = !currentManifest.dynamic && segmentNum == segmentIndex.getLastSegmentNum(); int nextAbsoluteSegmentNum = isLastSegment ? -1 : (representationHolder.segmentNumShift + segmentNum + 1); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); long presentationTimeOffsetUs = representation.presentationTimeOffsetUs; if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) { if (representationHolder.vttHeaderOffsetUs != presentationTimeOffsetUs) { // Update the VTT header. headerBuilder.setLength(0); headerBuilder.append(WebvttParser.EXO_HEADER).append("=") .append(WebvttParser.OFFSET).append(presentationTimeOffsetUs).append("\n"); representationHolder.vttHeader = headerBuilder.toString().getBytes(); representationHolder.vttHeaderOffsetUs = presentationTimeOffsetUs; } return new SingleSampleMediaChunk(dataSource, dataSpec, representation.format, 0, startTimeUs, endTimeUs, nextAbsoluteSegmentNum, null, representationHolder.vttHeader); } else { return new ContainerMediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs, endTimeUs, nextAbsoluteSegmentNum, representationHolder.extractor, psshInfo, false, presentationTimeOffsetUs); } }
/** * @param dataSource A {@link DataSource} suitable for loading the media data. * @param evaluator Selects from the available formats. * @param representations The representations to be considered by the source. */ public DashChunkSource(DataSource dataSource, FormatEvaluator evaluator, Representation... representations) { this.dataSource = dataSource; this.evaluator = evaluator; this.formats = new Format[representations.length]; this.extractors = new HashMap<String, Extractor>(); this.segmentIndexes = new HashMap<String, DashSegmentIndex>(); this.representations = new HashMap<String, Representation>(); this.trackInfo = new TrackInfo(representations[0].format.mimeType, representations[0].periodDurationMs * 1000); this.evaluation = new Evaluation(); int maxWidth = 0; int maxHeight = 0; for (int i = 0; i < representations.length; i++) { formats[i] = representations[i].format; maxWidth = Math.max(formats[i].width, maxWidth); maxHeight = Math.max(formats[i].height, maxHeight); Extractor extractor = formats[i].mimeType.startsWith(MimeTypes.VIDEO_WEBM) ? new WebmExtractor() : new FragmentedMp4Extractor(); extractors.put(formats[i].id, extractor); this.representations.put(formats[i].id, representations[i]); DashSegmentIndex segmentIndex = representations[i].getIndex(); if (segmentIndex != null) { segmentIndexes.put(formats[i].id, segmentIndex); } } this.maxWidth = maxWidth; this.maxHeight = maxHeight; Arrays.sort(formats, new DecreasingBandwidthComparator()); }
private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex, Extractor extractor, DataSource dataSource, int segmentNum, int trigger) { int lastSegmentNum = segmentIndex.getLastSegmentNum(); int nextSegmentNum = segmentNum == lastSegmentNum ? -1 : segmentNum + 1; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = segmentNum < lastSegmentNum ? segmentIndex.getTimeUs(segmentNum + 1) : startTimeUs + segmentIndex.getDurationUs(segmentNum); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); return new Mp4MediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs, endTimeUs, nextSegmentNum, extractor, false, 0); }
private static MediaPresentationDescription generateMpd(boolean live, List<Representation> representations, boolean limitTimeshiftBuffer) { Representation firstRepresentation = representations.get(0); AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN, representations); Period period = new Period(null, firstRepresentation.periodStartMs, firstRepresentation.periodDurationMs, Collections.singletonList(adaptationSet)); long duration = (live) ? TrackRenderer.UNKNOWN_TIME_US : firstRepresentation.periodDurationMs - firstRepresentation.periodStartMs; return new MediaPresentationDescription(AVAILABILITY_START_TIME_MS, duration, -1, live, -1, (limitTimeshiftBuffer) ? LIVE_TIMESHIFT_BUFFER_DEPTH_MS : -1, null, null, Collections.singletonList(period)); }
private static MediaPresentationDescription generateVodMpd() { List<Representation> representations = new ArrayList<>(); representations.add(generateVodRepresentation(0, VOD_DURATION_MS, TALL_VIDEO)); representations.add(generateVodRepresentation(0, VOD_DURATION_MS, WIDE_VIDEO)); return generateMpd(false, representations, false); }