Search in sources :

Example 1 with Cue

use of androidx.media3.common.text.Cue in project media by androidx.

the class WebvttExtractor method processSample.

@RequiresNonNull("output")
private void processSample() throws ParserException {
    ParsableByteArray webvttData = new ParsableByteArray(sampleData);
    // Validate the first line of the header.
    WebvttParserUtil.validateWebvttHeaderLine(webvttData);
    // Defaults to use if the header doesn't contain an X-TIMESTAMP-MAP header.
    long vttTimestampUs = 0;
    long tsTimestampUs = 0;
    // Parse the remainder of the header looking for X-TIMESTAMP-MAP.
    for (String line = webvttData.readLine(); !TextUtils.isEmpty(line); line = webvttData.readLine()) {
        if (line.startsWith("X-TIMESTAMP-MAP")) {
            Matcher localTimestampMatcher = LOCAL_TIMESTAMP.matcher(line);
            if (!localTimestampMatcher.find()) {
                throw ParserException.createForMalformedContainer("X-TIMESTAMP-MAP doesn't contain local timestamp: " + line, /* cause= */
                null);
            }
            Matcher mediaTimestampMatcher = MEDIA_TIMESTAMP.matcher(line);
            if (!mediaTimestampMatcher.find()) {
                throw ParserException.createForMalformedContainer("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line, /* cause= */
                null);
            }
            vttTimestampUs = WebvttParserUtil.parseTimestampUs(Assertions.checkNotNull(localTimestampMatcher.group(1)));
            tsTimestampUs = TimestampAdjuster.ptsToUs(Long.parseLong(Assertions.checkNotNull(mediaTimestampMatcher.group(1))));
        }
    }
    // Find the first cue header and parse the start time.
    Matcher cueHeaderMatcher = WebvttParserUtil.findNextCueHeader(webvttData);
    if (cueHeaderMatcher == null) {
        // No cues found. Don't output a sample, but still output a corresponding track.
        buildTrackOutput(0);
        return;
    }
    long firstCueTimeUs = WebvttParserUtil.parseTimestampUs(Assertions.checkNotNull(cueHeaderMatcher.group(1)));
    long sampleTimeUs = timestampAdjuster.adjustTsTimestamp(TimestampAdjuster.usToWrappedPts(firstCueTimeUs + tsTimestampUs - vttTimestampUs));
    long subsampleOffsetUs = sampleTimeUs - firstCueTimeUs;
    // Output the track.
    TrackOutput trackOutput = buildTrackOutput(subsampleOffsetUs);
    // Output the sample.
    sampleDataWrapper.reset(sampleData, sampleSize);
    trackOutput.sampleData(sampleDataWrapper, sampleSize);
    trackOutput.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null);
}
Also used : ParsableByteArray(androidx.media3.common.util.ParsableByteArray) Matcher(java.util.regex.Matcher) TrackOutput(androidx.media3.extractor.TrackOutput) RequiresNonNull(org.checkerframework.checker.nullness.qual.RequiresNonNull)

Example 2 with Cue

use of androidx.media3.common.text.Cue in project media by androidx.

the class ImaAdsLoaderTest method loadAd_withLargeAdCuePoint_updatesAdPlaybackStateWithLoadedAd.

@Test
public void loadAd_withLargeAdCuePoint_updatesAdPlaybackStateWithLoadedAd() {
    // Use a large enough value to test correct truncating of large cue points.
    float midrollTimeSecs = Float.MAX_VALUE;
    List<Float> cuePoints = ImmutableList.of(midrollTimeSecs);
    when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
    imaAdsLoader.start(adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
    videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, new AdPodInfo() {

        @Override
        public int getTotalAds() {
            return 1;
        }

        @Override
        public int getAdPosition() {
            return 1;
        }

        @Override
        public boolean isBumper() {
            return false;
        }

        @Override
        public double getMaxDuration() {
            return 0;
        }

        @Override
        public int getPodIndex() {
            return 0;
        }

        @Override
        public double getTimeOffset() {
            return midrollTimeSecs;
        }
    });
    assertThat(getAdPlaybackState(/* periodIndex= */
    0)).isEqualTo(new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)).withContentDurationUs(CONTENT_PERIOD_DURATION_US).withAdCount(/* adGroupIndex= */
    0, /* adCount= */
    1).withAdUri(/* adGroupIndex= */
    0, /* adIndexInAdGroup= */
    0, TEST_URI).withAdDurationsUs(new long[][] { { TEST_AD_DURATION_US } }));
}
Also used : AdPlaybackState(androidx.media3.common.AdPlaybackState) AdPodInfo(com.google.ads.interactivemedia.v3.api.AdPodInfo) Test(org.junit.Test)

