Search in sources :

Example 6 with Part

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

the class DefaultAnalyticsCollectorTest method release_withCallbacksArrivingAfterRelease_onPlayerReleasedForwardedLast.

@Test
public void release_withCallbacksArrivingAfterRelease_onPlayerReleasedForwardedLast() throws Exception {
    FakeClock fakeClock = new FakeClock(/* initialTimeMs= */
    0, /* isAutoAdvancing= */
    true);
    ExoPlayer exoPlayer = new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).setClock(fakeClock).build();
    AnalyticsListener analyticsListener = spy(new AnalyticsListener() {

        @Override
        public void onVideoDisabled(EventTime eventTime, DecoderCounters decoderCounters) {
            // Add delay in callback to test whether event timestamp and release timestamp are
            // in the correct order.
            fakeClock.advanceTime(1);
        }
    });
    exoPlayer.addAnalyticsListener(analyticsListener);
    // Prepare with media to ensure video renderer is enabled.
    exoPlayer.setMediaSource(new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT));
    exoPlayer.prepare();
    TestPlayerRunHelper.runUntilPlaybackState(exoPlayer, Player.STATE_READY);
    // Release and add delay on releasing thread to verify timestamps of events.
    exoPlayer.release();
    long releaseTimeMs = fakeClock.currentTimeMillis();
    fakeClock.advanceTime(1);
    ShadowLooper.idleMainLooper();
    // Verify video disable events and release events arrived in order.
    ArgumentCaptor<AnalyticsListener.EventTime> videoDisabledEventTime = ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
    ArgumentCaptor<AnalyticsListener.EventTime> releasedEventTime = ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
    InOrder inOrder = inOrder(analyticsListener);
    inOrder.verify(analyticsListener).onVideoDisabled(videoDisabledEventTime.capture(), any());
    inOrder.verify(analyticsListener).onEvents(same(exoPlayer), argThat(events -> events.contains(EVENT_VIDEO_DISABLED)));
    inOrder.verify(analyticsListener).onPlayerReleased(releasedEventTime.capture());
    inOrder.verify(analyticsListener).onEvents(same(exoPlayer), argThat(events -> events.contains(EVENT_PLAYER_RELEASED)));
    // Verify order of timestamps of these events.
    // This verification is needed as a regression test against [internal ref: b/195396384]. The
    // root cause of the regression was an onPlayerReleased timestamp that was less than the
    // previously reported timestamps for other events triggered as part of the release.
    long videoDisableTimeMs = videoDisabledEventTime.getValue().realtimeMs;
    assertThat(videoDisableTimeMs).isGreaterThan(releaseTimeMs);
    assertThat(releasedEventTime.getValue().realtimeMs).isGreaterThan(videoDisableTimeMs);
}
Also used : ExoPlaybackException(com.google.android.exoplayer2.ExoPlaybackException) LoadEventInfo(com.google.android.exoplayer2.source.LoadEventInfo) ArgumentMatchers.eq(org.mockito.ArgumentMatchers.eq) EVENT_TIMELINE_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_TIMELINE_CHANGED) ExoMediaDrm(com.google.android.exoplayer2.drm.ExoMediaDrm) TestExoPlayerBuilder(com.google.android.exoplayer2.testutil.TestExoPlayerBuilder) EVENT_RENDERED_FIRST_FRAME(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_RENDERED_FIRST_FRAME) ExoPlayerTestRunner(com.google.android.exoplayer2.testutil.ExoPlayerTestRunner) MimeTypes(com.google.android.exoplayer2.util.MimeTypes) Looper(android.os.Looper) EVENT_AUDIO_DECODER_INITIALIZED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_DECODER_INITIALIZED) C(com.google.android.exoplayer2.C) EVENT_LOAD_STARTED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_STARTED) DefaultDrmSessionManager(com.google.android.exoplayer2.drm.DefaultDrmSessionManager) PlayerRunnable(com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable) Surface(android.view.Surface) EVENT_AUDIO_ENABLED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_ENABLED) Mockito.atLeastOnce(org.mockito.Mockito.atLeastOnce) FakeClock(com.google.android.exoplayer2.testutil.FakeClock) FakeAudioRenderer(com.google.android.exoplayer2.testutil.FakeAudioRenderer) Nullable(androidx.annotation.Nullable) Timeline(com.google.android.exoplayer2.Timeline) EVENT_PLAYER_ERROR(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAYER_ERROR) RobolectricUtil(com.google.android.exoplayer2.robolectric.RobolectricUtil) ArgumentMatchers.anyFloat(org.mockito.ArgumentMatchers.anyFloat) EVENT_POSITION_DISCONTINUITY(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_POSITION_DISCONTINUITY) EVENT_VIDEO_INPUT_FORMAT_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_INPUT_FORMAT_CHANGED) EVENT_IS_PLAYING_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_IS_PLAYING_CHANGED) Mockito.mock(org.mockito.Mockito.mock) AdPlaybackState(com.google.android.exoplayer2.source.ads.AdPlaybackState) RunWith(org.junit.runner.RunWith) PlaybackParameters(com.google.android.exoplayer2.PlaybackParameters) ArgumentMatchers.anyBoolean(org.mockito.ArgumentMatchers.anyBoolean) Mockito.spy(org.mockito.Mockito.spy) EVENT_PLAYBACK_PARAMETERS_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAYBACK_PARAMETERS_CHANGED) TestUtil(com.google.android.exoplayer2.testutil.TestUtil) EVENT_VIDEO_SIZE_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_SIZE_CHANGED) ArrayList(java.util.ArrayList) ExoPlayer(com.google.android.exoplayer2.ExoPlayer) EVENT_VIDEO_FRAME_PROCESSING_OFFSET(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_FRAME_PROCESSING_OFFSET) MediaDrmCallbackException(com.google.android.exoplayer2.drm.MediaDrmCallbackException) EVENT_DRM_SESSION_ACQUIRED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_SESSION_ACQUIRED) Clock(com.google.android.exoplayer2.util.Clock) ShadowLooper(org.robolectric.shadows.ShadowLooper) IOException(java.io.IOException) Test(org.junit.Test) EVENT_VIDEO_DISABLED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_DISABLED) TestPlayerRunHelper(com.google.android.exoplayer2.robolectric.TestPlayerRunHelper) TracksInfo(com.google.android.exoplayer2.TracksInfo) DrmSessionManager(com.google.android.exoplayer2.drm.DrmSessionManager) DrmInitData(com.google.android.exoplayer2.drm.DrmInitData) ArgumentMatchers.same(org.mockito.ArgumentMatchers.same) Util(com.google.android.exoplayer2.util.Util) EVENT_VIDEO_ENABLED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_ENABLED) MediaPeriodId(com.google.android.exoplayer2.source.MediaSource.MediaPeriodId) ArgumentMatchers.argThat(org.mockito.ArgumentMatchers.argThat) ConcatenatingMediaSource(com.google.android.exoplayer2.source.ConcatenatingMediaSource) Player(com.google.android.exoplayer2.Player) EVENT_VIDEO_DECODER_INITIALIZED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_DECODER_INITIALIZED) AndroidJUnit4(androidx.test.ext.junit.runners.AndroidJUnit4) ApplicationProvider(androidx.test.core.app.ApplicationProvider) Format(com.google.android.exoplayer2.Format) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) EVENT_IS_LOADING_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_IS_LOADING_CHANGED) Method(java.lang.reflect.Method) MediaSource(com.google.android.exoplayer2.source.MediaSource) ActionSchedule(com.google.android.exoplayer2.testutil.ActionSchedule) EVENT_PLAYER_RELEASED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAYER_RELEASED) RenderersFactory(com.google.android.exoplayer2.RenderersFactory) EVENT_PLAYBACK_STATE_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAYBACK_STATE_CHANGED) MediaItem(com.google.android.exoplayer2.MediaItem) EVENT_DRM_KEYS_LOADED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_KEYS_LOADED) END_OF_STREAM_ITEM(com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM) EVENT_TRACKS_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_TRACKS_CHANGED) UUID(java.util.UUID) EVENT_DRM_SESSION_MANAGER_ERROR(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_SESSION_MANAGER_ERROR) SparseArray(android.util.SparseArray) FakeSampleStreamItem.oneByteSample(com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample) DrmSession(com.google.android.exoplayer2.drm.DrmSession) List(java.util.List) FakeTimeline(com.google.android.exoplayer2.testutil.FakeTimeline) Mockito.inOrder(org.mockito.Mockito.inOrder) EVENT_DRM_SESSION_RELEASED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_SESSION_RELEASED) PlaybackException(com.google.android.exoplayer2.PlaybackException) MediaLoadData(com.google.android.exoplayer2.source.MediaLoadData) FakeVideoRenderer(com.google.android.exoplayer2.testutil.FakeVideoRenderer) EVENT_AUDIO_POSITION_ADVANCING(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_POSITION_ADVANCING) Metadata(com.google.android.exoplayer2.metadata.Metadata) EVENT_LOAD_COMPLETED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_COMPLETED) ArgumentMatchers.any(org.mockito.ArgumentMatchers.any) DecoderCounters(com.google.android.exoplayer2.decoder.DecoderCounters) ArgumentMatchers.anyLong(org.mockito.ArgumentMatchers.anyLong) TimelineWindowDefinition(com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition) AtomicReference(java.util.concurrent.atomic.AtomicReference) EVENT_LOAD_ERROR(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_ERROR) ArgumentCaptor(org.mockito.ArgumentCaptor) ImmutableList(com.google.common.collect.ImmutableList) FakeRenderer(com.google.android.exoplayer2.testutil.FakeRenderer) ConditionVariable(com.google.android.exoplayer2.util.ConditionVariable) ArgumentMatchers.anyInt(org.mockito.ArgumentMatchers.anyInt) VideoSize(com.google.android.exoplayer2.video.VideoSize) SurfaceTexture(android.graphics.SurfaceTexture) Window(com.google.android.exoplayer2.Timeline.Window) EVENT_MEDIA_ITEM_TRANSITION(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_MEDIA_ITEM_TRANSITION) MediaDrmCallback(com.google.android.exoplayer2.drm.MediaDrmCallback) InOrder(org.mockito.InOrder) Iterator(java.util.Iterator) EVENT_AUDIO_DISABLED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_DISABLED) EVENT_DOWNSTREAM_FORMAT_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DOWNSTREAM_FORMAT_CHANGED) Truth.assertThat(com.google.common.truth.Truth.assertThat) EVENT_DROPPED_VIDEO_FRAMES(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DROPPED_VIDEO_FRAMES) Mockito.verify(org.mockito.Mockito.verify) EVENT_PLAY_WHEN_READY_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAY_WHEN_READY_CHANGED) FakeMediaSource(com.google.android.exoplayer2.testutil.FakeMediaSource) FakeExoMediaDrm(com.google.android.exoplayer2.testutil.FakeExoMediaDrm) EVENT_AUDIO_INPUT_FORMAT_CHANGED(com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_INPUT_FORMAT_CHANGED) Renderer(com.google.android.exoplayer2.Renderer) InOrder(org.mockito.InOrder) FakeMediaSource(com.google.android.exoplayer2.testutil.FakeMediaSource) FakeClock(com.google.android.exoplayer2.testutil.FakeClock) DecoderCounters(com.google.android.exoplayer2.decoder.DecoderCounters) ExoPlayer(com.google.android.exoplayer2.ExoPlayer) FakeTimeline(com.google.android.exoplayer2.testutil.FakeTimeline) TestExoPlayerBuilder(com.google.android.exoplayer2.testutil.TestExoPlayerBuilder) Test(org.junit.Test)

