use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class HlsMediaPeriod method buildAndPrepareMainSampleStreamWrapper.
/**
* This method creates and starts preparation of the main {@link HlsSampleStreamWrapper}.
*
* <p>The main sample stream wrapper is the first element of {@link #sampleStreamWrappers}. It
* provides {@link SampleStream}s for the variant urls in the multivariant playlist. It may be
* adaptive and may contain multiple muxed tracks.
*
* <p>If chunkless preparation is allowed, the media period will try preparation without segment
* downloads. This is only possible if variants contain the CODECS attribute. If not, traditional
* preparation with segment downloads will take place. The following points apply to chunkless
* preparation:
*
* <ul>
* <li>A muxed audio track will be exposed if the codecs list contain an audio entry and the
* multivariant playlist either contains an EXT-X-MEDIA tag without the URI attribute or
* does not contain any EXT-X-MEDIA tag.
* <li>Closed captions will only be exposed if they are declared by the multivariant playlist.
* <li>An ID3 track is exposed preemptively, in case the segments contain an ID3 track.
* </ul>
*
* @param multivariantPlaylist The HLS multivariant playlist.
* @param positionUs If preparation requires any chunk downloads, the position in microseconds at
* which downloading should start. Ignored otherwise.
* @param sampleStreamWrappers List to which the built main sample stream wrapper should be added.
* @param manifestUrlIndicesPerWrapper List to which the selected variant indices should be added.
* @param overridingDrmInitData Overriding {@link DrmInitData}, keyed by protection scheme type
* (i.e. {@link DrmInitData#schemeType}).
*/
private void buildAndPrepareMainSampleStreamWrapper(HlsMultivariantPlaylist multivariantPlaylist, long positionUs, List<HlsSampleStreamWrapper> sampleStreamWrappers, List<int[]> manifestUrlIndicesPerWrapper, Map<String, DrmInitData> overridingDrmInitData) {
int[] variantTypes = new int[multivariantPlaylist.variants.size()];
int videoVariantCount = 0;
int audioVariantCount = 0;
for (int i = 0; i < multivariantPlaylist.variants.size(); i++) {
Variant variant = multivariantPlaylist.variants.get(i);
Format format = variant.format;
if (format.height > 0 || Util.getCodecsOfType(format.codecs, C.TRACK_TYPE_VIDEO) != null) {
variantTypes[i] = C.TRACK_TYPE_VIDEO;
videoVariantCount++;
} else if (Util.getCodecsOfType(format.codecs, C.TRACK_TYPE_AUDIO) != null) {
variantTypes[i] = C.TRACK_TYPE_AUDIO;
audioVariantCount++;
} else {
variantTypes[i] = C.TRACK_TYPE_UNKNOWN;
}
}
boolean useVideoVariantsOnly = false;
boolean useNonAudioVariantsOnly = false;
int selectedVariantsCount = variantTypes.length;
if (videoVariantCount > 0) {
// We've identified some variants as definitely containing video. Assume variants within the
// multivariant playlist are marked consistently, and hence that we have the full set. Filter
// out any other variants, which are likely to be audio only.
useVideoVariantsOnly = true;
selectedVariantsCount = videoVariantCount;
} else if (audioVariantCount < variantTypes.length) {
// We've identified some variants, but not all, as being audio only. Filter them out to leave
// the remaining variants, which are likely to contain video.
useNonAudioVariantsOnly = true;
selectedVariantsCount = variantTypes.length - audioVariantCount;
}
Uri[] selectedPlaylistUrls = new Uri[selectedVariantsCount];
Format[] selectedPlaylistFormats = new Format[selectedVariantsCount];
int[] selectedVariantIndices = new int[selectedVariantsCount];
int outIndex = 0;
for (int i = 0; i < multivariantPlaylist.variants.size(); i++) {
if ((!useVideoVariantsOnly || variantTypes[i] == C.TRACK_TYPE_VIDEO) && (!useNonAudioVariantsOnly || variantTypes[i] != C.TRACK_TYPE_AUDIO)) {
Variant variant = multivariantPlaylist.variants.get(i);
selectedPlaylistUrls[outIndex] = variant.url;
selectedPlaylistFormats[outIndex] = variant.format;
selectedVariantIndices[outIndex++] = i;
}
}
String codecs = selectedPlaylistFormats[0].codecs;
int numberOfVideoCodecs = Util.getCodecCountOfType(codecs, C.TRACK_TYPE_VIDEO);
int numberOfAudioCodecs = Util.getCodecCountOfType(codecs, C.TRACK_TYPE_AUDIO);
boolean codecsStringAllowsChunklessPreparation = numberOfAudioCodecs <= 1 && numberOfVideoCodecs <= 1 && numberOfAudioCodecs + numberOfVideoCodecs > 0;
@C.TrackType int trackType = !useVideoVariantsOnly && numberOfAudioCodecs > 0 ? C.TRACK_TYPE_AUDIO : C.TRACK_TYPE_DEFAULT;
String sampleStreamWrapperUid = "main";
HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(sampleStreamWrapperUid, trackType, selectedPlaylistUrls, selectedPlaylistFormats, multivariantPlaylist.muxedAudioFormat, multivariantPlaylist.muxedCaptionFormats, overridingDrmInitData, positionUs);
sampleStreamWrappers.add(sampleStreamWrapper);
manifestUrlIndicesPerWrapper.add(selectedVariantIndices);
if (allowChunklessPreparation && codecsStringAllowsChunklessPreparation) {
List<TrackGroup> muxedTrackGroups = new ArrayList<>();
if (numberOfVideoCodecs > 0) {
Format[] videoFormats = new Format[selectedVariantsCount];
for (int i = 0; i < videoFormats.length; i++) {
videoFormats[i] = deriveVideoFormat(selectedPlaylistFormats[i]);
}
muxedTrackGroups.add(new TrackGroup(sampleStreamWrapperUid, videoFormats));
if (numberOfAudioCodecs > 0 && (multivariantPlaylist.muxedAudioFormat != null || multivariantPlaylist.audios.isEmpty())) {
muxedTrackGroups.add(new TrackGroup(/* id= */
sampleStreamWrapperUid + ":audio", deriveAudioFormat(selectedPlaylistFormats[0], multivariantPlaylist.muxedAudioFormat, /* isPrimaryTrackInVariant= */
false)));
}
List<Format> ccFormats = multivariantPlaylist.muxedCaptionFormats;
if (ccFormats != null) {
for (int i = 0; i < ccFormats.size(); i++) {
String ccId = sampleStreamWrapperUid + ":cc:" + i;
muxedTrackGroups.add(new TrackGroup(ccId, ccFormats.get(i)));
}
}
} else /* numberOfAudioCodecs > 0 */
{
// Variants only contain audio.
Format[] audioFormats = new Format[selectedVariantsCount];
for (int i = 0; i < audioFormats.length; i++) {
audioFormats[i] = deriveAudioFormat(/* variantFormat= */
selectedPlaylistFormats[i], multivariantPlaylist.muxedAudioFormat, /* isPrimaryTrackInVariant= */
true);
}
muxedTrackGroups.add(new TrackGroup(sampleStreamWrapperUid, audioFormats));
}
TrackGroup id3TrackGroup = new TrackGroup(/* id= */
sampleStreamWrapperUid + ":id3", new Format.Builder().setId("ID3").setSampleMimeType(MimeTypes.APPLICATION_ID3).build());
muxedTrackGroups.add(id3TrackGroup);
sampleStreamWrapper.prepareWithMultivariantPlaylistInfo(muxedTrackGroups.toArray(new TrackGroup[0]), /* primaryTrackGroupIndex= */
0, /* optionalTrackGroupsIndices...= */
muxedTrackGroups.indexOf(id3TrackGroup));
}
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class DefaultHlsPlaylistTracker method getLoadedPlaylistStartTimeUs.
private long getLoadedPlaylistStartTimeUs(@Nullable HlsMediaPlaylist oldPlaylist, HlsMediaPlaylist loadedPlaylist) {
if (loadedPlaylist.hasProgramDateTime) {
return loadedPlaylist.startTimeUs;
}
long primarySnapshotStartTimeUs = primaryMediaPlaylistSnapshot != null ? primaryMediaPlaylistSnapshot.startTimeUs : 0;
if (oldPlaylist == null) {
return primarySnapshotStartTimeUs;
}
int oldPlaylistSize = oldPlaylist.segments.size();
Segment firstOldOverlappingSegment = getFirstOldOverlappingSegment(oldPlaylist, loadedPlaylist);
if (firstOldOverlappingSegment != null) {
return oldPlaylist.startTimeUs + firstOldOverlappingSegment.relativeStartTimeUs;
} else if (oldPlaylistSize == loadedPlaylist.mediaSequence - oldPlaylist.mediaSequence) {
return oldPlaylist.getEndTimeUs();
} else {
// No segments overlap, we assume the new playlist start coincides with the primary playlist.
return primarySnapshotStartTimeUs;
}
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class DashMediaSourceTest method prepare_targetLiveOffsetTooLong_correctedTargetOffsetAndAlignedWindowStartPosition.
@Test
public void prepare_targetLiveOffsetTooLong_correctedTargetOffsetAndAlignedWindowStartPosition() throws InterruptedException {
DashMediaSource mediaSource = new DashMediaSource.Factory(() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_OFFSET_TOO_LONG)).createMediaSource(MediaItem.fromUri(Uri.EMPTY));
Window window = prepareAndWaitForTimelineRefresh(mediaSource);
// Expect the default position at the first segment start below the minimum live start position.
assertThat(window.getDefaultPositionMs()).isEqualTo(4_000);
// Expect the target live offset reaching from now time to the minimum live start position.
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(9000);
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class DashDownloader method getSegments.
@Override
protected List<Segment> getSegments(DataSource dataSource, DashManifest manifest, boolean removing) throws IOException, InterruptedException {
ArrayList<Segment> segments = new ArrayList<>();
for (int i = 0; i < manifest.getPeriodCount(); i++) {
Period period = manifest.getPeriod(i);
long periodStartUs = Util.msToUs(period.startMs);
long periodDurationUs = manifest.getPeriodDurationUs(i);
List<AdaptationSet> adaptationSets = period.adaptationSets;
for (int j = 0; j < adaptationSets.size(); j++) {
addSegmentsForAdaptationSet(dataSource, adaptationSets.get(j), periodStartUs, periodDurationUs, removing, segments);
}
}
return segments;
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment in project ExoPlayer by google.
the class DashDownloader method addSegmentsForAdaptationSet.
private void addSegmentsForAdaptationSet(DataSource dataSource, AdaptationSet adaptationSet, long periodStartUs, long periodDurationUs, boolean removing, ArrayList<Segment> out) throws IOException, InterruptedException {
for (int i = 0; i < adaptationSet.representations.size(); i++) {
Representation representation = adaptationSet.representations.get(i);
DashSegmentIndex index;
try {
index = getSegmentIndex(dataSource, adaptationSet.type, representation, removing);
if (index == null) {
// Loading succeeded but there was no index.
throw new DownloadException("Missing segment index");
}
} catch (IOException e) {
if (!removing) {
throw e;
}
// Generating an incomplete segment list is allowed. Advance to the next representation.
continue;
}
long segmentCount = index.getSegmentCount(periodDurationUs);
if (segmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
throw new DownloadException("Unbounded segment index");
}
String baseUrl = castNonNull(baseUrlExclusionList.selectBaseUrl(representation.baseUrls)).url;
@Nullable RangedUri initializationUri = representation.getInitializationUri();
if (initializationUri != null) {
out.add(createSegment(representation, baseUrl, periodStartUs, initializationUri));
}
@Nullable RangedUri indexUri = representation.getIndexUri();
if (indexUri != null) {
out.add(createSegment(representation, baseUrl, periodStartUs, indexUri));
}
long firstSegmentNum = index.getFirstSegmentNum();
long lastSegmentNum = firstSegmentNum + segmentCount - 1;
for (long j = firstSegmentNum; j <= lastSegmentNum; j++) {
out.add(createSegment(representation, baseUrl, periodStartUs + index.getTimeUs(j), index.getSegmentUrl(j)));
}
}
}
Aggregations