Example 3 with Cue

use of androidx.media3.common.text.Cue in project media by androidx.

the class ImaAdsLoaderTest method playbackWithTwoAdsMediaSources_preloadsSecondAdTagWithBackgroundResume.

@Test
public void playbackWithTwoAdsMediaSources_preloadsSecondAdTagWithBackgroundResume() {
    Object secondAdsId = new Object();
    AdsMediaSource secondAdsMediaSource = new AdsMediaSource(new FakeMediaSource(CONTENT_TIMELINE), TEST_DATA_SPEC, secondAdsId, new DefaultMediaSourceFactory((Context) getApplicationContext()), imaAdsLoader, adViewProvider);
    timelineWindowDefinitions = new TimelineWindowDefinition[] { getInitialTimelineWindowDefinition(TEST_ADS_ID), getInitialTimelineWindowDefinition(secondAdsId) };
    TestAdsLoaderListener secondAdsLoaderListener = new TestAdsLoaderListener(/* periodIndex= */
    1);
    // Load and play the preroll ad then content.
    imaAdsLoader.start(adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
    adEventListener.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
    videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
    adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
    videoAdPlayer.playAd(TEST_AD_MEDIA_INFO);
    fakePlayer.setPlayingAdPosition(/* periodIndex= */
    0, /* adGroupIndex= */
    0, /* adIndexInAdGroup= */
    0, /* position= */
    0, /* contentPosition= */
    0);
    fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */
    true);
    adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
    adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
    adEventListener.onAdEvent(getAdEvent(AdEventType.MIDPOINT, mockPrerollSingleAd));
    adEventListener.onAdEvent(getAdEvent(AdEventType.THIRD_QUARTILE, mockPrerollSingleAd));
    fakePlayer.setPlayingContentPosition(/* periodIndex= */
    0, /* positionMs= */
    0);
    videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
    adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */
    null));
    // Simulate starting to buffer the second ads media source.
    imaAdsLoader.start(secondAdsMediaSource, TEST_DATA_SPEC, secondAdsId, adViewProvider, secondAdsLoaderListener);
    // Simulate backgrounding/resuming.
    imaAdsLoader.stop(adsMediaSource, adsLoaderListener);
    imaAdsLoader.stop(secondAdsMediaSource, secondAdsLoaderListener);
    imaAdsLoader.start(adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
    imaAdsLoader.start(secondAdsMediaSource, TEST_DATA_SPEC, secondAdsId, adViewProvider, secondAdsLoaderListener);
    // Verify that the preroll ad has been marked as played.
    assertThat(getAdPlaybackState(/* periodIndex= */
    0)).isEqualTo(new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */
    0).withContentDurationUs(CONTENT_PERIOD_DURATION_US).withAdCount(/* adGroupIndex= */
    0, /* adCount= */
    1).withAdUri(/* adGroupIndex= */
    0, /* adIndexInAdGroup= */
    0, TEST_URI).withAdDurationsUs(new long[][] { { TEST_AD_DURATION_US } }).withPlayedAd(/* adGroupIndex= */
    0, /* adIndexInAdGroup= */
    0).withAdResumePositionUs(/* adResumePositionUs= */
    0));
    // Verify that the second source's ad cue points have preloaded.
    assertThat(getAdPlaybackState(/* periodIndex= */
    1)).isEqualTo(new AdPlaybackState(secondAdsId, /* adGroupTimesUs...= */
    0));
}
Also used : ApplicationProvider.getApplicationContext(androidx.test.core.app.ApplicationProvider.getApplicationContext) Context(android.content.Context) FakeMediaSource(androidx.media3.test.utils.FakeMediaSource) DefaultMediaSourceFactory(androidx.media3.exoplayer.source.DefaultMediaSourceFactory) AdPlaybackState(androidx.media3.common.AdPlaybackState) AdsMediaSource(androidx.media3.exoplayer.source.ads.AdsMediaSource) Test(org.junit.Test)

Example 4 with Cue

use of androidx.media3.common.text.Cue in project media by androidx.

the class ImaAdsLoaderTest method playbackWithTwoAdsMediaSources_preloadsSecondAdTag.

