• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.exoplayer2.transformerdemo;
17 
18 import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
19 import static com.google.android.exoplayer2.util.Assertions.checkState;
20 
21 import android.app.Activity;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.view.View;
27 import android.widget.ArrayAdapter;
28 import android.widget.Button;
29 import android.widget.CheckBox;
30 import android.widget.Spinner;
31 import android.widget.TextView;
32 import androidx.annotation.Nullable;
33 import androidx.appcompat.app.AlertDialog;
34 import androidx.appcompat.app.AppCompatActivity;
35 import com.google.android.exoplayer2.util.MimeTypes;
36 import com.google.android.exoplayer2.util.Util;
37 import com.google.android.material.slider.RangeSlider;
38 import com.google.android.material.slider.Slider;
39 import java.util.Arrays;
40 import java.util.List;
41 import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
42 import org.checkerframework.checker.nullness.qual.RequiresNonNull;
43 
44 /**
45  * An {@link Activity} that sets the configuration to use for transforming and playing media, using
46  * {@link TransformerActivity}.
47  */
48 public final class ConfigurationActivity extends AppCompatActivity {
49   public static final String SHOULD_REMOVE_AUDIO = "should_remove_audio";
50   public static final String SHOULD_REMOVE_VIDEO = "should_remove_video";
51   public static final String SHOULD_FLATTEN_FOR_SLOW_MOTION = "should_flatten_for_slow_motion";
52   public static final String AUDIO_MIME_TYPE = "audio_mime_type";
53   public static final String VIDEO_MIME_TYPE = "video_mime_type";
54   public static final String RESOLUTION_HEIGHT = "resolution_height";
55   public static final String SCALE_X = "scale_x";
56   public static final String SCALE_Y = "scale_y";
57   public static final String ROTATE_DEGREES = "rotate_degrees";
58   public static final String ENABLE_FALLBACK = "enable_fallback";
59   public static final String ENABLE_REQUEST_SDR_TONE_MAPPING = "enable_request_sdr_tone_mapping";
60   public static final String ENABLE_HDR_EDITING = "enable_hdr_editing";
61   public static final String DEMO_FRAME_PROCESSORS_SELECTIONS = "demo_frame_processors_selections";
62   public static final String PERIODIC_VIGNETTE_CENTER_X = "periodic_vignette_center_x";
63   public static final String PERIODIC_VIGNETTE_CENTER_Y = "periodic_vignette_center_y";
64   public static final String PERIODIC_VIGNETTE_INNER_RADIUS = "periodic_vignette_inner_radius";
65   public static final String PERIODIC_VIGNETTE_OUTER_RADIUS = "periodic_vignette_outer_radius";
66   private static final String[] INPUT_URIS = {
67     "https://html5demos.com/assets/dizzy.mp4",
68     "https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4",
69     "https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4",
70     "https://html5demos.com/assets/dizzy.webm",
71     "https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4",
72     "https://storage.googleapis.com/exoplayer-test-media-1/mp4/8k24fps_4s.mp4",
73     "https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_avc_aac.mp4",
74     "https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_rotated_avc_aac.mp4",
75     "https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/slowMotion_stopwatch_240fps_long.mp4",
76     "https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-hdr-hdr10.mp4",
77   };
78   private static final String[] URI_DESCRIPTIONS = { // same order as INPUT_URIS
79     "MP4 with H264 video and AAC audio",
80     "MP4 with H265 video and AAC audio",
81     "Long MP4 with H264 video and AAC audio",
82     "WebM with VP8 video and Vorbis audio",
83     "4K 60fps MP4 with H264 video and AAC audio (portrait, timestamps always increase)",
84     "8k 24fps MP4 with H265 video and AAC audio",
85     "MP4 with H264 video and AAC audio (portrait, H > W, 0\u00B0)",
86     "MP4 with H264 video and AAC audio (portrait, H < W, 90\u00B0)",
87     "SEF slow motion with 240 fps",
88     "MP4 with HDR (HDR10) H265 video (encoding may fail)",
89   };
90   private static final String[] DEMO_FRAME_PROCESSORS = {
91     "Dizzy crop", "Periodic vignette", "3D spin", "Overlay logo & timer", "Zoom in start"
92   };
93   private static final int PERIODIC_VIGNETTE_INDEX = 1;
94   private static final String SAME_AS_INPUT_OPTION = "same as input";
95   private static final float HALF_DIAGONAL = 1f / (float) Math.sqrt(2);
96 
97   private @MonotonicNonNull Button selectFileButton;
98   private @MonotonicNonNull TextView selectedFileTextView;
99   private @MonotonicNonNull CheckBox removeAudioCheckbox;
100   private @MonotonicNonNull CheckBox removeVideoCheckbox;
101   private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox;
102   private @MonotonicNonNull Spinner audioMimeSpinner;
103   private @MonotonicNonNull Spinner videoMimeSpinner;
104   private @MonotonicNonNull Spinner resolutionHeightSpinner;
105   private @MonotonicNonNull Spinner scaleSpinner;
106   private @MonotonicNonNull Spinner rotateSpinner;
107   private @MonotonicNonNull CheckBox enableFallbackCheckBox;
108   private @MonotonicNonNull CheckBox enableRequestSdrToneMappingCheckBox;
109   private @MonotonicNonNull CheckBox enableHdrEditingCheckBox;
110   private @MonotonicNonNull Button selectDemoFrameProcessorsButton;
111   private boolean @MonotonicNonNull [] demoFrameProcessorsSelections;
112   private int inputUriPosition;
113   private float periodicVignetteCenterX;
114   private float periodicVignetteCenterY;
115   private float periodicVignetteInnerRadius;
116   private float periodicVignetteOuterRadius;
117 
118   @Override
onCreate(@ullable Bundle savedInstanceState)119   protected void onCreate(@Nullable Bundle savedInstanceState) {
120     super.onCreate(savedInstanceState);
121     setContentView(R.layout.configuration_activity);
122 
123     findViewById(R.id.transform_button).setOnClickListener(this::startTransformation);
124 
125     selectFileButton = findViewById(R.id.select_file_button);
126     selectFileButton.setOnClickListener(this::selectFile);
127 
128     selectedFileTextView = findViewById(R.id.selected_file_text_view);
129     selectedFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
130 
131     removeAudioCheckbox = findViewById(R.id.remove_audio_checkbox);
132     removeAudioCheckbox.setOnClickListener(this::onRemoveAudio);
133 
134     removeVideoCheckbox = findViewById(R.id.remove_video_checkbox);
135     removeVideoCheckbox.setOnClickListener(this::onRemoveVideo);
136 
137     flattenForSlowMotionCheckbox = findViewById(R.id.flatten_for_slow_motion_checkbox);
138 
139     ArrayAdapter<String> audioMimeAdapter =
140         new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
141     audioMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
142     audioMimeSpinner = findViewById(R.id.audio_mime_spinner);
143     audioMimeSpinner.setAdapter(audioMimeAdapter);
144     audioMimeAdapter.addAll(
145         SAME_AS_INPUT_OPTION, MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB);
146 
147     ArrayAdapter<String> videoMimeAdapter =
148         new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
149     videoMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
150     videoMimeSpinner = findViewById(R.id.video_mime_spinner);
151     videoMimeSpinner.setAdapter(videoMimeAdapter);
152     videoMimeAdapter.addAll(
153         SAME_AS_INPUT_OPTION, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V);
154     if (Util.SDK_INT >= 24) {
155       videoMimeAdapter.add(MimeTypes.VIDEO_H265);
156     }
157 
158     ArrayAdapter<String> resolutionHeightAdapter =
159         new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
160     resolutionHeightAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
161     resolutionHeightSpinner = findViewById(R.id.resolution_height_spinner);
162     resolutionHeightSpinner.setAdapter(resolutionHeightAdapter);
163     resolutionHeightAdapter.addAll(
164         SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
165 
166     ArrayAdapter<String> scaleAdapter =
167         new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
168     scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
169     scaleSpinner = findViewById(R.id.scale_spinner);
170     scaleSpinner.setAdapter(scaleAdapter);
171     scaleAdapter.addAll(SAME_AS_INPUT_OPTION, "-1, -1", "-1, 1", "1, 1", ".5, 1", ".5, .5", "2, 2");
172 
173     ArrayAdapter<String> rotateAdapter =
174         new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
175     rotateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
176     rotateSpinner = findViewById(R.id.rotate_spinner);
177     rotateSpinner.setAdapter(rotateAdapter);
178     rotateAdapter.addAll(SAME_AS_INPUT_OPTION, "0", "10", "45", "60", "90", "180");
179 
180     enableFallbackCheckBox = findViewById(R.id.enable_fallback_checkbox);
181     enableRequestSdrToneMappingCheckBox = findViewById(R.id.request_sdr_tone_mapping_checkbox);
182     enableRequestSdrToneMappingCheckBox.setEnabled(isRequestSdrToneMappingSupported());
183     findViewById(R.id.request_sdr_tone_mapping).setEnabled(isRequestSdrToneMappingSupported());
184     enableHdrEditingCheckBox = findViewById(R.id.hdr_editing_checkbox);
185 
186     demoFrameProcessorsSelections = new boolean[DEMO_FRAME_PROCESSORS.length];
187     selectDemoFrameProcessorsButton = findViewById(R.id.select_demo_frameprocessors_button);
188     selectDemoFrameProcessorsButton.setOnClickListener(this::selectFrameProcessors);
189   }
190 
191   @Override
onResume()192   protected void onResume() {
193     super.onResume();
194     @Nullable Uri intentUri = getIntent().getData();
195     if (intentUri != null) {
196       checkNotNull(selectFileButton).setEnabled(false);
197       checkNotNull(selectedFileTextView).setText(intentUri.toString());
198     }
199   }
200 
201   @Override
onNewIntent(Intent intent)202   protected void onNewIntent(Intent intent) {
203     super.onNewIntent(intent);
204     setIntent(intent);
205   }
206 
207   @RequiresNonNull({
208     "removeAudioCheckbox",
209     "removeVideoCheckbox",
210     "flattenForSlowMotionCheckbox",
211     "audioMimeSpinner",
212     "videoMimeSpinner",
213     "resolutionHeightSpinner",
214     "scaleSpinner",
215     "rotateSpinner",
216     "enableFallbackCheckBox",
217     "enableRequestSdrToneMappingCheckBox",
218     "enableHdrEditingCheckBox",
219     "demoFrameProcessorsSelections"
220   })
startTransformation(View view)221   private void startTransformation(View view) {
222     Intent transformerIntent = new Intent(this, TransformerActivity.class);
223     Bundle bundle = new Bundle();
224     bundle.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.isChecked());
225     bundle.putBoolean(SHOULD_REMOVE_VIDEO, removeVideoCheckbox.isChecked());
226     bundle.putBoolean(SHOULD_FLATTEN_FOR_SLOW_MOTION, flattenForSlowMotionCheckbox.isChecked());
227     String selectedAudioMimeType = String.valueOf(audioMimeSpinner.getSelectedItem());
228     if (!SAME_AS_INPUT_OPTION.equals(selectedAudioMimeType)) {
229       bundle.putString(AUDIO_MIME_TYPE, selectedAudioMimeType);
230     }
231     String selectedVideoMimeType = String.valueOf(videoMimeSpinner.getSelectedItem());
232     if (!SAME_AS_INPUT_OPTION.equals(selectedVideoMimeType)) {
233       bundle.putString(VIDEO_MIME_TYPE, selectedVideoMimeType);
234     }
235     String selectedResolutionHeight = String.valueOf(resolutionHeightSpinner.getSelectedItem());
236     if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) {
237       bundle.putInt(RESOLUTION_HEIGHT, Integer.parseInt(selectedResolutionHeight));
238     }
239     String selectedScale = String.valueOf(scaleSpinner.getSelectedItem());
240     if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) {
241       List<String> scaleXY = Arrays.asList(selectedScale.split(", "));
242       checkState(scaleXY.size() == 2);
243       bundle.putFloat(SCALE_X, Float.parseFloat(scaleXY.get(0)));
244       bundle.putFloat(SCALE_Y, Float.parseFloat(scaleXY.get(1)));
245     }
246     String selectedRotate = String.valueOf(rotateSpinner.getSelectedItem());
247     if (!SAME_AS_INPUT_OPTION.equals(selectedRotate)) {
248       bundle.putFloat(ROTATE_DEGREES, Float.parseFloat(selectedRotate));
249     }
250     bundle.putBoolean(ENABLE_FALLBACK, enableFallbackCheckBox.isChecked());
251     bundle.putBoolean(
252         ENABLE_REQUEST_SDR_TONE_MAPPING, enableRequestSdrToneMappingCheckBox.isChecked());
253     bundle.putBoolean(ENABLE_HDR_EDITING, enableHdrEditingCheckBox.isChecked());
254     bundle.putBooleanArray(DEMO_FRAME_PROCESSORS_SELECTIONS, demoFrameProcessorsSelections);
255     bundle.putFloat(PERIODIC_VIGNETTE_CENTER_X, periodicVignetteCenterX);
256     bundle.putFloat(PERIODIC_VIGNETTE_CENTER_Y, periodicVignetteCenterY);
257     bundle.putFloat(PERIODIC_VIGNETTE_INNER_RADIUS, periodicVignetteInnerRadius);
258     bundle.putFloat(PERIODIC_VIGNETTE_OUTER_RADIUS, periodicVignetteOuterRadius);
259     transformerIntent.putExtras(bundle);
260 
261     @Nullable Uri intentUri = getIntent().getData();
262     transformerIntent.setData(
263         intentUri != null ? intentUri : Uri.parse(INPUT_URIS[inputUriPosition]));
264 
265     startActivity(transformerIntent);
266   }
267 
selectFile(View view)268   private void selectFile(View view) {
269     new AlertDialog.Builder(/* context= */ this)
270         .setTitle(R.string.select_file_title)
271         .setSingleChoiceItems(URI_DESCRIPTIONS, inputUriPosition, this::selectFileInDialog)
272         .setPositiveButton(android.R.string.ok, /* listener= */ null)
273         .create()
274         .show();
275   }
276 
selectFrameProcessors(View view)277   private void selectFrameProcessors(View view) {
278     new AlertDialog.Builder(/* context= */ this)
279         .setTitle(R.string.select_demo_frameprocessors)
280         .setMultiChoiceItems(
281             DEMO_FRAME_PROCESSORS,
282             checkNotNull(demoFrameProcessorsSelections),
283             this::selectFrameProcessor)
284         .setPositiveButton(android.R.string.ok, /* listener= */ null)
285         .create()
286         .show();
287   }
288 
289   @RequiresNonNull("selectedFileTextView")
selectFileInDialog(DialogInterface dialog, int which)290   private void selectFileInDialog(DialogInterface dialog, int which) {
291     inputUriPosition = which;
292     selectedFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
293   }
294 
295   @RequiresNonNull("demoFrameProcessorsSelections")
selectFrameProcessor(DialogInterface dialog, int which, boolean isChecked)296   private void selectFrameProcessor(DialogInterface dialog, int which, boolean isChecked) {
297     demoFrameProcessorsSelections[which] = isChecked;
298     if (!isChecked || which != PERIODIC_VIGNETTE_INDEX) {
299       return;
300     }
301 
302     View dialogView =
303         getLayoutInflater().inflate(R.layout.periodic_vignette_options, /* root= */ null);
304     Slider centerXSlider =
305         checkNotNull(dialogView.findViewById(R.id.periodic_vignette_center_x_slider));
306     Slider centerYSlider =
307         checkNotNull(dialogView.findViewById(R.id.periodic_vignette_center_y_slider));
308     RangeSlider radiusRangeSlider =
309         checkNotNull(dialogView.findViewById(R.id.periodic_vignette_radius_range_slider));
310     radiusRangeSlider.setValues(0f, HALF_DIAGONAL);
311     new AlertDialog.Builder(/* context= */ this)
312         .setTitle(R.string.periodic_vignette_options)
313         .setView(dialogView)
314         .setPositiveButton(
315             android.R.string.ok,
316             (DialogInterface dialogInterface, int i) -> {
317               periodicVignetteCenterX = centerXSlider.getValue();
318               periodicVignetteCenterY = centerYSlider.getValue();
319               List<Float> radiusRange = radiusRangeSlider.getValues();
320               periodicVignetteInnerRadius = radiusRange.get(0);
321               periodicVignetteOuterRadius = radiusRange.get(1);
322             })
323         .create()
324         .show();
325   }
326 
327   @RequiresNonNull({
328     "removeVideoCheckbox",
329     "audioMimeSpinner",
330     "videoMimeSpinner",
331     "resolutionHeightSpinner",
332     "scaleSpinner",
333     "rotateSpinner",
334     "enableRequestSdrToneMappingCheckBox",
335     "enableHdrEditingCheckBox",
336     "selectDemoFrameProcessorsButton"
337   })
onRemoveAudio(View view)338   private void onRemoveAudio(View view) {
339     if (((CheckBox) view).isChecked()) {
340       removeVideoCheckbox.setChecked(false);
341       enableTrackSpecificOptions(/* isAudioEnabled= */ false, /* isVideoEnabled= */ true);
342     } else {
343       enableTrackSpecificOptions(/* isAudioEnabled= */ true, /* isVideoEnabled= */ true);
344     }
345   }
346 
347   @RequiresNonNull({
348     "removeAudioCheckbox",
349     "audioMimeSpinner",
350     "videoMimeSpinner",
351     "resolutionHeightSpinner",
352     "scaleSpinner",
353     "rotateSpinner",
354     "enableRequestSdrToneMappingCheckBox",
355     "enableHdrEditingCheckBox",
356     "selectDemoFrameProcessorsButton"
357   })
onRemoveVideo(View view)358   private void onRemoveVideo(View view) {
359     if (((CheckBox) view).isChecked()) {
360       removeAudioCheckbox.setChecked(false);
361       enableTrackSpecificOptions(/* isAudioEnabled= */ true, /* isVideoEnabled= */ false);
362     } else {
363       enableTrackSpecificOptions(/* isAudioEnabled= */ true, /* isVideoEnabled= */ true);
364     }
365   }
366 
367   @RequiresNonNull({
368     "audioMimeSpinner",
369     "videoMimeSpinner",
370     "resolutionHeightSpinner",
371     "scaleSpinner",
372     "rotateSpinner",
373     "enableRequestSdrToneMappingCheckBox",
374     "enableHdrEditingCheckBox",
375     "selectDemoFrameProcessorsButton"
376   })
enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled)377   private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
378     audioMimeSpinner.setEnabled(isAudioEnabled);
379     videoMimeSpinner.setEnabled(isVideoEnabled);
380     resolutionHeightSpinner.setEnabled(isVideoEnabled);
381     scaleSpinner.setEnabled(isVideoEnabled);
382     rotateSpinner.setEnabled(isVideoEnabled);
383     enableRequestSdrToneMappingCheckBox.setEnabled(
384         isRequestSdrToneMappingSupported() && isVideoEnabled);
385     enableHdrEditingCheckBox.setEnabled(isVideoEnabled);
386     selectDemoFrameProcessorsButton.setEnabled(isVideoEnabled);
387 
388     findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled);
389     findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled);
390     findViewById(R.id.resolution_height_text_view).setEnabled(isVideoEnabled);
391     findViewById(R.id.scale).setEnabled(isVideoEnabled);
392     findViewById(R.id.rotate).setEnabled(isVideoEnabled);
393     findViewById(R.id.request_sdr_tone_mapping)
394         .setEnabled(isRequestSdrToneMappingSupported() && isVideoEnabled);
395     findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled);
396   }
397 
isRequestSdrToneMappingSupported()398   private static boolean isRequestSdrToneMappingSupported() {
399     return Util.SDK_INT >= 31;
400   }
401 }
402