diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/EditingMetricsCollector.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EditingMetricsCollector.java index 7088cab431..baa8be2faf 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EditingMetricsCollector.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EditingMetricsCollector.java @@ -16,7 +16,6 @@ package androidx.media3.transformer; -import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Util.usToMs; import android.content.Context; @@ -37,15 +36,64 @@ import androidx.media3.common.util.SystemClock; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** - * A metrics collector that collects editing events and forwards them to an {@link EditingSession} - * created by {@link MediaMetricsManager}. + * A metrics collector that collects editing events and forwards them to {@link MetricsReporter}. */ @RequiresApi(35) /* package */ final class EditingMetricsCollector { + /** Reports the collected metrics. */ + public interface MetricsReporter extends AutoCloseable { + + /** + * Reports the given {@link EditingEndedEvent}. + * + *

The method should be called at most once. + */ + void reportMetrics(EditingEndedEvent editingEndedEvent); + } + + /** + * A default implementation of {@link MetricsReporter} that reports metrics to an {@link + * EditingSession}. + */ + static final class DefaultMetricsReporter implements MetricsReporter { + + /** The {@link EditingSession} to report collected metrics to. */ + @Nullable private EditingSession editingSession; + + /** + * Creates an instance. + * + * @param context A {@link Context}. + */ + public DefaultMetricsReporter(Context context) { + @Nullable + MediaMetricsManager mediaMetricsManager = + (MediaMetricsManager) context.getSystemService(Context.MEDIA_METRICS_SERVICE); + if (mediaMetricsManager != null) { + editingSession = mediaMetricsManager.createEditingSession(); + } + } + + @Override + public void reportMetrics(EditingEndedEvent editingEndedEvent) { + if (editingSession != null) { + editingSession.reportEditingEndedEvent(editingEndedEvent); + close(); + } + } + + @Override + public void close() { + if (editingSession != null) { + editingSession.close(); + editingSession = null; + } + } + } + // TODO: b/386328723 - Add missing error codes to EditingEndedEvent.ErrorCode. private static final SparseIntArray ERROR_CODE_CONVERSION_MAP = new SparseIntArray(); private static final SparseIntArray DATA_SPACE_STANDARD_CONVERSION_MAP = new SparseIntArray(); @@ -128,10 +176,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } private static final int SUCCESS_PROGRESS_PERCENTAGE = 100; + private final long startTimeMs; private final String exporterName; @Nullable private final String muxerName; - private @MonotonicNonNull EditingSession editingSession; - private long startTimeMs; + private final MetricsReporter metricsReporter; /** * Creates an instance. @@ -141,22 +189,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; *

Both {@code exporterName} and {@code muxerName} should follow the format * ":". * - * @param context The {@link Context}. + * @param metricsReporter The {@link MetricsReporter} to report metrics. * @param exporterName Java package name and version of the library or application implementing * the editing operation. * @param muxerName Java package name and version of the library or application that writes to the * output file. */ - public EditingMetricsCollector(Context context, String exporterName, @Nullable String muxerName) { - @Nullable - MediaMetricsManager mediaMetricsManager = - (MediaMetricsManager) context.getSystemService(Context.MEDIA_METRICS_SERVICE); - if (mediaMetricsManager != null) { - editingSession = checkNotNull(mediaMetricsManager.createEditingSession()); - startTimeMs = SystemClock.DEFAULT.elapsedRealtime(); - } + public EditingMetricsCollector( + MetricsReporter metricsReporter, String exporterName, @Nullable String muxerName) { + this.metricsReporter = metricsReporter; this.exporterName = exporterName; this.muxerName = muxerName; + startTimeMs = SystemClock.DEFAULT.elapsedRealtime(); } /** @@ -165,9 +209,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param exportResult The {@link ExportResult} of the export. */ public void onExportSuccess(ExportResult exportResult) { - if (editingSession == null) { - return; - } EditingEndedEvent.Builder editingEndedEventBuilder = createEditingEndedEventBuilder(EditingEndedEvent.FINAL_STATE_SUCCEEDED) .setFinalProgressPercent(SUCCESS_PROGRESS_PERCENTAGE); @@ -180,8 +221,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } editingEndedEventBuilder.setOutputMediaItemInfo(getOutputMediaItemInfo(exportResult)); - editingSession.reportEditingEndedEvent(editingEndedEventBuilder.build()); - editingSession.close(); + metricsReporter.reportMetrics(editingEndedEventBuilder.build()); } /** @@ -194,9 +234,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; */ public void onExportError( int progressPercentage, ExportException exportException, ExportResult exportResult) { - if (editingSession == null) { - return; - } EditingEndedEvent.Builder editingEndedEventBuilder = createEditingEndedEventBuilder(EditingEndedEvent.FINAL_STATE_ERROR) .setErrorCode(getEditingEndedEventErrorCode(exportException.errorCode)); @@ -212,8 +249,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } editingEndedEventBuilder.setOutputMediaItemInfo(getOutputMediaItemInfo(exportResult)); - editingSession.reportEditingEndedEvent(editingEndedEventBuilder.build()); - editingSession.close(); + metricsReporter.reportMetrics(editingEndedEventBuilder.build()); } /** @@ -223,16 +259,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * C#PERCENTAGE_UNSET} if unknown or between 0 and 100 inclusive. */ public void onExportCancelled(int progressPercentage) { - if (editingSession == null) { - return; - } EditingEndedEvent.Builder editingEndedEventBuilder = createEditingEndedEventBuilder(EditingEndedEvent.FINAL_STATE_CANCELED); if (progressPercentage != C.PERCENTAGE_UNSET) { editingEndedEventBuilder.setFinalProgressPercent(progressPercentage); } - editingSession.reportEditingEndedEvent(editingEndedEventBuilder.build()); - editingSession.close(); + + metricsReporter.reportMetrics(editingEndedEventBuilder.build()); } private EditingEndedEvent.Builder createEditingEndedEventBuilder(int finalState) { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java index 551c5fe20b..e7871e38cf 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java @@ -1588,7 +1588,11 @@ public final class Transformer { } else if (muxerFactory instanceof DefaultMuxer.Factory) { muxerName = DefaultMuxer.MUXER_NAME; } - editingMetricsCollector = new EditingMetricsCollector(context, EXPORTER_NAME, muxerName); + editingMetricsCollector = + new EditingMetricsCollector( + new EditingMetricsCollector.DefaultMetricsReporter(context), + EXPORTER_NAME, + muxerName); } transformerInternal = new TransformerInternal( diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/EditingMetricsCollectorTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/EditingMetricsCollectorTest.java new file mode 100644 index 0000000000..7b30cfc1c9 --- /dev/null +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/EditingMetricsCollectorTest.java @@ -0,0 +1,309 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.transformer; + +import static androidx.media3.common.util.Util.usToMs; +import static com.google.common.truth.Truth.assertThat; + +import android.hardware.DataSpace; +import android.media.metrics.EditingEndedEvent; +import android.media.metrics.MediaItemInfo; +import android.util.Size; +import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; +import androidx.media3.common.Format; +import androidx.media3.common.MediaItem; +import androidx.media3.common.MediaLibraryInfo; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Unit tests for {@link EditingMetricsCollector}. */ +@Config(minSdk = 35) +@RunWith(AndroidJUnit4.class) +public final class EditingMetricsCollectorTest { + + private static final int MEDIA_DURATION_US = 1_000_000_000; + private static final String EXPORTER_NAME = + "androidx.media3.media3-transformer:" + MediaLibraryInfo.VERSION; + private static final String MUXER_NAME = + "androidx.media3.media3-muxer:" + MediaLibraryInfo.VERSION; + private static final String VIDEO_MIME_TYPE = "video/hevc"; + private static final String AUDIO_MIME_TYPE = "audio/mp4a-latm"; + private static final String VIDEO_ENCODER_NAME = "c2.android.hevc.encoder"; + private static final String AUDIO_ENCODER_NAME = "c2.android.aac.encoder"; + private static final String VIDEO_DECODER_NAME = "c2.android.hevc.decoder"; + private static final String AUDIO_DECODER_NAME = "c2.android.aac.decoder"; + private static final float VIDEO_FRAME_RATE = 30.0f; + private static final Size VIDEO_SIZE = new Size(/* width= */ 1920, /* height= */ 1080); + private static final ColorInfo VIDEO_COLOR_INFO = + new ColorInfo.Builder() + .setColorSpace(C.COLOR_SPACE_BT2020) + .setColorTransfer(C.COLOR_TRANSFER_ST2084) + .setColorRange(C.COLOR_RANGE_LIMITED) + .build(); + private static final int AUDIO_SAMPLE_RATE = 48_000; + private static final int AUDIO_CHANNEL_COUNT = 2; + private static final Format FAKE_AUDIO_FORMAT = + new Format.Builder() + .setSampleMimeType(AUDIO_MIME_TYPE) + .setSampleRate(AUDIO_SAMPLE_RATE) + .setChannelCount(AUDIO_CHANNEL_COUNT) + .setPcmEncoding(C.ENCODING_PCM_16BIT) + .build(); + private static final Format FAKE_VIDEO_FORMAT = + new Format.Builder() + .setContainerMimeType(VIDEO_MIME_TYPE) + .setSampleMimeType(VIDEO_MIME_TYPE) + .setFrameRate(VIDEO_FRAME_RATE) + .setWidth(VIDEO_SIZE.getWidth()) + .setHeight(VIDEO_SIZE.getHeight()) + .setColorInfo(VIDEO_COLOR_INFO) + .build(); + + @Test + public void onExportSuccess_populatesEditingEndedEvent() { + List processedInputs = new ArrayList<>(); + processedInputs.add( + new ExportResult.ProcessedInput( + MediaItem.EMPTY, + MEDIA_DURATION_US, + FAKE_AUDIO_FORMAT, + FAKE_VIDEO_FORMAT, + AUDIO_DECODER_NAME, + VIDEO_DECODER_NAME)); + ExportResult exportResult = + new ExportResult.Builder() + .setDurationMs(usToMs(MEDIA_DURATION_US)) + .setAudioMimeType(AUDIO_MIME_TYPE) + .setVideoMimeType(VIDEO_MIME_TYPE) + .setChannelCount(AUDIO_CHANNEL_COUNT) + .setSampleRate(AUDIO_SAMPLE_RATE) + .setAudioEncoderName(AUDIO_ENCODER_NAME) + .setVideoEncoderName(VIDEO_ENCODER_NAME) + .setVideoFrameCount(2400) + .setWidth(VIDEO_SIZE.getWidth()) + .setHeight(VIDEO_SIZE.getHeight()) + .setColorInfo(VIDEO_COLOR_INFO) + .addProcessedInputs(processedInputs) + .build(); + AtomicReference editingEndedEventAtomicReference = new AtomicReference<>(); + EditingMetricsCollector editingMetricsCollector = + new EditingMetricsCollector( + new EditingMetricsCollector.MetricsReporter() { + @Override + public void reportMetrics(EditingEndedEvent editingEndedEvent) { + editingEndedEventAtomicReference.set(editingEndedEvent); + } + + @Override + public void close() {} + }, + EXPORTER_NAME, + MUXER_NAME); + + editingMetricsCollector.onExportSuccess(exportResult); + + EditingEndedEvent editingEndedEvent = editingEndedEventAtomicReference.get(); + assertThat(editingEndedEvent.getFinalState()) + .isEqualTo(EditingEndedEvent.FINAL_STATE_SUCCEEDED); + assertThat(editingEndedEvent.getTimeSinceCreatedMillis()).isAtLeast(0); + assertThat(editingEndedEvent.getExporterName()).isEqualTo(EXPORTER_NAME); + assertThat(editingEndedEvent.getMuxerName()).isEqualTo(MUXER_NAME); + assertThat(editingEndedEvent.getFinalProgressPercent()).isEqualTo(100); + assertThat(editingEndedEvent.getInputMediaItemInfos()).hasSize(1); + // Assert input media items information + MediaItemInfo inputMediaItemInfo = editingEndedEvent.getInputMediaItemInfos().get(0); + assertThat(inputMediaItemInfo.getClipDurationMillis()) + .isEqualTo(usToMs(processedInputs.get(0).durationUs)); + assertThat(inputMediaItemInfo.getCodecNames().get(0)).isEqualTo(VIDEO_DECODER_NAME); + assertThat(inputMediaItemInfo.getCodecNames().get(1)).isEqualTo(AUDIO_DECODER_NAME); + assertThat(inputMediaItemInfo.getContainerMimeType()).isEqualTo(VIDEO_MIME_TYPE); + assertThat(inputMediaItemInfo.getSampleMimeTypes()).hasSize(2); + assertThat(inputMediaItemInfo.getSampleMimeTypes().get(0)) + .isAnyOf(AUDIO_MIME_TYPE, VIDEO_MIME_TYPE); + assertThat(inputMediaItemInfo.getSampleMimeTypes().get(1)) + .isAnyOf(AUDIO_MIME_TYPE, VIDEO_MIME_TYPE); + assertThat(inputMediaItemInfo.getDataTypes()) + .isEqualTo(MediaItemInfo.DATA_TYPE_VIDEO | MediaItemInfo.DATA_TYPE_AUDIO); + assertThat(inputMediaItemInfo.getVideoFrameRate()).isEqualTo(VIDEO_FRAME_RATE); + assertThat(inputMediaItemInfo.getVideoSize()).isEqualTo(VIDEO_SIZE); + assertThat(inputMediaItemInfo.getVideoDataSpace()) + .isEqualTo( + DataSpace.pack( + DataSpace.STANDARD_BT2020, DataSpace.TRANSFER_ST2084, DataSpace.RANGE_LIMITED)); + assertThat(inputMediaItemInfo.getAudioChannelCount()).isEqualTo(AUDIO_CHANNEL_COUNT); + assertThat(inputMediaItemInfo.getAudioSampleRateHz()).isEqualTo(AUDIO_SAMPLE_RATE); + // Assert output media item information + MediaItemInfo outputMediaItemInfo = editingEndedEvent.getOutputMediaItemInfo(); + assertThat(outputMediaItemInfo).isNotNull(); + assertThat(outputMediaItemInfo.getDurationMillis()).isEqualTo(exportResult.durationMs); + assertThat(outputMediaItemInfo.getSampleMimeTypes()).hasSize(2); + assertThat(outputMediaItemInfo.getSampleMimeTypes().get(0)) + .isAnyOf(exportResult.audioMimeType, exportResult.videoMimeType); + assertThat(outputMediaItemInfo.getSampleMimeTypes().get(1)) + .isAnyOf(exportResult.audioMimeType, exportResult.videoMimeType); + assertThat(outputMediaItemInfo.getAudioChannelCount()).isEqualTo(exportResult.channelCount); + assertThat(outputMediaItemInfo.getAudioSampleRateHz()).isEqualTo(exportResult.sampleRate); + assertThat(outputMediaItemInfo.getCodecNames()).hasSize(2); + assertThat(outputMediaItemInfo.getCodecNames().get(0)) + .isAnyOf(exportResult.audioEncoderName, exportResult.videoEncoderName); + assertThat(outputMediaItemInfo.getCodecNames().get(1)) + .isAnyOf(exportResult.audioEncoderName, exportResult.videoEncoderName); + assertThat(outputMediaItemInfo.getVideoSampleCount()).isEqualTo(exportResult.videoFrameCount); + assertThat(outputMediaItemInfo.getVideoSize()) + .isEqualTo(new Size(exportResult.width, exportResult.height)); + assertThat(inputMediaItemInfo.getVideoDataSpace()) + .isEqualTo( + DataSpace.pack( + DataSpace.STANDARD_BT2020, DataSpace.TRANSFER_ST2084, DataSpace.RANGE_LIMITED)); + } + + @Test + public void onExportError_populatesEditingEndedEvent() { + List processedInputs = new ArrayList<>(); + processedInputs.add( + new ExportResult.ProcessedInput( + MediaItem.EMPTY, + MEDIA_DURATION_US, + FAKE_AUDIO_FORMAT, + FAKE_VIDEO_FORMAT, + AUDIO_DECODER_NAME, + VIDEO_DECODER_NAME)); + ExportResult exportResult = + new ExportResult.Builder() + .setDurationMs(usToMs(MEDIA_DURATION_US)) + .setAudioMimeType(AUDIO_MIME_TYPE) + .setVideoMimeType(VIDEO_MIME_TYPE) + .setChannelCount(AUDIO_CHANNEL_COUNT) + .setSampleRate(AUDIO_SAMPLE_RATE) + .setAudioEncoderName(AUDIO_ENCODER_NAME) + .setVideoEncoderName(VIDEO_ENCODER_NAME) + .setVideoFrameCount(2400) + .setWidth(VIDEO_SIZE.getWidth()) + .setHeight(VIDEO_SIZE.getHeight()) + .setColorInfo(VIDEO_COLOR_INFO) + .addProcessedInputs(processedInputs) + .build(); + ExportException exception = + ExportException.createForMuxer( + new RuntimeException(), ExportException.ERROR_CODE_MUXING_FAILED); + AtomicReference editingEndedEventAtomicReference = new AtomicReference<>(); + EditingMetricsCollector editingMetricsCollector = + new EditingMetricsCollector( + new EditingMetricsCollector.MetricsReporter() { + @Override + public void reportMetrics(EditingEndedEvent editingEndedEvent) { + editingEndedEventAtomicReference.set(editingEndedEvent); + } + + @Override + public void close() {} + }, + EXPORTER_NAME, + MUXER_NAME); + int progressPercentage = 10; + + editingMetricsCollector.onExportError(progressPercentage, exception, exportResult); + + EditingEndedEvent editingEndedEvent = editingEndedEventAtomicReference.get(); + assertThat(editingEndedEvent.getFinalState()).isEqualTo(EditingEndedEvent.FINAL_STATE_ERROR); + assertThat(editingEndedEvent.getTimeSinceCreatedMillis()).isAtLeast(0); + assertThat(editingEndedEvent.getExporterName()).isEqualTo(EXPORTER_NAME); + assertThat(editingEndedEvent.getMuxerName()).isEqualTo(MUXER_NAME); + assertThat(editingEndedEvent.getFinalProgressPercent()).isEqualTo(progressPercentage); + assertThat(editingEndedEvent.getErrorCode()) + .isEqualTo(EditingEndedEvent.ERROR_CODE_MUXING_FAILED); + // Assert input media items information + assertThat(editingEndedEvent.getInputMediaItemInfos()).hasSize(1); + MediaItemInfo inputMediaItemInfo = editingEndedEvent.getInputMediaItemInfos().get(0); + assertThat(inputMediaItemInfo.getClipDurationMillis()) + .isEqualTo(usToMs(processedInputs.get(0).durationUs)); + assertThat(inputMediaItemInfo.getCodecNames().get(0)).isEqualTo(VIDEO_DECODER_NAME); + assertThat(inputMediaItemInfo.getCodecNames().get(1)).isEqualTo(AUDIO_DECODER_NAME); + assertThat(inputMediaItemInfo.getContainerMimeType()).isEqualTo(VIDEO_MIME_TYPE); + assertThat(inputMediaItemInfo.getSampleMimeTypes()).hasSize(2); + assertThat(inputMediaItemInfo.getSampleMimeTypes().get(0)) + .isAnyOf(AUDIO_MIME_TYPE, VIDEO_MIME_TYPE); + assertThat(inputMediaItemInfo.getSampleMimeTypes().get(1)) + .isAnyOf(AUDIO_MIME_TYPE, VIDEO_MIME_TYPE); + assertThat(inputMediaItemInfo.getDataTypes()) + .isEqualTo(MediaItemInfo.DATA_TYPE_VIDEO | MediaItemInfo.DATA_TYPE_AUDIO); + assertThat(inputMediaItemInfo.getVideoFrameRate()).isEqualTo(VIDEO_FRAME_RATE); + assertThat(inputMediaItemInfo.getVideoSize()).isEqualTo(VIDEO_SIZE); + assertThat(inputMediaItemInfo.getVideoDataSpace()) + .isEqualTo( + DataSpace.pack( + DataSpace.STANDARD_BT2020, DataSpace.TRANSFER_ST2084, DataSpace.RANGE_LIMITED)); + assertThat(inputMediaItemInfo.getAudioChannelCount()).isEqualTo(AUDIO_CHANNEL_COUNT); + assertThat(inputMediaItemInfo.getAudioSampleRateHz()).isEqualTo(AUDIO_SAMPLE_RATE); + // Assert output media item information + MediaItemInfo outputMediaItemInfo = editingEndedEvent.getOutputMediaItemInfo(); + assertThat(outputMediaItemInfo).isNotNull(); + assertThat(outputMediaItemInfo.getDurationMillis()).isEqualTo(exportResult.durationMs); + assertThat(outputMediaItemInfo.getSampleMimeTypes()).hasSize(2); + assertThat(outputMediaItemInfo.getSampleMimeTypes().get(0)) + .isAnyOf(exportResult.audioMimeType, exportResult.videoMimeType); + assertThat(outputMediaItemInfo.getSampleMimeTypes().get(1)) + .isAnyOf(exportResult.audioMimeType, exportResult.videoMimeType); + assertThat(outputMediaItemInfo.getAudioChannelCount()).isEqualTo(exportResult.channelCount); + assertThat(outputMediaItemInfo.getAudioSampleRateHz()).isEqualTo(exportResult.sampleRate); + assertThat(outputMediaItemInfo.getCodecNames()).hasSize(2); + assertThat(outputMediaItemInfo.getCodecNames().get(0)) + .isAnyOf(exportResult.audioEncoderName, exportResult.videoEncoderName); + assertThat(outputMediaItemInfo.getCodecNames().get(1)) + .isAnyOf(exportResult.audioEncoderName, exportResult.videoEncoderName); + assertThat(outputMediaItemInfo.getVideoSampleCount()).isEqualTo(exportResult.videoFrameCount); + assertThat(outputMediaItemInfo.getVideoSize()) + .isEqualTo(new Size(exportResult.width, exportResult.height)); + assertThat(inputMediaItemInfo.getVideoDataSpace()) + .isEqualTo( + DataSpace.pack( + DataSpace.STANDARD_BT2020, DataSpace.TRANSFER_ST2084, DataSpace.RANGE_LIMITED)); + } + + @Test + public void onExportCancelled_populatesEditingEndedEvent() { + AtomicReference editingEndedEventAtomicReference = new AtomicReference<>(); + EditingMetricsCollector editingMetricsCollector = + new EditingMetricsCollector( + new EditingMetricsCollector.MetricsReporter() { + @Override + public void reportMetrics(EditingEndedEvent editingEndedEvent) { + editingEndedEventAtomicReference.set(editingEndedEvent); + } + + @Override + public void close() {} + }, + EXPORTER_NAME, + MUXER_NAME); + int progressPercentage = 70; + + editingMetricsCollector.onExportCancelled(progressPercentage); + + EditingEndedEvent editingEndedEvent = editingEndedEventAtomicReference.get(); + assertThat(editingEndedEvent.getFinalState()).isEqualTo(EditingEndedEvent.FINAL_STATE_CANCELED); + assertThat(editingEndedEvent.getTimeSinceCreatedMillis()).isAtLeast(0); + assertThat(editingEndedEvent.getExporterName()).isEqualTo(EXPORTER_NAME); + assertThat(editingEndedEvent.getMuxerName()).isEqualTo(MUXER_NAME); + assertThat(editingEndedEvent.getFinalProgressPercent()).isEqualTo(progressPercentage); + } +}