@Test
public void playbackWithTwoAdsMediaSources_preloadsSecondAdTag() {
    Object secondAdsId = new Object();
    AdsMediaSource secondAdsMediaSource = new AdsMediaSource(new FakeMediaSource(CONTENT_TIMELINE), TEST_DATA_SPEC, secondAdsId, new DefaultMediaSourceFactory((Context) getApplicationContext()), imaAdsLoader, adViewProvider);
    timelineWindowDefinitions = new TimelineWindowDefinition[] { getInitialTimelineWindowDefinition(TEST_ADS_ID), getInitialTimelineWindowDefinition(secondAdsId) };
    TestAdsLoaderListener secondAdsLoaderListener = new TestAdsLoaderListener(/* periodIndex= */
    1);
    // Load and play the preroll ad then content.
    imaAdsLoader.start(adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
    adEventListener.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
    videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
    adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
    videoAdPlayer.playAd(TEST_AD_MEDIA_INFO);
    fakePlayer.setPlayingAdPosition(/* periodIndex= */
    0, /* adGroupIndex= */
    0, /* adIndexInAdGroup= */
    0, /* position= */
    0, /* contentPosition= */
    0);
    fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */
    true);
    adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
    adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
    adEventListener.onAdEvent(getAdEvent(AdEventType.MIDPOINT, mockPrerollSingleAd));
    adEventListener.onAdEvent(getAdEvent(AdEventType.THIRD_QUARTILE, mockPrerollSingleAd));
    fakePlayer.setPlayingContentPosition(/* periodIndex= */
    0, /* positionMs= */
    0);
    videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
    adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */
    null));
    // Simulate starting to buffer the second ads media source.
    imaAdsLoader.start(secondAdsMediaSource, TEST_DATA_SPEC, secondAdsId, adViewProvider, secondAdsLoaderListener);
    // Verify that the preroll ad has been marked as played.
    assertThat(getAdPlaybackState(/* periodIndex= */
    0)).isEqualTo(new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */
    0).withContentDurationUs(CONTENT_PERIOD_DURATION_US).withAdCount(/* adGroupIndex= */
    0, /* adCount= */
    1).withAdUri(/* adGroupIndex= */
    0, /* adIndexInAdGroup= */
    0, TEST_URI).withAdDurationsUs(new long[][] { { TEST_AD_DURATION_US } }).withPlayedAd(/* adGroupIndex= */
    0, /* adIndexInAdGroup= */
    0).withAdResumePositionUs(/* adResumePositionUs= */
    0));
    // Verify that the second source's ad cue points have preloaded.
    assertThat(getAdPlaybackState(/* periodIndex= */
    1)).isEqualTo(new AdPlaybackState(secondAdsId, /* adGroupTimesUs...= */
    0));
}
Also used : ApplicationProvider.getApplicationContext(androidx.test.core.app.ApplicationProvider.getApplicationContext) Context(android.content.Context) FakeMediaSource(androidx.media3.test.utils.FakeMediaSource) DefaultMediaSourceFactory(androidx.media3.exoplayer.source.DefaultMediaSourceFactory) AdPlaybackState(androidx.media3.common.AdPlaybackState) AdsMediaSource(androidx.media3.exoplayer.source.ads.AdsMediaSource) Test(org.junit.Test)

Example 5 with Cue

use of androidx.media3.common.text.Cue in project media by androidx.

the class DvbParser method decode.

/**
 * Decodes a subtitling packet, returning a list of parsed {@link Cue}s.
 *
 * @param data The subtitling packet data to decode.
 * @param limit The limit in {@code data} at which to stop decoding.
 * @return The parsed {@link Cue}s.
 */
