From d9cf350eb05652384519e27ef4bb2f319fae9928 Mon Sep 17 00:00:00 2001 From: simakova Date: Fri, 6 Oct 2023 03:03:26 -0700 Subject: [PATCH] Add CompositionPlayer to the Transformer demo app PiperOrigin-RevId: 571284291 --- demos/transformer/build.gradle | 1 + .../transformer/src/main/AndroidManifest.xml | 6 + .../demo/transformer/AssetItemAdapter.java | 70 ++++++++++ .../CompositionPreviewActivity.java | 126 ++++++++++++++++++ .../layout/composition_preview_activity.xml | 92 +++++++++++++ .../src/main/res/layout/preset_item.xml | 30 +++++ .../main/res/layout/transformer_activity.xml | 3 +- .../src/main/res/values/arrays.xml | 56 ++++++++ .../src/main/res/values/strings.xml | 3 + 9 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 demos/transformer/src/main/java/androidx/media3/demo/transformer/AssetItemAdapter.java create mode 100644 demos/transformer/src/main/java/androidx/media3/demo/transformer/CompositionPreviewActivity.java create mode 100644 demos/transformer/src/main/res/layout/composition_preview_activity.xml create mode 100644 demos/transformer/src/main/res/layout/preset_item.xml create mode 100644 demos/transformer/src/main/res/values/arrays.xml diff --git a/demos/transformer/build.gradle b/demos/transformer/build.gradle index a260eee977..d2a6b1e02b 100644 --- a/demos/transformer/build.gradle +++ b/demos/transformer/build.gradle @@ -77,6 +77,7 @@ dependencies { implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion implementation 'androidx.constraintlayout:constraintlayout:' + androidxConstraintLayoutVersion + implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion implementation 'androidx.multidex:multidex:' + androidxMultidexVersion implementation 'com.google.android.material:material:' + androidxMaterialVersion implementation project(modulePrefix + 'lib-effect') diff --git a/demos/transformer/src/main/AndroidManifest.xml b/demos/transformer/src/main/AndroidManifest.xml index 8c58f1ad6a..058521c85a 100644 --- a/demos/transformer/src/main/AndroidManifest.xml +++ b/demos/transformer/src/main/AndroidManifest.xml @@ -64,5 +64,11 @@ android:label="@string/app_name" android:exported="true" android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"/> + diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/AssetItemAdapter.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/AssetItemAdapter.java new file mode 100644 index 0000000000..ee03cba9d7 --- /dev/null +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/AssetItemAdapter.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 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.demo.transformer; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; +import java.util.List; + +/** A {@link RecyclerView.Adapter} that displays assets in a sequence in a {@link RecyclerView}. */ +public final class AssetItemAdapter extends RecyclerView.Adapter { + private static final String TAG = "AssetItemAdapter"; + + private final List dataSet; + + /** + * Creates a new instance + * + * @param data A list of items to populate RecyclerView with. + */ + public AssetItemAdapter(List data) { + this.dataSet = new ArrayList<>(data); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.preset_item, parent, false); + return new ViewHolder(v); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + holder.getTextView().setText(dataSet.get(position)); + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + /** A {@link RecyclerView.ViewHolder} used to build {@link AssetItemAdapter}. */ + public static final class ViewHolder extends RecyclerView.ViewHolder { + private final TextView textView; + + private ViewHolder(View view) { + super(view); + textView = view.findViewById(R.id.preset_name_text); + } + + private TextView getTextView() { + return textView; + } + } +} diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/CompositionPreviewActivity.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/CompositionPreviewActivity.java new file mode 100644 index 0000000000..7daab290db --- /dev/null +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/CompositionPreviewActivity.java @@ -0,0 +1,126 @@ +/* + * Copyright 2023 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.demo.transformer; + +import static androidx.media3.common.util.Assertions.checkStateNotNull; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatButton; +import androidx.media3.common.MediaItem; +import androidx.media3.transformer.Composition; +import androidx.media3.transformer.CompositionPlayer; +import androidx.media3.transformer.EditedMediaItem; +import androidx.media3.transformer.EditedMediaItemSequence; +import androidx.media3.ui.PlayerView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** + * An {@link Activity} that previews compositions, using {@link + * androidx.media3.transformer.CompositionPlayer}. + */ +public final class CompositionPreviewActivity extends AppCompatActivity { + private static final String TAG = "CompPreviewActivity"; + private static final ImmutableList SEQUENCE_FILE_INDICES = ImmutableList.of(0, 2); + + private @MonotonicNonNull PlayerView playerView; + private @MonotonicNonNull RecyclerView presetList; + private @MonotonicNonNull AppCompatButton previewButton; + + @Nullable private CompositionPlayer compositionPlayer; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.composition_preview_activity); + + String[] presetFileURIs = getResources().getStringArray(R.array.preset_uris); + String[] presetFileDescriptions = getResources().getStringArray(R.array.preset_descriptions); + + playerView = findViewById(R.id.composition_player_view); + presetList = findViewById(R.id.composition_preset_list); + previewButton = findViewById(R.id.preview_button); + previewButton.setOnClickListener(view -> previewComposition(view, presetFileURIs)); + + LinearLayoutManager layoutManager = + new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, /* reverseLayout= */ false); + presetList.setLayoutManager(layoutManager); + ArrayList sequenceFiles = new ArrayList<>(); + for (int i = 0; i < SEQUENCE_FILE_INDICES.size(); i++) { + if (SEQUENCE_FILE_INDICES.get(i) < presetFileDescriptions.length) { + sequenceFiles.add(presetFileDescriptions[SEQUENCE_FILE_INDICES.get(i)]); + } + } + AssetItemAdapter adapter = new AssetItemAdapter(sequenceFiles); + presetList.setAdapter(adapter); + } + + @Override + protected void onStart() { + super.onStart(); + checkStateNotNull(playerView).onResume(); + } + + @Override + protected void onStop() { + super.onStop(); + checkStateNotNull(playerView).onPause(); + releasePlayer(); + } + + private Composition prepareComposition(String[] presetFileURIs) { + List mediaItems = new ArrayList<>(); + for (int i = 0; i < SEQUENCE_FILE_INDICES.size(); i++) { + mediaItems.add( + new EditedMediaItem.Builder( + MediaItem.fromUri(presetFileURIs[SEQUENCE_FILE_INDICES.get(i)])) + .build()); + } + EditedMediaItemSequence videoSequence = + new EditedMediaItemSequence(Collections.unmodifiableList(mediaItems)); + return new Composition.Builder(ImmutableList.of(videoSequence)).build(); + } + + private void previewComposition(View view, String[] presetFileURIs) { + releasePlayer(); + Composition composition = prepareComposition(presetFileURIs); + checkStateNotNull(playerView).setPlayer(null); + + CompositionPlayer player = new CompositionPlayer(getApplicationContext(), /* looper= */ null); + this.compositionPlayer = player; + checkStateNotNull(playerView).setPlayer(compositionPlayer); + checkStateNotNull(playerView).setControllerAutoShow(false); + player.setComposition(composition); + player.prepare(); + player.play(); + } + + private void releasePlayer() { + if (compositionPlayer != null) { + compositionPlayer.release(); + compositionPlayer = null; + } + } +} diff --git a/demos/transformer/src/main/res/layout/composition_preview_activity.xml b/demos/transformer/src/main/res/layout/composition_preview_activity.xml new file mode 100644 index 0000000000..14ddd48e6e --- /dev/null +++ b/demos/transformer/src/main/res/layout/composition_preview_activity.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/transformer/src/main/res/layout/preset_item.xml b/demos/transformer/src/main/res/layout/preset_item.xml new file mode 100644 index 0000000000..ece15cc343 --- /dev/null +++ b/demos/transformer/src/main/res/layout/preset_item.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/demos/transformer/src/main/res/layout/transformer_activity.xml b/demos/transformer/src/main/res/layout/transformer_activity.xml index d3daac940d..d62e9cfe11 100644 --- a/demos/transformer/src/main/res/layout/transformer_activity.xml +++ b/demos/transformer/src/main/res/layout/transformer_activity.xml @@ -1,4 +1,5 @@ - + + + 720p H264 video and AAC audio + 1080p H265 video and AAC audio + 360p H264 video and AAC audio + 360p VP8 video and Vorbis audio + 4K H264 video and AAC audio (portrait, no B-frames) + 8k H265 video and AAC audio + Short 1080p H265 video and AAC audio + Long 180p H264 video and AAC audio + H264 video and AAC audio (portrait, H > W, 0°) + H264 video and AAC audio (portrait, H < W, 90°) + London JPG image (Plays for 5secs at 30fps) + Tokyo JPG image (Portrait, Plays for 5secs at 30fps) + SEF slow motion with 240 fps + 480p DASH (non-square pixels) + HDR (HDR10) H265 limited range video (encoding may fail) + HDR (HLG) H265 limited range video (encoding may fail) + 720p H264 video with no audio + + + https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4 + https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4 + https://html5demos.com/assets/dizzy.mp4 + https://html5demos.com/assets/dizzy.webm + https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/8k24fps_4s.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/1920w_1080h_4s.mp4 + https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_avc_aac.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_rotated_avc_aac.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg + https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg + https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/slowMotion_stopwatch_240fps_long.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/gen/screens/dash-vod-single-segment/manifest-baseline.mpd + https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/sample_video_track_only.mp4 + + diff --git a/demos/transformer/src/main/res/values/strings.xml b/demos/transformer/src/main/res/values/strings.xml index 0a3a4d2d16..4ec8f9682f 100644 --- a/demos/transformer/src/main/res/values/strings.xml +++ b/demos/transformer/src/main/res/values/strings.xml @@ -80,4 +80,7 @@ Text Text color Specify text overlay settings + Preview + Single sequence preview + Single sequence items: