/** * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. * @param manifest The initial manifest. * @param periodIndex The index of the period in the manifest. * @param adaptationSetIndex The index of the adaptation set in the period. * @param trackSelection The track selection. * @param dataSource A {@link DataSource} suitable for loading the media data. * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified * as the server's unix time minus the local elapsed time. If unknown, set to 0. * @param maxSegmentsPerLoad The maximum number of segments to combine into a single request. * Note that segments will only be combined if their {@link Uri}s are the same and if their * data ranges are adjacent. * @param enableEventMessageTrack Whether the chunks generated by the source may output an event * message track. * @param enableEventMessageTrack Whether the chunks generated by the source may output a CEA-608 * track. */ public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection, DataSource dataSource, long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad, boolean enableEventMessageTrack, boolean enableCea608Track) { this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifest = manifest; this.adaptationSetIndex = adaptationSetIndex; this.trackSelection = trackSelection; this.dataSource = dataSource; this.periodIndex = periodIndex; this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs; this.maxSegmentsPerLoad = maxSegmentsPerLoad; long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); AdaptationSet adaptationSet = getAdaptationSet(); List<Representation> representations = adaptationSet.representations; representationHolders = new RepresentationHolder[trackSelection.length()]; for (int i = 0; i < representationHolders.length; i++) { Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i)); representationHolders[i] = new RepresentationHolder(periodDurationUs, representation, enableEventMessageTrack, enableCea608Track, adaptationSet.type); } }
private ChunkSampleStream<DashChunkSource> buildSampleStream(int adaptationSetIndex, TrackSelection selection, long positionUs) { AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex); int embeddedTrackCount = 0; int[] embeddedTrackTypes = new int[2]; boolean enableEventMessageTrack = hasEventMessageTrack(adaptationSet); if (enableEventMessageTrack) { embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_METADATA; } boolean enableCea608Track = hasCea608Track(adaptationSet); if (enableCea608Track) { embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_TEXT; } if (embeddedTrackCount < embeddedTrackTypes.length) { embeddedTrackTypes = Arrays.copyOf(embeddedTrackTypes, embeddedTrackCount); } DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( manifestLoaderErrorThrower, manifest, periodIndex, adaptationSetIndex, selection, elapsedRealtimeOffset, enableEventMessageTrack, enableCea608Track); ChunkSampleStream<DashChunkSource> stream = new ChunkSampleStream<>(adaptationSet.type, embeddedTrackTypes, chunkSource, this, allocator, positionUs, minLoadableRetryCount, eventDispatcher); return stream; }
@Override protected List<Segment> getAllSegments(DataSource dataSource, DashManifest manifest, boolean allowIndexLoadErrors) throws InterruptedException, IOException { ArrayList<Segment> segments = new ArrayList<>(); for (int periodIndex = 0; periodIndex < manifest.getPeriodCount(); periodIndex++) { List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; for (int adaptationIndex = 0; adaptationIndex < adaptationSets.size(); adaptationIndex++) { AdaptationSet adaptationSet = adaptationSets.get(adaptationIndex); RepresentationKey[] keys = new RepresentationKey[adaptationSet.representations.size()]; for (int i = 0; i < keys.length; i++) { keys[i] = new RepresentationKey(periodIndex, adaptationIndex, i); } segments.addAll(getSegments(dataSource, manifest, keys, allowIndexLoadErrors)); } } return segments; }
/** * Downloads an offline license. * * @param dataSource The {@link HttpDataSource} to be used for download. * @param dashManifest The {@link DashManifest} of the DASH content. * @return The downloaded offline license key set id. * @throws IOException If an error occurs reading data from the stream. * @throws InterruptedException If the thread has been interrupted. * @throws DrmSessionException Thrown when there is an error during DRM session. */ public byte[] download(HttpDataSource dataSource, DashManifest dashManifest) throws IOException, InterruptedException, DrmSessionException { // Get DrmInitData // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream, // as per DASH IF Interoperability Recommendations V3.0, 7.5.3. if (dashManifest.getPeriodCount() < 1) { return null; } Period period = dashManifest.getPeriod(0); int adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_VIDEO); if (adaptationSetIndex == C.INDEX_UNSET) { adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_AUDIO); if (adaptationSetIndex == C.INDEX_UNSET) { return null; } } AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex); if (adaptationSet.representations.isEmpty()) { return null; } Representation representation = adaptationSet.representations.get(0); DrmInitData drmInitData = representation.format.drmInitData; if (drmInitData == null) { Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation); if (sampleFormat != null) { drmInitData = sampleFormat.drmInitData; } if (drmInitData == null) { return null; } } blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, null, drmInitData); return drmSessionManager.getOfflineLicenseKeySetId(); }
private static int getEmbeddedTrackCount(List<AdaptationSet> adaptationSets) { int embeddedTrackCount = 0; for (int i = 0; i < adaptationSets.size(); i++) { AdaptationSet adaptationSet = adaptationSets.get(i); if (hasEventMessageTrack(adaptationSet)) { embeddedTrackCount++; } if (hasCea608Track(adaptationSet)) { embeddedTrackCount++; } } return embeddedTrackCount; }
private static boolean hasEventMessageTrack(AdaptationSet adaptationSet) { List<Representation> representations = adaptationSet.representations; for (int i = 0; i < representations.size(); i++) { Representation representation = representations.get(i); if (!representation.inbandEventStreams.isEmpty()) { return true; } } return false; }
private static boolean hasCea608Track(AdaptationSet adaptationSet) { List<SchemeValuePair> descriptors = adaptationSet.accessibilityDescriptors; for (int i = 0; i < descriptors.size(); i++) { SchemeValuePair descriptor = descriptors.get(i); if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri)) { return true; } } return false; }
private static TrackGroupArray buildTrackGroups(Period period) { TrackGroup[] trackGroupArray = new TrackGroup[period.adaptationSets.size()]; for (int i = 0; i < period.adaptationSets.size(); i++) { AdaptationSet adaptationSet = period.adaptationSets.get(i); List<Representation> representations = adaptationSet.representations; Format[] formats = new Format[representations.size()]; for (int j = 0; j < formats.length; j++) { formats[j] = representations.get(j).format; } trackGroupArray[i] = new TrackGroup(formats); } return new TrackGroupArray(trackGroupArray); }
private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackSelection selection, long positionUs) { int adaptationSetIndex = trackGroups.indexOf(selection.getTrackGroup()); AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex); DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( manifestLoaderErrorThrower, manifest, index, adaptationSetIndex, selection, elapsedRealtimeOffset); return new ChunkSampleStream<>(adaptationSet.type, chunkSource, this, allocator, positionUs, minLoadableRetryCount, eventDispatcher); }
private ArrayList<Representation> getRepresentations() { List<AdaptationSet> manifestAdapationSets = manifest.getPeriod(periodIndex).adaptationSets; ArrayList<Representation> representations = new ArrayList<>(); for (int adaptationSetIndex : adaptationSetIndices) { representations.addAll(manifestAdapationSets.get(adaptationSetIndex).representations); } return representations; }
/** * 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); }
private static int[][] getGroupedAdaptationSetIndices(List<AdaptationSet> adaptationSets) { int adaptationSetCount = adaptationSets.size(); SparseIntArray idToIndexMap = new SparseIntArray(adaptationSetCount); for (int i = 0; i < adaptationSetCount; i++) { idToIndexMap.put(adaptationSets.get(i).id, i); } int[][] groupedAdaptationSetIndices = new int[adaptationSetCount][]; boolean[] adaptationSetUsedFlags = new boolean[adaptationSetCount]; int groupCount = 0; for (int i = 0; i < adaptationSetCount; i++) { if (adaptationSetUsedFlags[i]) { // This adaptation set has already been included in a group. continue; } adaptationSetUsedFlags[i] = true; Descriptor adaptationSetSwitchingProperty = findAdaptationSetSwitchingProperty( adaptationSets.get(i).supplementalProperties); if (adaptationSetSwitchingProperty == null) { groupedAdaptationSetIndices[groupCount++] = new int[] {i}; } else { String[] extraAdaptationSetIds = adaptationSetSwitchingProperty.value.split(","); int[] adaptationSetIndices = new int[1 + extraAdaptationSetIds.length]; adaptationSetIndices[0] = i; for (int j = 0; j < extraAdaptationSetIds.length; j++) { int extraIndex = idToIndexMap.get(Integer.parseInt(extraAdaptationSetIds[j])); adaptationSetUsedFlags[extraIndex] = true; adaptationSetIndices[1 + j] = extraIndex; } groupedAdaptationSetIndices[groupCount++] = adaptationSetIndices; } } return groupCount < adaptationSetCount ? Arrays.copyOf(groupedAdaptationSetIndices, groupCount) : groupedAdaptationSetIndices; }
private static boolean hasEventMessageTrack(List<AdaptationSet> adaptationSets, int[] adaptationSetIndices) { for (int i : adaptationSetIndices) { List<Representation> representations = adaptationSets.get(i).representations; for (int j = 0; j < representations.size(); j++) { Representation representation = representations.get(j); if (!representation.inbandEventStreams.isEmpty()) { return true; } } } return false; }
private static boolean hasCea608Track(List<AdaptationSet> adaptationSets, int[] adaptationSetIndices) { for (int i : adaptationSetIndices) { List<Descriptor> descriptors = adaptationSets.get(i).accessibilityDescriptors; for (int j = 0; j < descriptors.size(); j++) { Descriptor descriptor = descriptors.get(j); if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri)) { return true; } } } return false; }
private AdaptationSet getAdaptationSet() { return manifest.getPeriod(periodIndex).adaptationSets.get(adaptationSetIndex); }
private static Period newPeriod(AdaptationSet... adaptationSets) { return new Period("", 0, Arrays.asList(adaptationSets)); }
private static AdaptationSet newAdaptationSets(Representation... representations) { return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null, null); }