Example 7 with Part

use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part 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 Part

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

the class HlsChunkSource method getNextSegmentHolder.

@Nullable
private static SegmentBaseHolder getNextSegmentHolder(HlsMediaPlaylist mediaPlaylist, long nextMediaSequence, int nextPartIndex) {
    int segmentIndexInPlaylist = (int) (nextMediaSequence - mediaPlaylist.mediaSequence);
    if (segmentIndexInPlaylist == mediaPlaylist.segments.size()) {
        int index = nextPartIndex != C.INDEX_UNSET ? nextPartIndex : 0;
        return index < mediaPlaylist.trailingParts.size() ? new SegmentBaseHolder(mediaPlaylist.trailingParts.get(index), nextMediaSequence, index) : null;
    }
    Segment mediaSegment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
    if (nextPartIndex == C.INDEX_UNSET) {
        return new SegmentBaseHolder(mediaSegment, nextMediaSequence, /* partIndex= */
        C.INDEX_UNSET);
    }
    if (nextPartIndex < mediaSegment.parts.size()) {
        // The requested part is available in the requested segment.
        return new SegmentBaseHolder(mediaSegment.parts.get(nextPartIndex), nextMediaSequence, nextPartIndex);
    } else if (segmentIndexInPlaylist + 1 < mediaPlaylist.segments.size()) {
        // The first part of the next segment is requested, but we can use the next full segment.
        return new SegmentBaseHolder(mediaPlaylist.segments.get(segmentIndexInPlaylist + 1), nextMediaSequence + 1, /* partIndex= */
        C.INDEX_UNSET);
    } else if (!mediaPlaylist.trailingParts.isEmpty()) {
        // The part index is rolling over to the first trailing part.
        return new SegmentBaseHolder(mediaPlaylist.trailingParts.get(0), nextMediaSequence + 1, /* partIndex= */
        0);
    }
    // End of stream.
    return null;
}
Also used : Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) Nullable(androidx.annotation.Nullable)

