Search in sources :

Example 6 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

the class HlsPlaylistParser method parseMultivariantPlaylist.

private static HlsMultivariantPlaylist parseMultivariantPlaylist(LineIterator iterator, String baseUri) throws IOException {
    HashMap<Uri, ArrayList<VariantInfo>> urlToVariantInfos = new HashMap<>();
    HashMap<String, String> variableDefinitions = new HashMap<>();
    ArrayList<Variant> variants = new ArrayList<>();
    ArrayList<Rendition> videos = new ArrayList<>();
    ArrayList<Rendition> audios = new ArrayList<>();
    ArrayList<Rendition> subtitles = new ArrayList<>();
    ArrayList<Rendition> closedCaptions = new ArrayList<>();
    ArrayList<String> mediaTags = new ArrayList<>();
    ArrayList<DrmInitData> sessionKeyDrmInitData = new ArrayList<>();
    ArrayList<String> tags = new ArrayList<>();
    Format muxedAudioFormat = null;
    List<Format> muxedCaptionFormats = null;
    boolean noClosedCaptions = false;
    boolean hasIndependentSegmentsTag = false;
    String line;
    while (iterator.hasNext()) {
        line = iterator.next();
        if (line.startsWith(TAG_PREFIX)) {
            // We expose all tags through the playlist.
            tags.add(line);
        }
        boolean isIFrameOnlyVariant = line.startsWith(TAG_I_FRAME_STREAM_INF);
        if (line.startsWith(TAG_DEFINE)) {
            variableDefinitions.put(/* key= */
            parseStringAttr(line, REGEX_NAME, variableDefinitions), /* value= */
            parseStringAttr(line, REGEX_VALUE, variableDefinitions));
        } else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) {
            hasIndependentSegmentsTag = true;
        } else if (line.startsWith(TAG_MEDIA)) {
            // Media tags are parsed at the end to include codec information from #EXT-X-STREAM-INF
            // tags.
            mediaTags.add(line);
        } else if (line.startsWith(TAG_SESSION_KEY)) {
            String keyFormat = parseOptionalStringAttr(line, REGEX_KEYFORMAT, KEYFORMAT_IDENTITY, variableDefinitions);
            SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions);
            if (schemeData != null) {
                String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions);
                String scheme = parseEncryptionScheme(method);
                sessionKeyDrmInitData.add(new DrmInitData(scheme, schemeData));
            }
        } else if (line.startsWith(TAG_STREAM_INF) || isIFrameOnlyVariant) {
            noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
            int roleFlags = isIFrameOnlyVariant ? C.ROLE_FLAG_TRICK_PLAY : 0;
            int peakBitrate = parseIntAttr(line, REGEX_BANDWIDTH);
            int averageBitrate = parseOptionalIntAttr(line, REGEX_AVERAGE_BANDWIDTH, -1);
            String codecs = parseOptionalStringAttr(line, REGEX_CODECS, variableDefinitions);
            String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION, variableDefinitions);
            int width;
            int height;
            if (resolutionString != null) {
                String[] widthAndHeight = Util.split(resolutionString, "x");
                width = Integer.parseInt(widthAndHeight[0]);
                height = Integer.parseInt(widthAndHeight[1]);
                if (width <= 0 || height <= 0) {
                    // Resolution string is invalid.
                    width = Format.NO_VALUE;
                    height = Format.NO_VALUE;
                }
            } else {
                width = Format.NO_VALUE;
                height = Format.NO_VALUE;
            }
            float frameRate = Format.NO_VALUE;
            String frameRateString = parseOptionalStringAttr(line, REGEX_FRAME_RATE, variableDefinitions);
            if (frameRateString != null) {
                frameRate = Float.parseFloat(frameRateString);
            }
            String videoGroupId = parseOptionalStringAttr(line, REGEX_VIDEO, variableDefinitions);
            String audioGroupId = parseOptionalStringAttr(line, REGEX_AUDIO, variableDefinitions);
            String subtitlesGroupId = parseOptionalStringAttr(line, REGEX_SUBTITLES, variableDefinitions);
            String closedCaptionsGroupId = parseOptionalStringAttr(line, REGEX_CLOSED_CAPTIONS, variableDefinitions);
            Uri uri;
            if (isIFrameOnlyVariant) {
                uri = UriUtil.resolveToUri(baseUri, parseStringAttr(line, REGEX_URI, variableDefinitions));
            } else if (!iterator.hasNext()) {
                throw ParserException.createForMalformedManifest("#EXT-X-STREAM-INF must be followed by another line", /* cause= */
                null);
            } else {
                // The following line contains #EXT-X-STREAM-INF's URI.
                line = replaceVariableReferences(iterator.next(), variableDefinitions);
                uri = UriUtil.resolveToUri(baseUri, line);
            }
            Format format = new Format.Builder().setId(variants.size()).setContainerMimeType(MimeTypes.APPLICATION_M3U8).setCodecs(codecs).setAverageBitrate(averageBitrate).setPeakBitrate(peakBitrate).setWidth(width).setHeight(height).setFrameRate(frameRate).setRoleFlags(roleFlags).build();
            Variant variant = new Variant(uri, format, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId);
            variants.add(variant);
            @Nullable ArrayList<VariantInfo> variantInfosForUrl = urlToVariantInfos.get(uri);
            if (variantInfosForUrl == null) {
                variantInfosForUrl = new ArrayList<>();
                urlToVariantInfos.put(uri, variantInfosForUrl);
            }
            variantInfosForUrl.add(new VariantInfo(averageBitrate, peakBitrate, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId));
        }
    }
    // TODO: Don't deduplicate variants by URL.
    ArrayList<Variant> deduplicatedVariants = new ArrayList<>();
    HashSet<Uri> urlsInDeduplicatedVariants = new HashSet<>();
    for (int i = 0; i < variants.size(); i++) {
        Variant variant = variants.get(i);
        if (urlsInDeduplicatedVariants.add(variant.url)) {
            Assertions.checkState(variant.format.metadata == null);
            HlsTrackMetadataEntry hlsMetadataEntry = new HlsTrackMetadataEntry(/* groupId= */
            null, /* name= */
            null, checkNotNull(urlToVariantInfos.get(variant.url)));
            Metadata metadata = new Metadata(hlsMetadataEntry);
            Format format = variant.format.buildUpon().setMetadata(metadata).build();
            deduplicatedVariants.add(variant.copyWithFormat(format));
        }
    }
    for (int i = 0; i < mediaTags.size(); i++) {
        line = mediaTags.get(i);
        String groupId = parseStringAttr(line, REGEX_GROUP_ID, variableDefinitions);
        String name = parseStringAttr(line, REGEX_NAME, variableDefinitions);
        Format.Builder formatBuilder = new Format.Builder().setId(groupId + ":" + name).setLabel(name).setContainerMimeType(MimeTypes.APPLICATION_M3U8).setSelectionFlags(parseSelectionFlags(line)).setRoleFlags(parseRoleFlags(line, variableDefinitions)).setLanguage(parseOptionalStringAttr(line, REGEX_LANGUAGE, variableDefinitions));
        @Nullable String referenceUri = parseOptionalStringAttr(line, REGEX_URI, variableDefinitions);
        @Nullable Uri uri = referenceUri == null ? null : UriUtil.resolveToUri(baseUri, referenceUri);
        Metadata metadata = new Metadata(new HlsTrackMetadataEntry(groupId, name, Collections.emptyList()));
        switch(parseStringAttr(line, REGEX_TYPE, variableDefinitions)) {
            case TYPE_VIDEO:
                @Nullable Variant variant = getVariantWithVideoGroup(variants, groupId);
                if (variant != null) {
                    Format variantFormat = variant.format;
                    @Nullable String codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
                    formatBuilder.setCodecs(codecs).setSampleMimeType(MimeTypes.getMediaMimeType(codecs)).setWidth(variantFormat.width).setHeight(variantFormat.height).setFrameRate(variantFormat.frameRate);
                }
                if (uri == null) {
                // TODO: Remove this case and add a Rendition with a null uri to videos.
                } else {
                    formatBuilder.setMetadata(metadata);
                    videos.add(new Rendition(uri, formatBuilder.build(), groupId, name));
                }
                break;
            case TYPE_AUDIO:
                @Nullable String sampleMimeType = null;
                variant = getVariantWithAudioGroup(variants, groupId);
                if (variant != null) {
                    @Nullable String codecs = Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_AUDIO);
                    formatBuilder.setCodecs(codecs);
                    sampleMimeType = MimeTypes.getMediaMimeType(codecs);
                }
                @Nullable String channelsString = parseOptionalStringAttr(line, REGEX_CHANNELS, variableDefinitions);
                if (channelsString != null) {
                    int channelCount = Integer.parseInt(Util.splitAtFirst(channelsString, "/")[0]);
                    formatBuilder.setChannelCount(channelCount);
                    if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType) && channelsString.endsWith("/JOC")) {
                        sampleMimeType = MimeTypes.AUDIO_E_AC3_JOC;
                        formatBuilder.setCodecs(MimeTypes.CODEC_E_AC3_JOC);
                    }
                }
                formatBuilder.setSampleMimeType(sampleMimeType);
                if (uri != null) {
                    formatBuilder.setMetadata(metadata);
                    audios.add(new Rendition(uri, formatBuilder.build(), groupId, name));
                } else if (variant != null) {
                    // TODO: Remove muxedAudioFormat and add a Rendition with a null uri to audios.
                    muxedAudioFormat = formatBuilder.build();
                }
                break;
            case TYPE_SUBTITLES:
                sampleMimeType = null;
                variant = getVariantWithSubtitleGroup(variants, groupId);
                if (variant != null) {
                    @Nullable String codecs = Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_TEXT);
                    formatBuilder.setCodecs(codecs);
                    sampleMimeType = MimeTypes.getMediaMimeType(codecs);
                }
                if (sampleMimeType == null) {
                    sampleMimeType = MimeTypes.TEXT_VTT;
                }
                formatBuilder.setSampleMimeType(sampleMimeType).setMetadata(metadata);
                if (uri != null) {
                    subtitles.add(new Rendition(uri, formatBuilder.build(), groupId, name));
                } else {
                    Log.w(LOG_TAG, "EXT-X-MEDIA tag with missing mandatory URI attribute: skipping");
                }
                break;
            case TYPE_CLOSED_CAPTIONS:
                String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID, variableDefinitions);
                int accessibilityChannel;
                if (instreamId.startsWith("CC")) {
                    sampleMimeType = MimeTypes.APPLICATION_CEA608;
                    accessibilityChannel = Integer.parseInt(instreamId.substring(2));
                } else /* starts with SERVICE */
                {
                    sampleMimeType = MimeTypes.APPLICATION_CEA708;
                    accessibilityChannel = Integer.parseInt(instreamId.substring(7));
                }
                if (muxedCaptionFormats == null) {
                    muxedCaptionFormats = new ArrayList<>();
                }
                formatBuilder.setSampleMimeType(sampleMimeType).setAccessibilityChannel(accessibilityChannel);
                muxedCaptionFormats.add(formatBuilder.build());
                // TODO: Remove muxedCaptionFormats and add a Rendition with a null uri to closedCaptions.
                break;
            default:
                // Do nothing.
                break;
        }
    }
    if (noClosedCaptions) {
        muxedCaptionFormats = Collections.emptyList();
    }
    return new HlsMultivariantPlaylist(baseUri, tags, deduplicatedVariants, videos, audios, subtitles, closedCaptions, muxedAudioFormat, muxedCaptionFormats, hasIndependentSegmentsTag, variableDefinitions, sessionKeyDrmInitData);
}
Also used : VariantInfo(com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry.VariantInfo) HashMap(java.util.HashMap) Rendition(com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Rendition) ArrayList(java.util.ArrayList) Metadata(com.google.android.exoplayer2.metadata.Metadata) SchemeData(com.google.android.exoplayer2.drm.DrmInitData.SchemeData) Uri(android.net.Uri) DrmInitData(com.google.android.exoplayer2.drm.DrmInitData) Format(com.google.android.exoplayer2.Format) HashSet(java.util.HashSet) HlsTrackMetadataEntry(com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry) Variant(com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant) Nullable(androidx.annotation.Nullable)

