diff --git a/demos/composition/src/main/java/androidx/media3/demo/composition/CompositionPreviewViewModel.kt b/demos/composition/src/main/java/androidx/media3/demo/composition/CompositionPreviewViewModel.kt index a784c9bf937..446c8e798a8 100644 --- a/demos/composition/src/main/java/androidx/media3/demo/composition/CompositionPreviewViewModel.kt +++ b/demos/composition/src/main/java/androidx/media3/demo/composition/CompositionPreviewViewModel.kt @@ -491,13 +491,42 @@ class CompositionPreviewViewModel(application: Application) : AndroidViewModel(a return@launch } - val newItem = - Media( - durationUs = durationUs, - title = displayName, - uri = uri.toString(), - selectedEffects = emptySet(), + val uriString = uri.toString() + val newItems = mutableListOf() + if (durationUs > SPLIT_THRESHOLD_US) { + newItems.add( + Media( + durationUs = SPLIT_THRESHOLD_US, + title = "$displayName (part 1)", + uri = uriString, + selectedEffects = emptySet(), + clipStartUs = 0L, + clipEndUs = SPLIT_THRESHOLD_US, + originalDurationUs = durationUs, + ) + ) + newItems.add( + Media( + durationUs = durationUs - SPLIT_THRESHOLD_US, + title = "$displayName (part 2)", + uri = uriString, + selectedEffects = emptySet(), + clipStartUs = SPLIT_THRESHOLD_US, + clipEndUs = durationUs, + originalDurationUs = durationUs, + ) ) + Log.i(TAG, "Split media \"$displayName\" at ${SPLIT_THRESHOLD_US / 1_000_000}s into 2 parts") + } else { + newItems.add( + Media( + durationUs = durationUs, + title = displayName, + uri = uriString, + selectedEffects = emptySet(), + ) + ) + } withContext(Dispatchers.Main) { _uiState.update { currentState -> @@ -505,7 +534,7 @@ class CompositionPreviewViewModel(application: Application) : AndroidViewModel(a currentState.mediaState.selectedItemsBySequence.toMutableList() if (sequenceIndex < currentItemsBySequence.size) { val updatedItems = currentItemsBySequence[sequenceIndex].toMutableList() - updatedItems.add(newItem) + updatedItems.addAll(newItems) currentItemsBySequence[sequenceIndex] = updatedItems } currentState.copy( @@ -776,11 +805,19 @@ class CompositionPreviewViewModel(application: Application) : AndroidViewModel(a sequenceBuilder.addGap(item.durationUs) } is Media -> { - val mediaItem = + val mediaItemBuilder = MediaItem.Builder() .setUri(item.uri) .setImageDurationMs(usToMs(item.durationUs)) // Ignored for audio/video - .build() + if (item.clipStartUs > 0L || item.clipEndUs < Long.MAX_VALUE) { + val clippingConfig = + MediaItem.ClippingConfiguration.Builder() + .setStartPositionUs(item.clipStartUs) + .setEndPositionUs(item.clipEndUs) + .build() + mediaItemBuilder.setClippingConfiguration(clippingConfig) + } + val mediaItem = mediaItemBuilder.build() val effectsForItem = mutableListOf() for (effectName in item.selectedEffects) { // TODO(b/433484977): Order of applied effects should be more clear in the UI @@ -804,8 +841,9 @@ class CompositionPreviewViewModel(application: Application) : AndroidViewModel(a // Required for image inputs. For video inputs, it sets the target FPS. .setFrameRate(DEFAULT_FRAME_RATE_FPS) // Setting duration explicitly is only required for preview with CompositionPlayer, - // and is not needed for export with Transformer. - .setDurationUs(item.durationUs) + // and is not needed for export with Transformer. When clipping is used, this must + // be the original media duration so getClippedDuration can compute correctly. + .setDurationUs(item.originalDurationUs) sequenceBuilder.addItem(itemBuilder.build()) } } @@ -1018,6 +1056,7 @@ class CompositionPreviewViewModel(application: Application) : AndroidViewModel(a // and provide a stable, readable real-time playback FPS value in the UI. private const val FPS_TRACKING_DURATION_MS = 3000L private const val DEFAULT_IMAGE_DURATION_US = 1_000_000L + private const val SPLIT_THRESHOLD_US = 3_000_000L val MEDIA_TYPES = arrayOf("video/*", "image/*", "audio/*") val HDR_MODE_DESCRIPTIONS = mapOf( diff --git a/demos/composition/src/main/java/androidx/media3/demo/composition/data/CompositionState.kt b/demos/composition/src/main/java/androidx/media3/demo/composition/data/CompositionState.kt index 68eccd9749a..02734666d3b 100644 --- a/demos/composition/src/main/java/androidx/media3/demo/composition/data/CompositionState.kt +++ b/demos/composition/src/main/java/androidx/media3/demo/composition/data/CompositionState.kt @@ -89,6 +89,9 @@ data class Media( val uri: String, val selectedEffects: Set = emptySet(), val speed: Float = 1.0f, + val clipStartUs: Long = 0L, + val clipEndUs: Long = Long.MAX_VALUE, + val originalDurationUs: Long = durationUs, ) : Item { override fun copy(): Item = copy( @@ -97,6 +100,9 @@ data class Media( uri = uri, selectedEffects = selectedEffects, speed = speed, + clipStartUs = clipStartUs, + clipEndUs = clipEndUs, + originalDurationUs = originalDurationUs, ) } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/SimpleGlFrameProcessor.kt b/libraries/effect/src/main/java/androidx/media3/effect/SimpleGlFrameProcessor.kt index df46d63608d..f9d4857fd56 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/SimpleGlFrameProcessor.kt +++ b/libraries/effect/src/main/java/androidx/media3/effect/SimpleGlFrameProcessor.kt @@ -39,6 +39,7 @@ import androidx.media3.common.VideoFrameProcessingException import androidx.media3.common.util.ExperimentalApi import androidx.media3.common.util.GlProgram import androidx.media3.common.util.GlUtil +import androidx.media3.common.util.Log import androidx.media3.common.util.Size import androidx.media3.common.util.Util import androidx.media3.common.video.AsyncFrame @@ -115,6 +116,7 @@ private constructor( private val overlayMatrixProvider = OverlayMatrixProvider() private var targetFormat: Format? = null private var presentationEffect: Presentation? = null + private var lastItemIndex: Int = -1 private val lock = Any() @GuardedBy("lock") private var needsWakeup = false @@ -226,6 +228,14 @@ private constructor( inputReleaseFences = result.readCompleteFences // Step 4: Update the output frame with the new content time and metadata. + val itemIndex = firstInputFrame.metadata[KEY_ITEM_INDEX] as? Int ?: -1 + if (itemIndex != lastItemIndex) { + Log.d(TAG, "Packet changed: itemIndex=$lastItemIndex -> $itemIndex" + + " at contentTimeUs=${firstInputFrame.contentTimeUs}") + lastItemIndex = itemIndex + } + Log.d(TAG, "Processing frame: contentTimeUs=${firstInputFrame.contentTimeUs}," + + " itemIndex=$itemIndex") val updatedOutputFrame = (outputFrame.frame as HardwareBufferFrame) .buildUpon() @@ -606,6 +616,8 @@ private constructor( } companion object { + private const val TAG = "SimpleGlFrameProcessor" + private const val KEY_ITEM_INDEX = "KEY_COMPOSITION_ITEM_INDEX" private val FENCE_TIMEOUT = Duration.ofMillis(500) private val USAGE = Frame.USAGE_GPU_COLOR_OUTPUT or Frame.USAGE_GPU_SAMPLED_IMAGE }