Example 9 with Part

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

the class HlsChunkSource method getChunkPublicationState.

/**
 * Returns the publication state of the given chunk.
 *
 * @param mediaChunk The media chunk for which to evaluate the publication state.
 * @return Whether the media chunk is {@link #CHUNK_PUBLICATION_STATE_PRELOAD a preload chunk},
 *     has been {@link #CHUNK_PUBLICATION_STATE_REMOVED removed} or is definitely {@link
 *     #CHUNK_PUBLICATION_STATE_PUBLISHED published}.
 */
@ChunkPublicationState
public int getChunkPublicationState(HlsMediaChunk mediaChunk) {
    if (mediaChunk.partIndex == C.INDEX_UNSET) {
        // Chunks based on full segments can't be removed and are always published.
        return CHUNK_PUBLICATION_STATE_PUBLISHED;
    }
    Uri playlistUrl = playlistUrls[trackGroup.indexOf(mediaChunk.trackFormat)];
    HlsMediaPlaylist mediaPlaylist = checkNotNull(playlistTracker.getPlaylistSnapshot(playlistUrl, /* isForPlayback= */
    false));
    int segmentIndexInPlaylist = (int) (mediaChunk.chunkIndex - mediaPlaylist.mediaSequence);
    if (segmentIndexInPlaylist < 0) {
        // The parent segment of the previous chunk is not in the current playlist anymore.
        return CHUNK_PUBLICATION_STATE_PUBLISHED;
    }
    List<HlsMediaPlaylist.Part> partsInCurrentPlaylist = segmentIndexInPlaylist < mediaPlaylist.segments.size() ? mediaPlaylist.segments.get(segmentIndexInPlaylist).parts : mediaPlaylist.trailingParts;
    if (mediaChunk.partIndex >= partsInCurrentPlaylist.size()) {
        // sequence in the new playlist.
        return CHUNK_PUBLICATION_STATE_REMOVED;
    }
    HlsMediaPlaylist.Part newPart = partsInCurrentPlaylist.get(mediaChunk.partIndex);
    if (newPart.isPreload) {
        // The playlist did not change and the part in the new playlist is still a preload hint.
        return CHUNK_PUBLICATION_STATE_PRELOAD;
    }
    Uri newUri = Uri.parse(UriUtil.resolve(mediaPlaylist.baseUri, newPart.url));
    return Util.areEqual(newUri, mediaChunk.dataSpec.uri) ? CHUNK_PUBLICATION_STATE_PUBLISHED : CHUNK_PUBLICATION_STATE_REMOVED;
}
Also used : HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) Uri(android.net.Uri)