Example 7 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

the class HlsPlaylistParser method parseMediaPlaylist.

private static HlsMediaPlaylist parseMediaPlaylist(HlsMultivariantPlaylist multivariantPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist, LineIterator iterator, String baseUri) throws IOException {
    @HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
    long startOffsetUs = C.TIME_UNSET;
    long mediaSequence = 0;
    // Default version == 1.
    int version = 1;
    long targetDurationUs = C.TIME_UNSET;
    long partTargetDurationUs = C.TIME_UNSET;
    boolean hasIndependentSegmentsTag = multivariantPlaylist.hasIndependentSegments;
    boolean hasEndTag = false;
    @Nullable Segment initializationSegment = null;
    HashMap<String, String> variableDefinitions = new HashMap<>();
    HashMap<String, Segment> urlToInferredInitSegment = new HashMap<>();
    List<Segment> segments = new ArrayList<>();
    List<Part> trailingParts = new ArrayList<>();
    @Nullable Part preloadPart = null;
    List<RenditionReport> renditionReports = new ArrayList<>();
    List<String> tags = new ArrayList<>();
    long segmentDurationUs = 0;
    String segmentTitle = "";
    boolean hasDiscontinuitySequence = false;
    int playlistDiscontinuitySequence = 0;
    int relativeDiscontinuitySequence = 0;
    long playlistStartTimeUs = 0;
    long segmentStartTimeUs = 0;
    boolean preciseStart = false;
    long segmentByteRangeOffset = 0;
    long segmentByteRangeLength = C.LENGTH_UNSET;
    long partStartTimeUs = 0;
    long partByteRangeOffset = 0;
    boolean isIFrameOnly = false;
    long segmentMediaSequence = 0;
    boolean hasGapTag = false;
    HlsMediaPlaylist.ServerControl serverControl = new HlsMediaPlaylist.ServerControl(/* skipUntilUs= */
    C.TIME_UNSET, /* canSkipDateRanges= */
    false, /* holdBackUs= */
    C.TIME_UNSET, /* partHoldBackUs= */
    C.TIME_UNSET, /* canBlockReload= */
    false);
    @Nullable DrmInitData playlistProtectionSchemes = null;
    @Nullable String fullSegmentEncryptionKeyUri = null;
    @Nullable String fullSegmentEncryptionIV = null;
    TreeMap<String, SchemeData> currentSchemeDatas = new TreeMap<>();
    @Nullable String encryptionScheme = null;
    @Nullable DrmInitData cachedDrmInitData = null;
    String line;
    while (iterator.hasNext()) {
        line = iterator.next();
        if (line.startsWith(TAG_PREFIX)) {
            // We expose all tags through the playlist.
            tags.add(line);
        }
        if (line.startsWith(TAG_PLAYLIST_TYPE)) {
            String playlistTypeString = parseStringAttr(line, REGEX_PLAYLIST_TYPE, variableDefinitions);
            if ("VOD".equals(playlistTypeString)) {
                playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_VOD;
            } else if ("EVENT".equals(playlistTypeString)) {
                playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_EVENT;
            }
        } else if (line.equals(TAG_IFRAME)) {
            isIFrameOnly = true;
        } else if (line.startsWith(TAG_START)) {
            startOffsetUs = (long) (parseDoubleAttr(line, REGEX_TIME_OFFSET) * C.MICROS_PER_SECOND);
            preciseStart = parseOptionalBooleanAttribute(line, REGEX_PRECISE, /* defaultValue= */
            false);
        } else if (line.startsWith(TAG_SERVER_CONTROL)) {
            serverControl = parseServerControl(line);
        } else if (line.startsWith(TAG_PART_INF)) {
            double partTargetDurationSeconds = parseDoubleAttr(line, REGEX_PART_TARGET_DURATION);
            partTargetDurationUs = (long) (partTargetDurationSeconds * C.MICROS_PER_SECOND);
        } else if (line.startsWith(TAG_INIT_SEGMENT)) {
            String uri = parseStringAttr(line, REGEX_URI, variableDefinitions);
            String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE, variableDefinitions);
            if (byteRange != null) {
                String[] splitByteRange = Util.split(byteRange, "@");
                segmentByteRangeLength = Long.parseLong(splitByteRange[0]);
                if (splitByteRange.length > 1) {
                    segmentByteRangeOffset = Long.parseLong(splitByteRange[1]);
                }
            }
            if (segmentByteRangeLength == C.LENGTH_UNSET) {
                // The segment has no byte range defined.
                segmentByteRangeOffset = 0;
            }
            if (fullSegmentEncryptionKeyUri != null && fullSegmentEncryptionIV == null) {
                // See RFC 8216, Section 4.3.2.5.
                throw ParserException.createForMalformedManifest("The encryption IV attribute must be present when an initialization segment is" + " encrypted with METHOD=AES-128.", /* cause= */
                null);
            }
            initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength, fullSegmentEncryptionKeyUri, fullSegmentEncryptionIV);
            if (segmentByteRangeLength != C.LENGTH_UNSET) {
                segmentByteRangeOffset += segmentByteRangeLength;
            }
            segmentByteRangeLength = C.LENGTH_UNSET;
        } else if (line.startsWith(TAG_TARGET_DURATION)) {
            targetDurationUs = parseIntAttr(line, REGEX_TARGET_DURATION) * C.MICROS_PER_SECOND;
        } else if (line.startsWith(TAG_MEDIA_SEQUENCE)) {
            mediaSequence = parseLongAttr(line, REGEX_MEDIA_SEQUENCE);
            segmentMediaSequence = mediaSequence;
        } else if (line.startsWith(TAG_VERSION)) {
            version = parseIntAttr(line, REGEX_VERSION);
        } else if (line.startsWith(TAG_DEFINE)) {
            String importName = parseOptionalStringAttr(line, REGEX_IMPORT, variableDefinitions);
            if (importName != null) {
                String value = multivariantPlaylist.variableDefinitions.get(importName);
                if (value != null) {
                    variableDefinitions.put(importName, value);
                } else {
                // The multivariant playlist does not declare the imported variable. Ignore.
                }
            } else {
                variableDefinitions.put(parseStringAttr(line, REGEX_NAME, variableDefinitions), parseStringAttr(line, REGEX_VALUE, variableDefinitions));
            }
        } else if (line.startsWith(TAG_MEDIA_DURATION)) {
            segmentDurationUs = parseTimeSecondsToUs(line, REGEX_MEDIA_DURATION);
            segmentTitle = parseOptionalStringAttr(line, REGEX_MEDIA_TITLE, "", variableDefinitions);
        } else if (line.startsWith(TAG_SKIP)) {
            int skippedSegmentCount = parseIntAttr(line, REGEX_SKIPPED_SEGMENTS);
            checkState(previousMediaPlaylist != null && segments.isEmpty());
            int startIndex = (int) (mediaSequence - castNonNull(previousMediaPlaylist).mediaSequence);
            int endIndex = startIndex + skippedSegmentCount;
            if (startIndex < 0 || endIndex > previousMediaPlaylist.segments.size()) {
                // Throw to force a reload if not all segments are available in the previous playlist.
                throw new DeltaUpdateException();
            }
            for (int i = startIndex; i < endIndex; i++) {
                Segment segment = previousMediaPlaylist.segments.get(i);
                if (mediaSequence != previousMediaPlaylist.mediaSequence) {
                    // If the media sequences of the playlists are not the same, we need to recreate the
                    // object with the updated relative start time and the relative discontinuity
                    // sequence. With identical playlist media sequences these values do not change.
                    int newRelativeDiscontinuitySequence = previousMediaPlaylist.discontinuitySequence - playlistDiscontinuitySequence + segment.relativeDiscontinuitySequence;
                    segment = segment.copyWith(segmentStartTimeUs, newRelativeDiscontinuitySequence);
                }
                segments.add(segment);
                segmentStartTimeUs += segment.durationUs;
                partStartTimeUs = segmentStartTimeUs;
                if (segment.byteRangeLength != C.LENGTH_UNSET) {
                    segmentByteRangeOffset = segment.byteRangeOffset + segment.byteRangeLength;
                }
                relativeDiscontinuitySequence = segment.relativeDiscontinuitySequence;
                initializationSegment = segment.initializationSegment;
                cachedDrmInitData = segment.drmInitData;
                fullSegmentEncryptionKeyUri = segment.fullSegmentEncryptionKeyUri;
                if (segment.encryptionIV == null || !segment.encryptionIV.equals(Long.toHexString(segmentMediaSequence))) {
                    fullSegmentEncryptionIV = segment.encryptionIV;
                }
                segmentMediaSequence++;
            }
        } else if (line.startsWith(TAG_KEY)) {
            String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions);
            String keyFormat = parseOptionalStringAttr(line, REGEX_KEYFORMAT, KEYFORMAT_IDENTITY, variableDefinitions);
            fullSegmentEncryptionKeyUri = null;
            fullSegmentEncryptionIV = null;
            if (METHOD_NONE.equals(method)) {
                currentSchemeDatas.clear();
                cachedDrmInitData = null;
            } else /* !METHOD_NONE.equals(method) */
            {
                fullSegmentEncryptionIV = parseOptionalStringAttr(line, REGEX_IV, variableDefinitions);
                if (KEYFORMAT_IDENTITY.equals(keyFormat)) {
                    if (METHOD_AES_128.equals(method)) {
                        // The segment is fully encrypted using an identity key.
                        fullSegmentEncryptionKeyUri = parseStringAttr(line, REGEX_URI, variableDefinitions);
                    } else {
                    // Do nothing. Samples are encrypted using an identity key, but this is not supported.
                    // Hopefully, a traditional DRM alternative is also provided.
                    }
                } else {
                    if (encryptionScheme == null) {
                        encryptionScheme = parseEncryptionScheme(method);
                    }
                    SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions);
                    if (schemeData != null) {
                        cachedDrmInitData = null;
                        currentSchemeDatas.put(keyFormat, schemeData);
                    }
                }
            }
        } else if (line.startsWith(TAG_BYTERANGE)) {
            String byteRange = parseStringAttr(line, REGEX_BYTERANGE, variableDefinitions);
            String[] splitByteRange = Util.split(byteRange, "@");
            segmentByteRangeLength = Long.parseLong(splitByteRange[0]);
            if (splitByteRange.length > 1) {
                segmentByteRangeOffset = Long.parseLong(splitByteRange[1]);
            }
        } else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) {
            hasDiscontinuitySequence = true;
            playlistDiscontinuitySequence = Integer.parseInt(line.substring(line.indexOf(':') + 1));
        } else if (line.equals(TAG_DISCONTINUITY)) {
            relativeDiscontinuitySequence++;
        } else if (line.startsWith(TAG_PROGRAM_DATE_TIME)) {
            if (playlistStartTimeUs == 0) {
                long programDatetimeUs = Util.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1)));
                playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs;
            }
        } else if (line.equals(TAG_GAP)) {
            hasGapTag = true;
        } else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) {
            hasIndependentSegmentsTag = true;
        } else if (line.equals(TAG_ENDLIST)) {
            hasEndTag = true;
        } else if (line.startsWith(TAG_RENDITION_REPORT)) {
            long lastMediaSequence = parseOptionalLongAttr(line, REGEX_LAST_MSN, C.INDEX_UNSET);
            int lastPartIndex = parseOptionalIntAttr(line, REGEX_LAST_PART, C.INDEX_UNSET);
            String uri = parseStringAttr(line, REGEX_URI, variableDefinitions);
            Uri playlistUri = Uri.parse(UriUtil.resolve(baseUri, uri));
            renditionReports.add(new RenditionReport(playlistUri, lastMediaSequence, lastPartIndex));
        } else if (line.startsWith(TAG_PRELOAD_HINT)) {
            if (preloadPart != null) {
                continue;
            }
            String type = parseStringAttr(line, REGEX_PRELOAD_HINT_TYPE, variableDefinitions);
            if (!TYPE_PART.equals(type)) {
                continue;
            }
            String url = parseStringAttr(line, REGEX_URI, variableDefinitions);
            long byteRangeStart = parseOptionalLongAttr(line, REGEX_BYTERANGE_START, /* defaultValue= */
            C.LENGTH_UNSET);
            long byteRangeLength = parseOptionalLongAttr(line, REGEX_BYTERANGE_LENGTH, /* defaultValue= */
            C.LENGTH_UNSET);
            @Nullable String segmentEncryptionIV = getSegmentEncryptionIV(segmentMediaSequence, fullSegmentEncryptionKeyUri, fullSegmentEncryptionIV);
            if (cachedDrmInitData == null && !currentSchemeDatas.isEmpty()) {
                SchemeData[] schemeDatas = currentSchemeDatas.values().toArray(new SchemeData[0]);
                cachedDrmInitData = new DrmInitData(encryptionScheme, schemeDatas);
                if (playlistProtectionSchemes == null) {
                    playlistProtectionSchemes = getPlaylistProtectionSchemes(encryptionScheme, schemeDatas);
                }
            }
            if (byteRangeStart == C.LENGTH_UNSET || byteRangeLength != C.LENGTH_UNSET) {
                // Skip preload part if it is an unbounded range request.
                preloadPart = new Part(url, initializationSegment, /* durationUs= */
                0, relativeDiscontinuitySequence, partStartTimeUs, cachedDrmInitData, fullSegmentEncryptionKeyUri, segmentEncryptionIV, byteRangeStart != C.LENGTH_UNSET ? byteRangeStart : 0, byteRangeLength, /* hasGapTag= */
                false, /* isIndependent= */
                false, /* isPreload= */
                true);
            }
        } else if (line.startsWith(TAG_PART)) {
            @Nullable String segmentEncryptionIV = getSegmentEncryptionIV(segmentMediaSequence, fullSegmentEncryptionKeyUri, fullSegmentEncryptionIV);
            String url = parseStringAttr(line, REGEX_URI, variableDefinitions);
            long partDurationUs = (long) (parseDoubleAttr(line, REGEX_ATTR_DURATION) * C.MICROS_PER_SECOND);
            boolean isIndependent = parseOptionalBooleanAttribute(line, REGEX_INDEPENDENT, /* defaultValue= */
            false);
            // The first part of a segment is always independent if the segments are independent.
            isIndependent |= hasIndependentSegmentsTag && trailingParts.isEmpty();
            boolean isGap = parseOptionalBooleanAttribute(line, REGEX_GAP, /* defaultValue= */
            false);
            @Nullable String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE, variableDefinitions);
            long partByteRangeLength = C.LENGTH_UNSET;
            if (byteRange != null) {
                String[] splitByteRange = Util.split(byteRange, "@");
                partByteRangeLength = Long.parseLong(splitByteRange[0]);
                if (splitByteRange.length > 1) {
                    partByteRangeOffset = Long.parseLong(splitByteRange[1]);
                }
            }
            if (partByteRangeLength == C.LENGTH_UNSET) {
                partByteRangeOffset = 0;
            }
            if (cachedDrmInitData == null && !currentSchemeDatas.isEmpty()) {
                SchemeData[] schemeDatas = currentSchemeDatas.values().toArray(new SchemeData[0]);
                cachedDrmInitData = new DrmInitData(encryptionScheme, schemeDatas);
                if (playlistProtectionSchemes == null) {
                    playlistProtectionSchemes = getPlaylistProtectionSchemes(encryptionScheme, schemeDatas);
                }
            }
            trailingParts.add(new Part(url, initializationSegment, partDurationUs, relativeDiscontinuitySequence, partStartTimeUs, cachedDrmInitData, fullSegmentEncryptionKeyUri, segmentEncryptionIV, partByteRangeOffset, partByteRangeLength, isGap, isIndependent, /* isPreload= */
            false));
            partStartTimeUs += partDurationUs;
            if (partByteRangeLength != C.LENGTH_UNSET) {
                partByteRangeOffset += partByteRangeLength;
            }
        } else if (!line.startsWith("#")) {
            @Nullable String segmentEncryptionIV = getSegmentEncryptionIV(segmentMediaSequence, fullSegmentEncryptionKeyUri, fullSegmentEncryptionIV);
            segmentMediaSequence++;
            String segmentUri = replaceVariableReferences(line, variableDefinitions);
            @Nullable Segment inferredInitSegment = urlToInferredInitSegment.get(segmentUri);
            if (segmentByteRangeLength == C.LENGTH_UNSET) {
                // The segment has no byte range defined.
                segmentByteRangeOffset = 0;
            } else if (isIFrameOnly && initializationSegment == null && inferredInitSegment == null) {
                // The segment is a resource byte range without an initialization segment.
                // As per RFC 8216, Section 4.3.3.6, we assume the initialization section exists in the
                // bytes preceding the first segment in this segment's URL.
                // We assume the implicit initialization segment is unencrypted, since there's no way for
                // the playlist to provide an initialization vector for it.
                inferredInitSegment = new Segment(segmentUri, /* byteRangeOffset= */
                0, segmentByteRangeOffset, /* fullSegmentEncryptionKeyUri= */
                null, /* encryptionIV= */
                null);
                urlToInferredInitSegment.put(segmentUri, inferredInitSegment);
            }
            if (cachedDrmInitData == null && !currentSchemeDatas.isEmpty()) {
                SchemeData[] schemeDatas = currentSchemeDatas.values().toArray(new SchemeData[0]);
                cachedDrmInitData = new DrmInitData(encryptionScheme, schemeDatas);
                if (playlistProtectionSchemes == null) {
                    playlistProtectionSchemes = getPlaylistProtectionSchemes(encryptionScheme, schemeDatas);
                }
            }
            segments.add(new Segment(segmentUri, initializationSegment != null ? initializationSegment : inferredInitSegment, segmentTitle, segmentDurationUs, relativeDiscontinuitySequence, segmentStartTimeUs, cachedDrmInitData, fullSegmentEncryptionKeyUri, segmentEncryptionIV, segmentByteRangeOffset, segmentByteRangeLength, hasGapTag, trailingParts));
            segmentStartTimeUs += segmentDurationUs;
            partStartTimeUs = segmentStartTimeUs;
            segmentDurationUs = 0;
            segmentTitle = "";
            trailingParts = new ArrayList<>();
            if (segmentByteRangeLength != C.LENGTH_UNSET) {
                segmentByteRangeOffset += segmentByteRangeLength;
            }
            segmentByteRangeLength = C.LENGTH_UNSET;
            hasGapTag = false;
        }
    }
    Map<Uri, RenditionReport> renditionReportMap = new HashMap<>();
    for (int i = 0; i < renditionReports.size(); i++) {
        RenditionReport renditionReport = renditionReports.get(i);
        long lastMediaSequence = renditionReport.lastMediaSequence;
        if (lastMediaSequence == C.INDEX_UNSET) {
            lastMediaSequence = mediaSequence + segments.size() - (trailingParts.isEmpty() ? 1 : 0);
        }
        int lastPartIndex = renditionReport.lastPartIndex;
        if (lastPartIndex == C.INDEX_UNSET && partTargetDurationUs != C.TIME_UNSET) {
            List<Part> lastParts = trailingParts.isEmpty() ? Iterables.getLast(segments).parts : trailingParts;
            lastPartIndex = lastParts.size() - 1;
        }
        renditionReportMap.put(renditionReport.playlistUri, new RenditionReport(renditionReport.playlistUri, lastMediaSequence, lastPartIndex));
    }
    if (preloadPart != null) {
        trailingParts.add(preloadPart);
    }
    return new HlsMediaPlaylist(playlistType, baseUri, tags, startOffsetUs, preciseStart, playlistStartTimeUs, hasDiscontinuitySequence, playlistDiscontinuitySequence, mediaSequence, version, targetDurationUs, partTargetDurationUs, hasIndependentSegmentsTag, hasEndTag, /* hasProgramDateTime= */
    playlistStartTimeUs != 0, playlistProtectionSchemes, segments, trailingParts, serverControl, renditionReportMap);
}
Also used : HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) RenditionReport(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.RenditionReport) SchemeData(com.google.android.exoplayer2.drm.DrmInitData.SchemeData) Uri(android.net.Uri) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) DrmInitData(com.google.android.exoplayer2.drm.DrmInitData) TreeMap(java.util.TreeMap) Part(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part) Nullable(androidx.annotation.Nullable)

