Publish the transformer demo app
PiperOrigin-RevId: 424850283
This commit is contained in:
parent
857c18b2e9
commit
ce4a028829
@ -37,6 +37,7 @@ project.ext {
|
|||||||
androidxAnnotationExperimentalVersion = '1.2.0'
|
androidxAnnotationExperimentalVersion = '1.2.0'
|
||||||
androidxAppCompatVersion = '1.3.1'
|
androidxAppCompatVersion = '1.3.1'
|
||||||
androidxCollectionVersion = '1.1.0'
|
androidxCollectionVersion = '1.1.0'
|
||||||
|
androidxConstraintLayoutVersion = '2.0.4'
|
||||||
androidxCoreVersion = '1.7.0'
|
androidxCoreVersion = '1.7.0'
|
||||||
androidxFuturesVersion = '1.1.0'
|
androidxFuturesVersion = '1.1.0'
|
||||||
androidxMediaVersion = '1.4.3'
|
androidxMediaVersion = '1.4.3'
|
||||||
|
9
demos/transformer/README.md
Normal file
9
demos/transformer/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Transformer demo
|
||||||
|
|
||||||
|
This app demonstrates how to use the [Transformer][] API to modify videos, for
|
||||||
|
example by removing audio or video.
|
||||||
|
|
||||||
|
See the [demos README](../README.md) for instructions on how to build and run
|
||||||
|
this demo.
|
||||||
|
|
||||||
|
[Transformer]: https://exoplayer.dev/transforming-media.html
|
60
demos/transformer/build.gradle
Normal file
60
demos/transformer/build.gradle
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.
|
||||||
|
*/
|
||||||
|
apply from: '../../constants.gradle'
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
versionName project.ext.releaseVersion
|
||||||
|
versionCode project.ext.releaseVersionCode
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion project.ext.appTargetSdkVersion
|
||||||
|
multiDexEnabled true
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
shrinkResources true
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
// This demo app isn't indexed and doesn't have translations.
|
||||||
|
disable 'GoogleAppIndexingWarning','MissingTranslation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||||
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
|
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:' + androidxConstraintLayoutVersion
|
||||||
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
|
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||||
|
implementation project(modulePrefix + 'lib-exoplayer')
|
||||||
|
implementation project(modulePrefix + 'lib-transformer')
|
||||||
|
implementation project(modulePrefix + 'lib-ui')
|
||||||
|
}
|
61
demos/transformer/src/main/AndroidManifest.xml
Normal file
61
demos/transformer/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2021 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.
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="androidx.media3.demo.transformer">
|
||||||
|
<uses-sdk />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.AppCompat"
|
||||||
|
android:taskAffinity=""
|
||||||
|
tools:targetApi="29">
|
||||||
|
<activity android:name=".ConfigurationActivity"
|
||||||
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="androidx.media3.demo.transformer.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:scheme="http"/>
|
||||||
|
<data android:scheme="https"/>
|
||||||
|
<data android:scheme="content"/>
|
||||||
|
<data android:scheme="asset"/>
|
||||||
|
<data android:scheme="file"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity android:name=".TransformerActivity"
|
||||||
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"/>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.checkNotNull;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link Activity} that sets the configuration to use for transforming and playing media, using
|
||||||
|
* {@link TransformerActivity}.
|
||||||
|
*/
|
||||||
|
public final class ConfigurationActivity extends AppCompatActivity {
|
||||||
|
public static final String SHOULD_REMOVE_AUDIO = "should_remove_audio";
|
||||||
|
public static final String SHOULD_REMOVE_VIDEO = "should_remove_video";
|
||||||
|
public static final String SHOULD_FLATTEN_FOR_SLOW_MOTION = "should_flatten_for_slow_motion";
|
||||||
|
public static final String AUDIO_MIME_TYPE = "audio_mime_type";
|
||||||
|
public static final String VIDEO_MIME_TYPE = "video_mime_type";
|
||||||
|
public static final String RESOLUTION_HEIGHT = "resolution_height";
|
||||||
|
public static final String TRANSLATE_X = "translate_x";
|
||||||
|
public static final String TRANSLATE_Y = "translate_y";
|
||||||
|
public static final String SCALE_X = "scale_x";
|
||||||
|
public static final String SCALE_Y = "scale_y";
|
||||||
|
public static final String ROTATE_DEGREES = "rotate_degrees";
|
||||||
|
private static final String[] INPUT_URIS = {
|
||||||
|
"https://html5demos.com/assets/dizzy.mp4",
|
||||||
|
"https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4",
|
||||||
|
"https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4",
|
||||||
|
"https://html5demos.com/assets/dizzy.webm",
|
||||||
|
};
|
||||||
|
private static final String[] URI_DESCRIPTIONS = { // same order as INPUT_URIS
|
||||||
|
"MP4 with H264 video and AAC audio",
|
||||||
|
"MP4 with H265 video and AAC audio",
|
||||||
|
"Long MP4 with H264 video and AAC audio",
|
||||||
|
"WebM with VP8 video and Vorbis audio",
|
||||||
|
};
|
||||||
|
private static final String SAME_AS_INPUT_OPTION = "same as input";
|
||||||
|
|
||||||
|
private @MonotonicNonNull Button chooseFileButton;
|
||||||
|
private @MonotonicNonNull CheckBox removeAudioCheckbox;
|
||||||
|
private @MonotonicNonNull CheckBox removeVideoCheckbox;
|
||||||
|
private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox;
|
||||||
|
private @MonotonicNonNull Spinner audioMimeSpinner;
|
||||||
|
private @MonotonicNonNull Spinner videoMimeSpinner;
|
||||||
|
private @MonotonicNonNull Spinner resolutionHeightSpinner;
|
||||||
|
private @MonotonicNonNull Spinner translateSpinner;
|
||||||
|
private @MonotonicNonNull Spinner scaleSpinner;
|
||||||
|
private @MonotonicNonNull Spinner rotateSpinner;
|
||||||
|
private @MonotonicNonNull TextView chosenFileTextView;
|
||||||
|
private int inputUriPosition;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.configuration_activity);
|
||||||
|
|
||||||
|
findViewById(R.id.transform_button).setOnClickListener(this::startTransformation);
|
||||||
|
|
||||||
|
chooseFileButton = findViewById(R.id.choose_file_button);
|
||||||
|
chooseFileButton.setOnClickListener(this::chooseFile);
|
||||||
|
|
||||||
|
chosenFileTextView = findViewById(R.id.chosen_file_text_view);
|
||||||
|
chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
|
||||||
|
|
||||||
|
removeAudioCheckbox = findViewById(R.id.remove_audio_checkbox);
|
||||||
|
removeAudioCheckbox.setOnClickListener(this::onRemoveAudio);
|
||||||
|
|
||||||
|
removeVideoCheckbox = findViewById(R.id.remove_video_checkbox);
|
||||||
|
removeVideoCheckbox.setOnClickListener(this::onRemoveVideo);
|
||||||
|
|
||||||
|
flattenForSlowMotionCheckbox = findViewById(R.id.flatten_for_slow_motion_checkbox);
|
||||||
|
|
||||||
|
ArrayAdapter<String> audioMimeAdapter =
|
||||||
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
|
audioMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
audioMimeSpinner = findViewById(R.id.audio_mime_spinner);
|
||||||
|
audioMimeSpinner.setAdapter(audioMimeAdapter);
|
||||||
|
audioMimeAdapter.addAll(
|
||||||
|
SAME_AS_INPUT_OPTION, MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB);
|
||||||
|
|
||||||
|
ArrayAdapter<String> videoMimeAdapter =
|
||||||
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
|
videoMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
videoMimeSpinner = findViewById(R.id.video_mime_spinner);
|
||||||
|
videoMimeSpinner.setAdapter(videoMimeAdapter);
|
||||||
|
videoMimeAdapter.addAll(
|
||||||
|
SAME_AS_INPUT_OPTION,
|
||||||
|
MimeTypes.VIDEO_H263,
|
||||||
|
MimeTypes.VIDEO_H264,
|
||||||
|
MimeTypes.VIDEO_H265,
|
||||||
|
MimeTypes.VIDEO_MP4V);
|
||||||
|
|
||||||
|
ArrayAdapter<String> resolutionHeightAdapter =
|
||||||
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
|
resolutionHeightAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
resolutionHeightSpinner = findViewById(R.id.resolution_height_spinner);
|
||||||
|
resolutionHeightSpinner.setAdapter(resolutionHeightAdapter);
|
||||||
|
resolutionHeightAdapter.addAll(
|
||||||
|
SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
|
||||||
|
|
||||||
|
ArrayAdapter<String> translateAdapter =
|
||||||
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
|
translateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
translateSpinner = findViewById(R.id.translate_spinner);
|
||||||
|
translateSpinner.setAdapter(translateAdapter);
|
||||||
|
translateAdapter.addAll(
|
||||||
|
SAME_AS_INPUT_OPTION, "-.1, -.1", "0, 0", ".5, 0", "0, .5", "1, 1", "1.9, 0", "0, 1.9");
|
||||||
|
|
||||||
|
ArrayAdapter<String> scaleAdapter =
|
||||||
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
|
scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
scaleSpinner = findViewById(R.id.scale_spinner);
|
||||||
|
scaleSpinner.setAdapter(scaleAdapter);
|
||||||
|
scaleAdapter.addAll(SAME_AS_INPUT_OPTION, "-1, -1", "-1, 1", "1, 1", ".5, 1", ".5, .5", "2, 2");
|
||||||
|
|
||||||
|
ArrayAdapter<String> rotateAdapter =
|
||||||
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
|
rotateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
rotateSpinner = findViewById(R.id.rotate_spinner);
|
||||||
|
rotateSpinner.setAdapter(rotateAdapter);
|
||||||
|
rotateAdapter.addAll(SAME_AS_INPUT_OPTION, "0", "10", "45", "90", "180");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
@Nullable Uri intentUri = getIntent().getData();
|
||||||
|
if (intentUri != null) {
|
||||||
|
checkNotNull(chooseFileButton).setEnabled(false);
|
||||||
|
checkNotNull(chosenFileTextView).setText(intentUri.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
setIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({
|
||||||
|
"removeAudioCheckbox",
|
||||||
|
"removeVideoCheckbox",
|
||||||
|
"flattenForSlowMotionCheckbox",
|
||||||
|
"audioMimeSpinner",
|
||||||
|
"videoMimeSpinner",
|
||||||
|
"resolutionHeightSpinner",
|
||||||
|
"translateSpinner",
|
||||||
|
"scaleSpinner",
|
||||||
|
"rotateSpinner"
|
||||||
|
})
|
||||||
|
private void startTransformation(View view) {
|
||||||
|
Intent transformerIntent = new Intent(this, TransformerActivity.class);
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.isChecked());
|
||||||
|
bundle.putBoolean(SHOULD_REMOVE_VIDEO, removeVideoCheckbox.isChecked());
|
||||||
|
bundle.putBoolean(SHOULD_FLATTEN_FOR_SLOW_MOTION, flattenForSlowMotionCheckbox.isChecked());
|
||||||
|
String selectedAudioMimeType = String.valueOf(audioMimeSpinner.getSelectedItem());
|
||||||
|
if (!SAME_AS_INPUT_OPTION.equals(selectedAudioMimeType)) {
|
||||||
|
bundle.putString(AUDIO_MIME_TYPE, selectedAudioMimeType);
|
||||||
|
}
|
||||||
|
String selectedVideoMimeType = String.valueOf(videoMimeSpinner.getSelectedItem());
|
||||||
|
if (!SAME_AS_INPUT_OPTION.equals(selectedVideoMimeType)) {
|
||||||
|
bundle.putString(VIDEO_MIME_TYPE, selectedVideoMimeType);
|
||||||
|
}
|
||||||
|
String selectedResolutionHeight = String.valueOf(resolutionHeightSpinner.getSelectedItem());
|
||||||
|
if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) {
|
||||||
|
bundle.putInt(RESOLUTION_HEIGHT, Integer.valueOf(selectedResolutionHeight));
|
||||||
|
}
|
||||||
|
String selectedTranslate = String.valueOf(translateSpinner.getSelectedItem());
|
||||||
|
if (!SAME_AS_INPUT_OPTION.equals(selectedTranslate)) {
|
||||||
|
List<String> translateXY = Arrays.asList(selectedTranslate.split(", "));
|
||||||
|
checkState(translateXY.size() == 2);
|
||||||
|
bundle.putFloat(TRANSLATE_X, Float.valueOf(translateXY.get(0)));
|
||||||
|
bundle.putFloat(TRANSLATE_Y, Float.valueOf(translateXY.get(1)));
|
||||||
|
}
|
||||||
|
String selectedScale = String.valueOf(scaleSpinner.getSelectedItem());
|
||||||
|
if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) {
|
||||||
|
List<String> scaleXY = Arrays.asList(selectedScale.split(", "));
|
||||||
|
checkState(scaleXY.size() == 2);
|
||||||
|
bundle.putFloat(SCALE_X, Float.valueOf(scaleXY.get(0)));
|
||||||
|
bundle.putFloat(SCALE_Y, Float.valueOf(scaleXY.get(1)));
|
||||||
|
}
|
||||||
|
String selectedRotate = String.valueOf(rotateSpinner.getSelectedItem());
|
||||||
|
if (!SAME_AS_INPUT_OPTION.equals(selectedRotate)) {
|
||||||
|
bundle.putFloat(ROTATE_DEGREES, Float.valueOf(selectedRotate));
|
||||||
|
}
|
||||||
|
transformerIntent.putExtras(bundle);
|
||||||
|
|
||||||
|
@Nullable Uri intentUri = getIntent().getData();
|
||||||
|
transformerIntent.setData(
|
||||||
|
intentUri != null ? intentUri : Uri.parse(INPUT_URIS[inputUriPosition]));
|
||||||
|
|
||||||
|
startActivity(transformerIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseFile(View view) {
|
||||||
|
new AlertDialog.Builder(/* context= */ this)
|
||||||
|
.setTitle(R.string.choose_file_title)
|
||||||
|
.setSingleChoiceItems(URI_DESCRIPTIONS, inputUriPosition, this::selectFileInDialog)
|
||||||
|
.setPositiveButton(android.R.string.ok, /* listener= */ null)
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull("chosenFileTextView")
|
||||||
|
private void selectFileInDialog(DialogInterface dialog, int which) {
|
||||||
|
inputUriPosition = which;
|
||||||
|
chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({
|
||||||
|
"removeVideoCheckbox",
|
||||||
|
"audioMimeSpinner",
|
||||||
|
"videoMimeSpinner",
|
||||||
|
"resolutionHeightSpinner",
|
||||||
|
"translateSpinner",
|
||||||
|
"scaleSpinner",
|
||||||
|
"rotateSpinner"
|
||||||
|
})
|
||||||
|
private void onRemoveAudio(View view) {
|
||||||
|
if (((CheckBox) view).isChecked()) {
|
||||||
|
removeVideoCheckbox.setChecked(false);
|
||||||
|
enableTrackSpecificOptions(/* isAudioEnabled= */ false, /* isVideoEnabled= */ true);
|
||||||
|
} else {
|
||||||
|
enableTrackSpecificOptions(/* isAudioEnabled= */ true, /* isVideoEnabled= */ true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({
|
||||||
|
"removeAudioCheckbox",
|
||||||
|
"audioMimeSpinner",
|
||||||
|
"videoMimeSpinner",
|
||||||
|
"resolutionHeightSpinner",
|
||||||
|
"translateSpinner",
|
||||||
|
"scaleSpinner",
|
||||||
|
"rotateSpinner"
|
||||||
|
})
|
||||||
|
private void onRemoveVideo(View view) {
|
||||||
|
if (((CheckBox) view).isChecked()) {
|
||||||
|
removeAudioCheckbox.setChecked(false);
|
||||||
|
enableTrackSpecificOptions(/* isAudioEnabled= */ true, /* isVideoEnabled= */ false);
|
||||||
|
} else {
|
||||||
|
enableTrackSpecificOptions(/* isAudioEnabled= */ true, /* isVideoEnabled= */ true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({
|
||||||
|
"audioMimeSpinner",
|
||||||
|
"videoMimeSpinner",
|
||||||
|
"resolutionHeightSpinner",
|
||||||
|
"translateSpinner",
|
||||||
|
"scaleSpinner",
|
||||||
|
"rotateSpinner"
|
||||||
|
})
|
||||||
|
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
|
||||||
|
audioMimeSpinner.setEnabled(isAudioEnabled);
|
||||||
|
videoMimeSpinner.setEnabled(isVideoEnabled);
|
||||||
|
resolutionHeightSpinner.setEnabled(isVideoEnabled);
|
||||||
|
translateSpinner.setEnabled(isVideoEnabled);
|
||||||
|
scaleSpinner.setEnabled(isVideoEnabled);
|
||||||
|
rotateSpinner.setEnabled(isVideoEnabled);
|
||||||
|
|
||||||
|
findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled);
|
||||||
|
findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled);
|
||||||
|
findViewById(R.id.resolution_height_text_view).setEnabled(isVideoEnabled);
|
||||||
|
findViewById(R.id.translate).setEnabled(isVideoEnabled);
|
||||||
|
findViewById(R.id.scale).setEnabled(isVideoEnabled);
|
||||||
|
findViewById(R.id.rotate).setEnabled(isVideoEnabled);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,372 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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 android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||||
|
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.util.Log;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
|
import androidx.media3.exoplayer.util.DebugTextViewHelper;
|
||||||
|
import androidx.media3.transformer.ProgressHolder;
|
||||||
|
import androidx.media3.transformer.TransformationException;
|
||||||
|
import androidx.media3.transformer.TransformationRequest;
|
||||||
|
import androidx.media3.transformer.Transformer;
|
||||||
|
import androidx.media3.ui.AspectRatioFrameLayout;
|
||||||
|
import androidx.media3.ui.PlayerView;
|
||||||
|
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
||||||
|
import com.google.common.base.Stopwatch;
|
||||||
|
import com.google.common.base.Ticker;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
|
/** An {@link Activity} that transforms and plays media using {@link Transformer}. */
|
||||||
|
public final class TransformerActivity extends AppCompatActivity {
|
||||||
|
private static final String TAG = "TransformerActivity";
|
||||||
|
|
||||||
|
private @MonotonicNonNull PlayerView playerView;
|
||||||
|
private @MonotonicNonNull TextView debugTextView;
|
||||||
|
private @MonotonicNonNull TextView informationTextView;
|
||||||
|
private @MonotonicNonNull ViewGroup progressViewGroup;
|
||||||
|
private @MonotonicNonNull LinearProgressIndicator progressIndicator;
|
||||||
|
private @MonotonicNonNull Stopwatch transformationStopwatch;
|
||||||
|
private @MonotonicNonNull AspectRatioFrameLayout debugFrame;
|
||||||
|
|
||||||
|
@Nullable private DebugTextViewHelper debugTextViewHelper;
|
||||||
|
@Nullable private ExoPlayer player;
|
||||||
|
@Nullable private Transformer transformer;
|
||||||
|
@Nullable private File externalCacheFile;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.transformer_activity);
|
||||||
|
|
||||||
|
playerView = findViewById(R.id.player_view);
|
||||||
|
debugTextView = findViewById(R.id.debug_text_view);
|
||||||
|
informationTextView = findViewById(R.id.information_text_view);
|
||||||
|
progressViewGroup = findViewById(R.id.progress_view_group);
|
||||||
|
progressIndicator = findViewById(R.id.progress_indicator);
|
||||||
|
debugFrame = findViewById(R.id.debug_aspect_ratio_frame_layout);
|
||||||
|
|
||||||
|
transformationStopwatch =
|
||||||
|
Stopwatch.createUnstarted(
|
||||||
|
new Ticker() {
|
||||||
|
public long read() {
|
||||||
|
return android.os.SystemClock.elapsedRealtimeNanos();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
checkNotNull(progressIndicator);
|
||||||
|
checkNotNull(informationTextView);
|
||||||
|
checkNotNull(transformationStopwatch);
|
||||||
|
checkNotNull(playerView);
|
||||||
|
checkNotNull(debugTextView);
|
||||||
|
checkNotNull(progressViewGroup);
|
||||||
|
startTransformation();
|
||||||
|
|
||||||
|
playerView.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
checkNotNull(transformationStopwatch).reset();
|
||||||
|
|
||||||
|
checkNotNull(transformer).cancel();
|
||||||
|
transformer = null;
|
||||||
|
|
||||||
|
checkNotNull(playerView).onPause();
|
||||||
|
releasePlayer();
|
||||||
|
|
||||||
|
checkNotNull(externalCacheFile).delete();
|
||||||
|
externalCacheFile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({
|
||||||
|
"playerView",
|
||||||
|
"debugTextView",
|
||||||
|
"informationTextView",
|
||||||
|
"progressIndicator",
|
||||||
|
"transformationStopwatch",
|
||||||
|
"progressViewGroup",
|
||||||
|
})
|
||||||
|
private void startTransformation() {
|
||||||
|
requestTransformerPermissions();
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
Uri uri = checkNotNull(intent.getData());
|
||||||
|
try {
|
||||||
|
externalCacheFile = createExternalCacheFile("transformer-output.mp4");
|
||||||
|
String filePath = externalCacheFile.getAbsolutePath();
|
||||||
|
@Nullable Bundle bundle = intent.getExtras();
|
||||||
|
Transformer transformer = createTransformer(bundle, filePath);
|
||||||
|
transformationStopwatch.start();
|
||||||
|
transformer.startTransformation(MediaItem.fromUri(uri), filePath);
|
||||||
|
this.transformer = transformer;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
informationTextView.setText(R.string.transformation_started);
|
||||||
|
Handler mainHandler = new Handler(getMainLooper());
|
||||||
|
ProgressHolder progressHolder = new ProgressHolder();
|
||||||
|
mainHandler.post(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (transformer != null
|
||||||
|
&& transformer.getProgress(progressHolder)
|
||||||
|
!= Transformer.PROGRESS_STATE_NO_TRANSFORMATION) {
|
||||||
|
progressIndicator.setProgress(progressHolder.progress);
|
||||||
|
informationTextView.setText(
|
||||||
|
getString(
|
||||||
|
R.string.transformation_timer,
|
||||||
|
transformationStopwatch.elapsed(TimeUnit.SECONDS)));
|
||||||
|
mainHandler.postDelayed(/* r= */ this, /* delayMillis= */ 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a cache file, resetting it if it already exists.
|
||||||
|
private File createExternalCacheFile(String fileName) throws IOException {
|
||||||
|
File file = new File(getExternalCacheDir(), fileName);
|
||||||
|
if (file.exists() && !file.delete()) {
|
||||||
|
throw new IllegalStateException("Could not delete the previous transformer output file");
|
||||||
|
}
|
||||||
|
if (!file.createNewFile()) {
|
||||||
|
throw new IllegalStateException("Could not create the transformer output file");
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({
|
||||||
|
"playerView",
|
||||||
|
"debugTextView",
|
||||||
|
"informationTextView",
|
||||||
|
"transformationStopwatch",
|
||||||
|
"progressViewGroup",
|
||||||
|
})
|
||||||
|
private Transformer createTransformer(@Nullable Bundle bundle, String filePath) {
|
||||||
|
Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this);
|
||||||
|
if (bundle != null) {
|
||||||
|
TransformationRequest.Builder requestBuilder = new TransformationRequest.Builder();
|
||||||
|
requestBuilder.setFlattenForSlowMotion(
|
||||||
|
bundle.getBoolean(ConfigurationActivity.SHOULD_FLATTEN_FOR_SLOW_MOTION));
|
||||||
|
@Nullable String audioMimeType = bundle.getString(ConfigurationActivity.AUDIO_MIME_TYPE);
|
||||||
|
if (audioMimeType != null) {
|
||||||
|
requestBuilder.setAudioMimeType(audioMimeType);
|
||||||
|
}
|
||||||
|
@Nullable String videoMimeType = bundle.getString(ConfigurationActivity.VIDEO_MIME_TYPE);
|
||||||
|
if (videoMimeType != null) {
|
||||||
|
requestBuilder.setVideoMimeType(videoMimeType);
|
||||||
|
}
|
||||||
|
int resolutionHeight =
|
||||||
|
bundle.getInt(
|
||||||
|
ConfigurationActivity.RESOLUTION_HEIGHT, /* defaultValue= */ C.LENGTH_UNSET);
|
||||||
|
if (resolutionHeight != C.LENGTH_UNSET) {
|
||||||
|
requestBuilder.setResolution(resolutionHeight);
|
||||||
|
}
|
||||||
|
Matrix transformationMatrix = getTransformationMatrix(bundle);
|
||||||
|
if (!transformationMatrix.isIdentity()) {
|
||||||
|
requestBuilder.setTransformationMatrix(transformationMatrix);
|
||||||
|
}
|
||||||
|
transformerBuilder
|
||||||
|
.setTransformationRequest(requestBuilder.build())
|
||||||
|
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
|
||||||
|
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO));
|
||||||
|
}
|
||||||
|
return transformerBuilder
|
||||||
|
.addListener(
|
||||||
|
new Transformer.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onTransformationCompleted(MediaItem mediaItem) {
|
||||||
|
TransformerActivity.this.onTransformationCompleted(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTransformationError(
|
||||||
|
MediaItem mediaItem, TransformationException exception) {
|
||||||
|
TransformerActivity.this.onTransformationError(exception);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setDebugViewProvider(new DemoDebugViewProvider())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matrix getTransformationMatrix(Bundle bundle) {
|
||||||
|
Matrix transformationMatrix = new Matrix();
|
||||||
|
|
||||||
|
float translateX = bundle.getFloat(ConfigurationActivity.TRANSLATE_X, /* defaultValue= */ 0);
|
||||||
|
float translateY = bundle.getFloat(ConfigurationActivity.TRANSLATE_Y, /* defaultValue= */ 0);
|
||||||
|
// TODO(b/213198690): Get resolution for aspect ratio and scale all translations' translateX
|
||||||
|
// by this aspect ratio.
|
||||||
|
transformationMatrix.postTranslate(translateX, translateY);
|
||||||
|
|
||||||
|
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
|
||||||
|
float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
|
||||||
|
transformationMatrix.postScale(scaleX, scaleY);
|
||||||
|
|
||||||
|
float rotateDegrees =
|
||||||
|
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
|
||||||
|
transformationMatrix.postRotate(rotateDegrees);
|
||||||
|
|
||||||
|
return transformationMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({
|
||||||
|
"informationTextView",
|
||||||
|
"progressViewGroup",
|
||||||
|
"transformationStopwatch",
|
||||||
|
})
|
||||||
|
private void onTransformationError(TransformationException exception) {
|
||||||
|
transformationStopwatch.stop();
|
||||||
|
informationTextView.setText(R.string.transformation_error);
|
||||||
|
progressViewGroup.setVisibility(View.GONE);
|
||||||
|
Toast.makeText(
|
||||||
|
TransformerActivity.this, "Transformation error: " + exception, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
Log.e(TAG, "Transformation error", exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({
|
||||||
|
"playerView",
|
||||||
|
"debugTextView",
|
||||||
|
"informationTextView",
|
||||||
|
"progressViewGroup",
|
||||||
|
"transformationStopwatch",
|
||||||
|
})
|
||||||
|
private void onTransformationCompleted(String filePath) {
|
||||||
|
transformationStopwatch.stop();
|
||||||
|
informationTextView.setText(
|
||||||
|
getString(
|
||||||
|
R.string.transformation_completed, transformationStopwatch.elapsed(TimeUnit.SECONDS)));
|
||||||
|
progressViewGroup.setVisibility(View.GONE);
|
||||||
|
playMediaItem(MediaItem.fromUri("file://" + filePath));
|
||||||
|
Log.d(TAG, "Output file path: file://" + filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({"playerView", "debugTextView"})
|
||||||
|
private void playMediaItem(MediaItem mediaItem) {
|
||||||
|
playerView.setPlayer(null);
|
||||||
|
releasePlayer();
|
||||||
|
|
||||||
|
ExoPlayer player = new ExoPlayer.Builder(/* context= */ this).build();
|
||||||
|
playerView.setPlayer(player);
|
||||||
|
player.setMediaItem(mediaItem);
|
||||||
|
player.play();
|
||||||
|
player.prepare();
|
||||||
|
this.player = player;
|
||||||
|
debugTextViewHelper = new DebugTextViewHelper(player, debugTextView);
|
||||||
|
debugTextViewHelper.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releasePlayer() {
|
||||||
|
if (debugTextViewHelper != null) {
|
||||||
|
debugTextViewHelper.stop();
|
||||||
|
debugTextViewHelper = null;
|
||||||
|
}
|
||||||
|
if (player != null) {
|
||||||
|
player.release();
|
||||||
|
player = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestTransformerPermissions() {
|
||||||
|
if (Util.SDK_INT < 23) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (checkSelfPermission(READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|
||||||
|
|| checkSelfPermission(WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestPermissions(
|
||||||
|
new String[] {READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE}, /* requestCode= */ 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class DemoDebugViewProvider implements Transformer.DebugViewProvider {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public SurfaceView getDebugPreviewSurfaceView(int width, int height) {
|
||||||
|
// Update the UI on the main thread and wait for the output surface to be available.
|
||||||
|
CountDownLatch surfaceCreatedCountDownLatch = new CountDownLatch(1);
|
||||||
|
SurfaceView surfaceView = new SurfaceView(/* context= */ TransformerActivity.this);
|
||||||
|
runOnUiThread(
|
||||||
|
() -> {
|
||||||
|
AspectRatioFrameLayout debugFrame = checkNotNull(TransformerActivity.this.debugFrame);
|
||||||
|
debugFrame.addView(surfaceView);
|
||||||
|
debugFrame.setAspectRatio((float) width / height);
|
||||||
|
surfaceView
|
||||||
|
.getHolder()
|
||||||
|
.addCallback(
|
||||||
|
new SurfaceHolder.Callback() {
|
||||||
|
@Override
|
||||||
|
public void surfaceCreated(SurfaceHolder surfaceHolder) {
|
||||||
|
surfaceCreatedCountDownLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceChanged(
|
||||||
|
SurfaceHolder surfaceHolder, int format, int width, int height) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
surfaceCreatedCountDownLatch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.w(TAG, "Interrupted waiting for debug surface.");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return surfaceView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.
|
||||||
|
*/
|
||||||
|
@NonNullApi
|
||||||
|
package androidx.media3.demo.transformer;
|
||||||
|
|
||||||
|
import androidx.media3.common.util.NonNullApi;
|
179
demos/transformer/src/main/res/layout/configuration_activity.xml
Normal file
179
demos/transformer/src/main/res/layout/configuration_activity.xml
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2021 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.
|
||||||
|
-->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ConfigurationActivity">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/configuration_text_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:text="@string/configuration"
|
||||||
|
android:textSize="24sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
<Button
|
||||||
|
android:id="@+id/choose_file_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:text="@string/choose_file_title"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/configuration_text_view"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/chosen_file_text_view"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingRight="24dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/choose_file_button" />
|
||||||
|
<TableLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:stretchColumns="1"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:measureWithLargestChild="true"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/chosen_file_text_view" >
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:text="@string/remove_audio" />
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/remove_audio_checkbox"
|
||||||
|
android:layout_gravity="right"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:text="@string/remove_video"/>
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/remove_video_checkbox"
|
||||||
|
android:layout_gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:text="@string/flatten_for_slow_motion"/>
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/flatten_for_slow_motion_checkbox"
|
||||||
|
android:layout_gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/audio_mime_text_view"
|
||||||
|
android:text="@string/audio_mime"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/audio_mime_spinner"
|
||||||
|
android:layout_gravity="right|center_vertical"
|
||||||
|
android:gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_mime_text_view"
|
||||||
|
android:text="@string/video_mime"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/video_mime_spinner"
|
||||||
|
android:layout_gravity="right|center_vertical"
|
||||||
|
android:gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/resolution_height_text_view"
|
||||||
|
android:text="@string/resolution_height"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/resolution_height_spinner"
|
||||||
|
android:layout_gravity="right|center_vertical"
|
||||||
|
android:gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/translate"
|
||||||
|
android:text="@string/translate"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/translate_spinner"
|
||||||
|
android:layout_gravity="right|center_vertical"
|
||||||
|
android:gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/scale"
|
||||||
|
android:text="@string/scale"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/scale_spinner"
|
||||||
|
android:layout_gravity="right|center_vertical"
|
||||||
|
android:gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/rotate"
|
||||||
|
android:text="@string/rotate"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/rotate_spinner"
|
||||||
|
android:layout_gravity="right|center_vertical"
|
||||||
|
android:gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/transform_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="28dp"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:text="@string/transform"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
26
demos/transformer/src/main/res/layout/spinner_item.xml
Normal file
26
demos/transformer/src/main/res/layout/spinner_item.xml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2021 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.
|
||||||
|
-->
|
||||||
|
<TextView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:gravity="left|center_vertical"
|
||||||
|
android:paddingLeft="4dp"
|
||||||
|
android:paddingRight="4dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:textIsSelectable="false" />
|
107
demos/transformer/src/main/res/layout/transformer_activity.xml
Normal file
107
demos/transformer/src/main/res/layout/transformer_activity.xml
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Copyright 2021 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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:keepScreenOn="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:cardCornerRadius="4dp"
|
||||||
|
app:cardElevation="2dp"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/information_text_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp" />
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:cardCornerRadius="4dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.media3.ui.PlayerView
|
||||||
|
android:id="@+id/player_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/debug_text_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="4dp"
|
||||||
|
android:paddingRight="4dp"
|
||||||
|
android:textSize="10sp"
|
||||||
|
tools:ignore="SmallSp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/progress_view_group"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/progress_indicator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/debug_preview" />
|
||||||
|
|
||||||
|
<androidx.media3.ui.AspectRatioFrameLayout
|
||||||
|
android:id="@+id/debug_aspect_ratio_frame_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/debug_preview_not_available" />
|
||||||
|
|
||||||
|
</androidx.media3.ui.AspectRatioFrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
</LinearLayout>
|
BIN
demos/transformer/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
demos/transformer/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
BIN
demos/transformer/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
demos/transformer/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
demos/transformer/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
demos/transformer/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
BIN
demos/transformer/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
demos/transformer/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
BIN
demos/transformer/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
demos/transformer/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
37
demos/transformer/src/main/res/values/strings.xml
Normal file
37
demos/transformer/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2021 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.
|
||||||
|
-->
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name" translatable="false">Transformer Demo</string>
|
||||||
|
<string name="configuration" translatable="false">Configuration</string>
|
||||||
|
<string name="choose_file_title" translatable="false">Choose File</string>
|
||||||
|
<string name="remove_audio" translatable="false">Remove audio</string>
|
||||||
|
<string name="remove_video" translatable="false">Remove video</string>
|
||||||
|
<string name="flatten_for_slow_motion" translatable="false">Flatten for slow motion</string>
|
||||||
|
<string name="audio_mime" translatable="false">Output audio MIME type</string>
|
||||||
|
<string name="video_mime" translatable="false">Output video MIME type</string>
|
||||||
|
<string name="resolution_height" translatable="false">Output video resolution</string>
|
||||||
|
<string name="translate" translatable="false">Translate video</string>
|
||||||
|
<string name="scale" translatable="false">Scale video</string>
|
||||||
|
<string name="rotate" translatable="false">Rotate video (degrees)</string>
|
||||||
|
<string name="transform" translatable="false">Transform</string>
|
||||||
|
<string name="debug_preview" translatable="false">Debug preview:</string>
|
||||||
|
<string name="debug_preview_not_available" translatable="false">No debug preview available</string>
|
||||||
|
<string name="transformation_started" translatable="false">Transformation started</string>
|
||||||
|
<string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string>
|
||||||
|
<string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string>
|
||||||
|
<string name="transformation_error" translatable="false">Transformation error</string>
|
||||||
|
</resources>
|
@ -28,6 +28,8 @@ include modulePrefix + 'demo-session'
|
|||||||
project(modulePrefix + 'demo-session').projectDir = new File(rootDir, 'demos/session')
|
project(modulePrefix + 'demo-session').projectDir = new File(rootDir, 'demos/session')
|
||||||
include modulePrefix + 'demo-surface'
|
include modulePrefix + 'demo-surface'
|
||||||
project(modulePrefix + 'demo-surface').projectDir = new File(rootDir, 'demos/surface')
|
project(modulePrefix + 'demo-surface').projectDir = new File(rootDir, 'demos/surface')
|
||||||
|
include modulePrefix + 'demo-transformer'
|
||||||
|
project(modulePrefix + 'demo-transformer').projectDir = new File(rootDir, 'demos/transformer')
|
||||||
|
|
||||||
include modulePrefix + 'test-exoplayer-playback'
|
include modulePrefix + 'test-exoplayer-playback'
|
||||||
project(modulePrefix + 'test-exoplayer-playback').projectDir = new File(rootDir, 'libraries/test_exoplayer_playback')
|
project(modulePrefix + 'test-exoplayer-playback').projectDir = new File(rootDir, 'libraries/test_exoplayer_playback')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user