Example 10 with Part

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

the class HlsChunkSource method getNextChunk.

/**
 * Returns the next chunk to load.
 *
 * <p>If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream
 * has been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available
 * but the end of the stream has not been reached, {@link HlsChunkHolder#playlistUrl} is set to
 * contain the {@link Uri} that refers to the playlist that needs refreshing.
 *
 * @param playbackPositionUs The current playback position relative to the period start in
 *     microseconds. If playback of the period to which this chunk source belongs has not yet
 *     started, the value will be the starting position in the period minus the duration of any
 *     media in previous periods still to be played.
 * @param loadPositionUs The current load position relative to the period start in microseconds.
 * @param queue The queue of buffered {@link HlsMediaChunk}s.
 * @param allowEndOfStream Whether {@link HlsChunkHolder#endOfStream} is allowed to be set for
 *     non-empty media playlists. If {@code false}, the last available chunk is returned instead.
 *     If the media playlist is empty, {@link HlsChunkHolder#endOfStream} is always set.
 * @param out A holder to populate.
 */
public void getNextChunk(long playbackPositionUs, long loadPositionUs, List<HlsMediaChunk> queue, boolean allowEndOfStream, HlsChunkHolder out) {
    @Nullable HlsMediaChunk previous = queue.isEmpty() ? null : Iterables.getLast(queue);
    int oldTrackIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat);
    long bufferedDurationUs = loadPositionUs - playbackPositionUs;
    long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs);
    if (previous != null && !independentSegments) {
        // Unless segments are known to be independent, switching tracks requires downloading
        // overlapping segments. Hence we subtract the previous segment's duration from the buffered
        // duration.
        // This may affect the live-streaming adaptive track selection logic, when we compare the
        // buffered duration to time-to-live-edge to decide whether to switch. Therefore, we subtract
        // the duration of the last loaded segment from timeToLiveEdgeUs as well.
        long subtractedDurationUs = previous.getDurationUs();
        bufferedDurationUs = max(0, bufferedDurationUs - subtractedDurationUs);
        if (timeToLiveEdgeUs != C.TIME_UNSET) {
            timeToLiveEdgeUs = max(0, timeToLiveEdgeUs - subtractedDurationUs);
        }
    }
    // Select the track.
    MediaChunkIterator[] mediaChunkIterators = createMediaChunkIterators(previous, loadPositionUs);
    trackSelection.updateSelectedTrack(playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, mediaChunkIterators);
    int selectedTrackIndex = trackSelection.getSelectedIndexInTrackGroup();
    boolean switchingTrack = oldTrackIndex != selectedTrackIndex;
    Uri selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
    if (!playlistTracker.isSnapshotValid(selectedPlaylistUrl)) {
        out.playlistUrl = selectedPlaylistUrl;
        seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
        expectedPlaylistUrl = selectedPlaylistUrl;
        // Retry when playlist is refreshed.
        return;
    }
    @Nullable HlsMediaPlaylist playlist = playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */
    true);
    // playlistTracker snapshot is valid (checked by if() above), so playlist must be non-null.
    checkNotNull(playlist);
    independentSegments = playlist.hasIndependentSegments;
    updateLiveEdgeTimeUs(playlist);
    // Select the chunk.
    long startOfPlaylistInPeriodUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
    Pair<Long, Integer> nextMediaSequenceAndPartIndex = getNextMediaSequenceAndPartIndex(previous, switchingTrack, playlist, startOfPlaylistInPeriodUs, loadPositionUs);
    long chunkMediaSequence = nextMediaSequenceAndPartIndex.first;
    int partIndex = nextMediaSequenceAndPartIndex.second;
    if (chunkMediaSequence < playlist.mediaSequence && previous != null && switchingTrack) {
        // We try getting the next chunk without adapting in case that's the reason for falling
        // behind the live window.
        selectedTrackIndex = oldTrackIndex;
        selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
        playlist = playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */
        true);
        // playlistTracker snapshot is valid (checked by if() above), so playlist must be non-null.
        checkNotNull(playlist);
        startOfPlaylistInPeriodUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
        // Get the next segment/part without switching tracks.
        Pair<Long, Integer> nextMediaSequenceAndPartIndexWithoutAdapting = getNextMediaSequenceAndPartIndex(previous, /* switchingTrack= */
        false, playlist, startOfPlaylistInPeriodUs, loadPositionUs);
        chunkMediaSequence = nextMediaSequenceAndPartIndexWithoutAdapting.first;
        partIndex = nextMediaSequenceAndPartIndexWithoutAdapting.second;
    }
    if (chunkMediaSequence < playlist.mediaSequence) {
        fatalError = new BehindLiveWindowException();
        return;
    }
    @Nullable SegmentBaseHolder segmentBaseHolder = getNextSegmentHolder(playlist, chunkMediaSequence, partIndex);
    if (segmentBaseHolder == null) {
        if (!playlist.hasEndTag) {
            // Reload the playlist in case of a live stream.
            out.playlistUrl = selectedPlaylistUrl;
            seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
            expectedPlaylistUrl = selectedPlaylistUrl;
            return;
        } else if (allowEndOfStream || playlist.segments.isEmpty()) {
            out.endOfStream = true;
            return;
        }
        // Use the last segment available in case of a VOD stream.
        segmentBaseHolder = new SegmentBaseHolder(Iterables.getLast(playlist.segments), playlist.mediaSequence + playlist.segments.size() - 1, /* partIndex= */
        C.INDEX_UNSET);
    }
    // We have a valid media segment, we can discard any playlist errors at this point.
    seenExpectedPlaylistError = false;
    expectedPlaylistUrl = null;
    // Check if the media segment or its initialization segment are fully encrypted.
    @Nullable Uri initSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase.initializationSegment);
    out.chunk = maybeCreateEncryptionChunkFor(initSegmentKeyUri, selectedTrackIndex);
    if (out.chunk != null) {
        return;
    }
    @Nullable Uri mediaSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase);
    out.chunk = maybeCreateEncryptionChunkFor(mediaSegmentKeyUri, selectedTrackIndex);
    if (out.chunk != null) {
        return;
    }
    boolean shouldSpliceIn = HlsMediaChunk.shouldSpliceIn(previous, selectedPlaylistUrl, playlist, segmentBaseHolder, startOfPlaylistInPeriodUs);
    if (shouldSpliceIn && segmentBaseHolder.isPreload) {
        // becomes fully available (or the track selection selects another track).
        return;
    }
    out.chunk = HlsMediaChunk.createInstance(extractorFactory, mediaDataSource, playlistFormats[selectedTrackIndex], startOfPlaylistInPeriodUs, playlist, segmentBaseHolder, selectedPlaylistUrl, muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), isTimestampMaster, timestampAdjusterProvider, previous, /* mediaSegmentKey= */
    keyCache.get(mediaSegmentKeyUri), /* initSegmentKey= */
    keyCache.get(initSegmentKeyUri), shouldSpliceIn, playerId);
}
Also used : Uri(android.net.Uri) BaseMediaChunkIterator(com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator) MediaChunkIterator(com.google.android.exoplayer2.source.chunk.MediaChunkIterator) BehindLiveWindowException(com.google.android.exoplayer2.source.BehindLiveWindowException) HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) Nullable(androidx.annotation.Nullable)

Aggregations

Test (org.junit.Test)14 MediaItem (com.google.android.exoplayer2.MediaItem)11 Timeline (com.google.android.exoplayer2.Timeline)11 Nullable (androidx.annotation.Nullable)6 HlsMediaPlaylist (com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist)6 DataSpec (com.google.android.exoplayer2.upstream.DataSpec)6 ArrayList (java.util.ArrayList)6 Segment (com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)4 Uri (android.net.Uri)3 MediaPeriodId (com.google.android.exoplayer2.source.MediaSource.MediaPeriodId)3 SurfaceTexture (android.graphics.SurfaceTexture)2 Pair (android.util.Pair)2 Surface (android.view.Surface)2 ApplicationProvider (androidx.test.core.app.ApplicationProvider)2 AndroidJUnit4 (androidx.test.ext.junit.runners.AndroidJUnit4)2 ExoPlayer (com.google.android.exoplayer2.ExoPlayer)2 Player (com.google.android.exoplayer2.Player)2 DrmInitData (com.google.android.exoplayer2.drm.DrmInitData)2 MediaChunkIterator (com.google.android.exoplayer2.source.chunk.MediaChunkIterator)2 RequiresNonNull (org.checkerframework.checker.nullness.qual.RequiresNonNull)2