Example 8 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

the class HlsMediaPeriod method buildAndPrepareSampleStreamWrappers.

// Internal methods.
private void buildAndPrepareSampleStreamWrappers(long positionUs) {
    HlsMultivariantPlaylist multivariantPlaylist = Assertions.checkNotNull(playlistTracker.getMultivariantPlaylist());
    Map<String, DrmInitData> overridingDrmInitData = useSessionKeys ? deriveOverridingDrmInitData(multivariantPlaylist.sessionKeyDrmInitData) : Collections.emptyMap();
    boolean hasVariants = !multivariantPlaylist.variants.isEmpty();
    List<Rendition> audioRenditions = multivariantPlaylist.audios;
    List<Rendition> subtitleRenditions = multivariantPlaylist.subtitles;
    pendingPrepareCount = 0;
    ArrayList<HlsSampleStreamWrapper> sampleStreamWrappers = new ArrayList<>();
    ArrayList<int[]> manifestUrlIndicesPerWrapper = new ArrayList<>();
    if (hasVariants) {
        buildAndPrepareMainSampleStreamWrapper(multivariantPlaylist, positionUs, sampleStreamWrappers, manifestUrlIndicesPerWrapper, overridingDrmInitData);
    }
    // TODO: Build video stream wrappers here.
    buildAndPrepareAudioSampleStreamWrappers(positionUs, audioRenditions, sampleStreamWrappers, manifestUrlIndicesPerWrapper, overridingDrmInitData);
    audioVideoSampleStreamWrapperCount = sampleStreamWrappers.size();
    // these.
    for (int i = 0; i < subtitleRenditions.size(); i++) {
        Rendition subtitleRendition = subtitleRenditions.get(i);
        String sampleStreamWrapperUid = "subtitle:" + i + ":" + subtitleRendition.name;
        HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(sampleStreamWrapperUid, C.TRACK_TYPE_TEXT, new Uri[] { subtitleRendition.url }, new Format[] { subtitleRendition.format }, null, Collections.emptyList(), overridingDrmInitData, positionUs);
        manifestUrlIndicesPerWrapper.add(new int[] { i });
        sampleStreamWrappers.add(sampleStreamWrapper);
        sampleStreamWrapper.prepareWithMultivariantPlaylistInfo(new TrackGroup[] { new TrackGroup(sampleStreamWrapperUid, subtitleRendition.format) }, /* primaryTrackGroupIndex= */
        0);
    }
    this.sampleStreamWrappers = sampleStreamWrappers.toArray(new HlsSampleStreamWrapper[0]);
    this.manifestUrlIndicesPerWrapper = manifestUrlIndicesPerWrapper.toArray(new int[0][]);
    pendingPrepareCount = this.sampleStreamWrappers.length;
    // Set timestamp master and trigger preparation (if not already prepared)
    this.sampleStreamWrappers[0].setIsTimestampMaster(true);
    for (HlsSampleStreamWrapper sampleStreamWrapper : this.sampleStreamWrappers) {
        sampleStreamWrapper.continuePreparing();
    }
    // All wrappers are enabled during preparation.
    enabledSampleStreamWrappers = this.sampleStreamWrappers;
}
Also used : Rendition(com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Rendition) ArrayList(java.util.ArrayList) DrmInitData(com.google.android.exoplayer2.drm.DrmInitData) TrackGroup(com.google.android.exoplayer2.source.TrackGroup) HlsMultivariantPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist)

Example 9 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

the class HlsDownloader method getSegments.

@Override
protected List<Segment> getSegments(DataSource dataSource, HlsPlaylist playlist, boolean removing) throws IOException, InterruptedException {
    ArrayList<DataSpec> mediaPlaylistDataSpecs = new ArrayList<>();
    if (playlist instanceof HlsMultivariantPlaylist) {
        HlsMultivariantPlaylist multivariantPlaylist = (HlsMultivariantPlaylist) playlist;
        addMediaPlaylistDataSpecs(multivariantPlaylist.mediaPlaylistUrls, mediaPlaylistDataSpecs);
    } else {
        mediaPlaylistDataSpecs.add(SegmentDownloader.getCompressibleDataSpec(Uri.parse(playlist.baseUri)));
    }
    ArrayList<Segment> segments = new ArrayList<>();
    HashSet<Uri> seenEncryptionKeyUris = new HashSet<>();
    for (DataSpec mediaPlaylistDataSpec : mediaPlaylistDataSpecs) {
        segments.add(new Segment(/* startTimeUs= */
        0, mediaPlaylistDataSpec));
        HlsMediaPlaylist mediaPlaylist;
        try {
            mediaPlaylist = (HlsMediaPlaylist) getManifest(dataSource, mediaPlaylistDataSpec, removing);
        } catch (IOException e) {
            if (!removing) {
                throw e;
            }
            // Generating an incomplete segment list is allowed. Advance to the next media playlist.
            continue;
        }
        @Nullable HlsMediaPlaylist.Segment lastInitSegment = null;
        List<HlsMediaPlaylist.Segment> hlsSegments = mediaPlaylist.segments;
        for (int i = 0; i < hlsSegments.size(); i++) {
            HlsMediaPlaylist.Segment segment = hlsSegments.get(i);
            HlsMediaPlaylist.Segment initSegment = segment.initializationSegment;
            if (initSegment != null && initSegment != lastInitSegment) {
                lastInitSegment = initSegment;
                addSegment(mediaPlaylist, initSegment, seenEncryptionKeyUris, segments);
            }
            addSegment(mediaPlaylist, segment, seenEncryptionKeyUris, segments);
        }
    }
    return segments;
}
Also used : ArrayList(java.util.ArrayList) IOException(java.io.IOException) Uri(android.net.Uri) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) HlsMultivariantPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist) Nullable(androidx.annotation.Nullable) HashSet(java.util.HashSet)