public List<Cue> decode(byte[] data, int limit) {
    // Parse the input data.
    ParsableBitArray dataBitArray = new ParsableBitArray(data, limit);
    while (// sync_byte (8) + segment header (40)
    dataBitArray.bitsLeft() >= 48 && dataBitArray.readBits(8) == 0x0F) {
        parseSubtitlingSegment(dataBitArray, subtitleService);
    }
    @Nullable PageComposition pageComposition = subtitleService.pageComposition;
    if (pageComposition == null) {
        return Collections.emptyList();
    }
    // Update the canvas bitmap if necessary.
    DisplayDefinition displayDefinition = subtitleService.displayDefinition != null ? subtitleService.displayDefinition : defaultDisplayDefinition;
    if (bitmap == null || displayDefinition.width + 1 != bitmap.getWidth() || displayDefinition.height + 1 != bitmap.getHeight()) {
        bitmap = Bitmap.createBitmap(displayDefinition.width + 1, displayDefinition.height + 1, Bitmap.Config.ARGB_8888);
        canvas.setBitmap(bitmap);
    }
    // Build the cues.
    List<Cue> cues = new ArrayList<>();
    SparseArray<PageRegion> pageRegions = pageComposition.regions;
    for (int i = 0; i < pageRegions.size(); i++) {
        // Save clean clipping state.
        canvas.save();
        PageRegion pageRegion = pageRegions.valueAt(i);
        int regionId = pageRegions.keyAt(i);
        RegionComposition regionComposition = subtitleService.regions.get(regionId);
        // Clip drawing to the current region and display definition window.
        int baseHorizontalAddress = pageRegion.horizontalAddress + displayDefinition.horizontalPositionMinimum;
        int baseVerticalAddress = pageRegion.verticalAddress + displayDefinition.verticalPositionMinimum;
        int clipRight = min(baseHorizontalAddress + regionComposition.width, displayDefinition.horizontalPositionMaximum);
        int clipBottom = min(baseVerticalAddress + regionComposition.height, displayDefinition.verticalPositionMaximum);
        canvas.clipRect(baseHorizontalAddress, baseVerticalAddress, clipRight, clipBottom);
        ClutDefinition clutDefinition = subtitleService.cluts.get(regionComposition.clutId);
        if (clutDefinition == null) {
            clutDefinition = subtitleService.ancillaryCluts.get(regionComposition.clutId);
            if (clutDefinition == null) {
                clutDefinition = defaultClutDefinition;
            }
        }
        SparseArray<RegionObject> regionObjects = regionComposition.regionObjects;
        for (int j = 0; j < regionObjects.size(); j++) {
            int objectId = regionObjects.keyAt(j);
            RegionObject regionObject = regionObjects.valueAt(j);
            ObjectData objectData = subtitleService.objects.get(objectId);
            if (objectData == null) {
                objectData = subtitleService.ancillaryObjects.get(objectId);
            }
            if (objectData != null) {
                @Nullable Paint paint = objectData.nonModifyingColorFlag ? null : defaultPaint;
                paintPixelDataSubBlocks(objectData, clutDefinition, regionComposition.depth, baseHorizontalAddress + regionObject.horizontalPosition, baseVerticalAddress + regionObject.verticalPosition, paint, canvas);
            }
        }
        if (regionComposition.fillFlag) {
            int color;
            if (regionComposition.depth == REGION_DEPTH_8_BIT) {
                color = clutDefinition.clutEntries8Bit[regionComposition.pixelCode8Bit];
            } else if (regionComposition.depth == REGION_DEPTH_4_BIT) {
                color = clutDefinition.clutEntries4Bit[regionComposition.pixelCode4Bit];
            } else {
                color = clutDefinition.clutEntries2Bit[regionComposition.pixelCode2Bit];
            }
            fillRegionPaint.setColor(color);
            canvas.drawRect(baseHorizontalAddress, baseVerticalAddress, baseHorizontalAddress + regionComposition.width, baseVerticalAddress + regionComposition.height, fillRegionPaint);
        }
        cues.add(new Cue.Builder().setBitmap(Bitmap.createBitmap(bitmap, baseHorizontalAddress, baseVerticalAddress, regionComposition.width, regionComposition.height)).setPosition((float) baseHorizontalAddress / displayDefinition.width).setPositionAnchor(Cue.ANCHOR_TYPE_START).setLine((float) baseVerticalAddress / displayDefinition.height, Cue.LINE_TYPE_FRACTION).setLineAnchor(Cue.ANCHOR_TYPE_START).setSize((float) regionComposition.width / displayDefinition.width).setBitmapHeight((float) regionComposition.height / displayDefinition.height).build());
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        // Restore clean clipping state.
        canvas.restore();
    }
    return Collections.unmodifiableList(cues);
}
Also used : ArrayList(java.util.ArrayList) Paint(android.graphics.Paint) Paint(android.graphics.Paint) Cue(androidx.media3.common.text.Cue) Nullable(androidx.annotation.Nullable) ParsableBitArray(androidx.media3.common.util.ParsableBitArray)

Aggregations

Cue (androidx.media3.common.text.Cue)58 Test (org.junit.Test)47 Nullable (androidx.annotation.Nullable)12 ArrayList (java.util.ArrayList)12 Subtitle (androidx.media3.extractor.text.Subtitle)11 Spanned (android.text.Spanned)8 Bundle (android.os.Bundle)4 UnderlineSpan (android.text.style.UnderlineSpan)4 ParsableByteArray (androidx.media3.common.util.ParsableByteArray)4 LargeTest (androidx.test.filters.LargeTest)4 List (java.util.List)4 SpannableString (android.text.SpannableString)3 AdPlaybackState (androidx.media3.common.AdPlaybackState)3 Format (androidx.media3.common.Format)3 Player (androidx.media3.common.Player)3 HorizontalTextInVerticalContextSpan (androidx.media3.common.text.HorizontalTextInVerticalContextSpan)3 RubySpan (androidx.media3.common.text.RubySpan)3 FakeExtractorOutput (androidx.media3.test.utils.FakeExtractorOutput)3 Context (android.content.Context)2 Bitmap (android.graphics.Bitmap)2