Example 10 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

the class DefaultHlsPlaylistTracker method onLoadCompleted.

// Loader.Callback implementation.
@Override
public void onLoadCompleted(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs, long loadDurationMs) {
    HlsPlaylist result = loadable.getResult();
    HlsMultivariantPlaylist multivariantPlaylist;
    boolean isMediaPlaylist = result instanceof HlsMediaPlaylist;
    if (isMediaPlaylist) {
        multivariantPlaylist = HlsMultivariantPlaylist.createSingleVariantMultivariantPlaylist(result.baseUri);
    } else /* result instanceof HlsMultivariantPlaylist */
    {
        multivariantPlaylist = (HlsMultivariantPlaylist) result;
    }
    this.multivariantPlaylist = multivariantPlaylist;
    primaryMediaPlaylistUrl = multivariantPlaylist.variants.get(0).url;
    // Add a temporary playlist listener for loading the first primary playlist.
    listeners.add(new FirstPrimaryMediaPlaylistListener());
    createBundles(multivariantPlaylist.mediaPlaylistUrls);
    LoadEventInfo loadEventInfo = new LoadEventInfo(loadable.loadTaskId, loadable.dataSpec, loadable.getUri(), loadable.getResponseHeaders(), elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded());
    MediaPlaylistBundle primaryBundle = playlistBundles.get(primaryMediaPlaylistUrl);
    if (isMediaPlaylist) {
        // We don't need to load the playlist again. We can use the same result.
        primaryBundle.processLoadedPlaylist((HlsMediaPlaylist) result, loadEventInfo);
    } else {
        primaryBundle.loadPlaylist();
    }
    loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId);
    eventDispatcher.loadCompleted(loadEventInfo, C.DATA_TYPE_MANIFEST);
}
Also used : LoadEventInfo(com.google.android.exoplayer2.source.LoadEventInfo)

Aggregations

Format (com.google.android.exoplayer2.Format)9 Test (org.junit.Test)9 Variant (com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant)6 ArrayList (java.util.ArrayList)6 Uri (android.net.Uri)5 HlsMultivariantPlaylist (com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist)4 Nullable (androidx.annotation.Nullable)3 DrmInitData (com.google.android.exoplayer2.drm.DrmInitData)3 TrackGroup (com.google.android.exoplayer2.source.TrackGroup)3 Rendition (com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Rendition)3 SchemeData (com.google.android.exoplayer2.drm.DrmInitData.SchemeData)2 HashMap (java.util.HashMap)2 HashSet (java.util.HashSet)2 AndroidJUnit4 (androidx.test.ext.junit.runners.AndroidJUnit4)1 PlayerId (com.google.android.exoplayer2.analytics.PlayerId)1 DrmSessionEventListener (com.google.android.exoplayer2.drm.DrmSessionEventListener)1 DrmSessionManager (com.google.android.exoplayer2.drm.DrmSessionManager)1 Metadata (com.google.android.exoplayer2.metadata.Metadata)1 StreamKey (com.google.android.exoplayer2.offline.StreamKey)1 CompositeSequenceableLoaderFactory (com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory)1