diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 97e431f75b..62889430ef 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -18,6 +18,7 @@ body: label: ExoPlayer Version description: What version of ExoPlayer are you using? options: + - 2.18.2 - 2.18.1 - 2.18.0 - 2.17.1 diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d8194abf8f..3bc2239e6c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,113 @@ # Release notes +### 2.18.2 (2022-11-22) + +This release corresponds to the +[AndroidX Media3 1.0.0-beta03 release](https://github.com/androidx/media/releases/tag/1.0.0-beta03). + +* Core library: + * Add `ExoPlayer.isTunnelingEnabled` to check if tunneling is enabled for + the currently selected tracks + ([#2518](https://github.com/google/ExoPlayer/issues/2518)). + * Add `WrappingMediaSource` to simplify wrapping a single `MediaSource` + ([#7279](https://github.com/google/ExoPlayer/issues/7279)). + * Discard back buffer before playback gets stuck due to insufficient + available memory. + * Close the Tracing "doSomeWork" block when offload is enabled. + * Fix session tracking problem with fast seeks in `PlaybackStatsListener` + ([#180](https://github.com/androidx/media/issues/180)). + * Send missing `onMediaItemTransition` callback when calling `seekToNext` + or `seekToPrevious` in a single-item playlist + ([#10667](https://github.com/google/ExoPlayer/issues/10667)). + * Add `Player.getSurfaceSize` that returns the size of the surface on + which the video is rendered. + * Fix bug where removing listeners during the player release can cause an + `IllegalStateException` + ([#10758](https://github.com/google/ExoPlayer/issues/10758)). +* Build: + * Enforce minimum `compileSdkVersion` to avoid compilation errors + ([#10684](https://github.com/google/ExoPlayer/issues/10684)). +* Track selection: + * Prefer other tracks to Dolby Vision if display does not support it. + ([#8944](https://github.com/google/ExoPlayer/issues/8944)). +* Downloads: + * Fix potential infinite loop in `ProgressiveDownloader` caused by + simultaneous download and playback with the same `PriorityTaskManager` + ([#10570](https://github.com/google/ExoPlayer/pull/10570)). + * Make download notification appear immediately + ([#183](https://github.com/androidx/media/pull/183)). + * Limit parallel download removals to 1 to avoid excessive thread creation + ([#10458](https://github.com/google/ExoPlayer/issues/10458)). +* Video: + * Try alternative decoder for Dolby Vision if display does not support it. + ([#9794](https://github.com/google/ExoPlayer/issues/9794)). +* Audio: + * Use `SingleThreadExecutor` for releasing `AudioTrack` instances to avoid + OutOfMemory errors when releasing multiple players at the same time + ([#10057](https://github.com/google/ExoPlayer/issues/10057)). + * Adds `AudioOffloadListener.onExperimentalOffloadedPlayback` for the + AudioTrack offload state. + ([#134](https://github.com/androidx/media/issues/134)). + * Make `AudioTrackBufferSizeProvider` a public interface. + * Add `ExoPlayer.setPreferredAudioDevice` to set the preferred audio + output device ([#135](https://github.com/androidx/media/issues/135)). + * Map 8-channel and 12-channel audio to the 7.1 and 7.1.4 channel masks + respectively on all Android versions + ([#10701](https://github.com/google/ExoPlayer/issues/10701)). +* Metadata: + * `MetadataRenderer` can now be configured to render metadata as soon as + they are available. Create an instance with + `MetadataRenderer(MetadataOutput, Looper, MetadataDecoderFactory, + boolean)` to specify whether the renderer will output metadata early or + in sync with the player position. +* DRM: + * Work around a bug in the Android 13 ClearKey implementation that returns + a non-empty but invalid license URL. + * Fix `setMediaDrmSession failed: session not opened` error when switching + between DRM schemes in a playlist (e.g. Widevine to ClearKey). +* Text: + * CEA-608: Ensure service switch commands on field 2 are handled correctly + ([#10666](https://github.com/google/ExoPlayer/issues/10666)). +* DASH: + * Parse `EventStream.presentationTimeOffset` from manifests + ([#10460](https://github.com/google/ExoPlayer/issues/10460)). +* UI: + * Use current overrides of the player as preset in + `TrackSelectionDialogBuilder` + ([#10429](https://github.com/google/ExoPlayer/issues/10429)). +* RTSP: + * Add H263 fragmented packet handling + ([#119](https://github.com/androidx/media/pull/119)). + * Add support for MP4A-LATM + ([#162](https://github.com/androidx/media/pull/162)). +* IMA: + * Add timeout for loading ad information to handle cases where the IMA SDK + gets stuck loading an ad + ([#10510](https://github.com/google/ExoPlayer/issues/10510)). + * Prevent skipping mid-roll ads when seeking to the end of the content + ([#10685](https://github.com/google/ExoPlayer/issues/10685)). + * Correctly calculate window duration for live streams with server-side + inserted ads, for example IMA DAI + ([#10764](https://github.com/google/ExoPlayer/issues/10764)). +* FFmpeg extension: + * Add newly required flags to link FFmpeg libraries with NDK 23.1.7779620 + and above ([#9933](https://github.com/google/ExoPlayer/issues/9933)). +* AV1 extension: + * Update CMake version to avoid incompatibilities with the latest Android + Studio releases + ([#9933](https://github.com/google/ExoPlayer/issues/9933)). +* Cast extension: + * Implement `getDeviceInfo()` to be able to identify `CastPlayer` when + controlling playback with a `MediaController` + ([#142](https://github.com/androidx/media/issues/142)). +* Transformer: + * Add muxer watchdog timer to detect when generating an output sample is + too slow. +* Remove deprecated symbols: + * Remove `Transformer.Builder.setOutputMimeType(String)`. This feature has + been removed. The MIME type will always be MP4 when the default muxer is + used. + ### 2.18.1 (2022-07-21) This release corresponds to the diff --git a/constants.gradle b/constants.gradle index afedfe1ec1..3068e7646b 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.18.1' - releaseVersionCode = 2_018_001 + releaseVersion = '2.18.2' + releaseVersionCode = 2_018_002 minSdkVersion = 16 appTargetSdkVersion = 33 // API version before restricting local file access. diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index 5783424b52..36b3729453 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ android:largeHeap="true" android:allowBackup="false" android:requestLegacyExternalStorage="true" + android:supportsRtl="true" android:name="androidx.multidex.MultiDexApplication" tools:targetApi="29"> diff --git a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/BitmapOverlayProcessor.java b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/BitmapOverlayProcessor.java deleted file mode 100644 index 0f3beb8b8f..0000000000 --- a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/BitmapOverlayProcessor.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2022 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 com.google.android.exoplayer2.transformerdemo; - -import static com.google.android.exoplayer2.util.Assertions.checkArgument; -import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.drawable.BitmapDrawable; -import android.opengl.GLES20; -import android.opengl.GLUtils; -import android.util.Pair; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.effect.SingleFrameGlTextureProcessor; -import com.google.android.exoplayer2.util.FrameProcessingException; -import com.google.android.exoplayer2.util.GlProgram; -import com.google.android.exoplayer2.util.GlUtil; -import java.io.IOException; -import java.util.Locale; - -/** - * A {@link SingleFrameGlTextureProcessor} that overlays a bitmap with a logo and timer on each - * frame. - * - *

The bitmap is drawn using an Android {@link Canvas}. - */ -// TODO(b/227625365): Delete this class and use a texture processor from the Transformer library, -// once overlaying a bitmap and text is supported in Transformer. -/* package */ final class BitmapOverlayProcessor extends SingleFrameGlTextureProcessor { - - private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl"; - private static final String FRAGMENT_SHADER_PATH = "fragment_shader_bitmap_overlay_es2.glsl"; - - private static final int BITMAP_WIDTH_HEIGHT = 512; - - private final Paint paint; - private final Bitmap overlayBitmap; - private final Bitmap logoBitmap; - private final Canvas overlayCanvas; - private final GlProgram glProgram; - - private float bitmapScaleX; - private float bitmapScaleY; - private int bitmapTexId; - - /** - * Creates a new instance. - * - * @param context The {@link Context}. - * @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be - * in linear RGB BT.2020. If {@code false}, colors will be in linear RGB BT.709. - * @throws FrameProcessingException If a problem occurs while reading shader files. - */ - public BitmapOverlayProcessor(Context context, boolean useHdr) throws FrameProcessingException { - super(useHdr); - checkArgument(!useHdr, "BitmapOverlayProcessor does not support HDR colors."); - paint = new Paint(); - paint.setTextSize(64); - paint.setAntiAlias(true); - paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF); - paint.setColor(Color.GRAY); - overlayBitmap = - Bitmap.createBitmap(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT, Bitmap.Config.ARGB_8888); - overlayCanvas = new Canvas(overlayBitmap); - - try { - logoBitmap = - ((BitmapDrawable) - context.getPackageManager().getApplicationIcon(context.getPackageName())) - .getBitmap(); - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalStateException(e); - } - try { - bitmapTexId = - GlUtil.createTexture( - BITMAP_WIDTH_HEIGHT, - BITMAP_WIDTH_HEIGHT, - /* useHighPrecisionColorComponents= */ false); - GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0); - - glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); - } catch (GlUtil.GlException | IOException e) { - throw new FrameProcessingException(e); - } - // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y. - glProgram.setBufferAttribute( - "aFramePosition", - GlUtil.getNormalizedCoordinateBounds(), - GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); - glProgram.setSamplerTexIdUniform("uTexSampler1", bitmapTexId, /* texUnitIndex= */ 1); - } - - @Override - public Pair configure(int inputWidth, int inputHeight) { - if (inputWidth > inputHeight) { - bitmapScaleX = inputWidth / (float) inputHeight; - bitmapScaleY = 1f; - } else { - bitmapScaleX = 1f; - bitmapScaleY = inputHeight / (float) inputWidth; - } - - glProgram.setFloatUniform("uScaleX", bitmapScaleX); - glProgram.setFloatUniform("uScaleY", bitmapScaleY); - - return Pair.create(inputWidth, inputHeight); - } - - @Override - public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException { - try { - glProgram.use(); - - // Draw to the canvas and store it in a texture. - String text = - String.format(Locale.US, "%.02f", presentationTimeUs / (float) C.MICROS_PER_SECOND); - overlayBitmap.eraseColor(Color.TRANSPARENT); - overlayCanvas.drawBitmap(checkStateNotNull(logoBitmap), /* left= */ 3, /* top= */ 378, paint); - overlayCanvas.drawText(text, /* x= */ 160, /* y= */ 466, paint); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexId); - GLUtils.texSubImage2D( - GLES20.GL_TEXTURE_2D, - /* level= */ 0, - /* xoffset= */ 0, - /* yoffset= */ 0, - flipBitmapVertically(overlayBitmap)); - GlUtil.checkGlError(); - - glProgram.setSamplerTexIdUniform("uTexSampler0", inputTexId, /* texUnitIndex= */ 0); - glProgram.bindAttributesAndUniforms(); - // The four-vertex triangle strip forms a quad. - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); - GlUtil.checkGlError(); - } catch (GlUtil.GlException e) { - throw new FrameProcessingException(e, presentationTimeUs); - } - } - - @Override - public void release() throws FrameProcessingException { - super.release(); - try { - glProgram.delete(); - } catch (GlUtil.GlException e) { - throw new FrameProcessingException(e); - } - } - - private static Bitmap flipBitmapVertically(Bitmap bitmap) { - Matrix flip = new Matrix(); - flip.postScale(1f, -1f); - return Bitmap.createBitmap( - bitmap, - /* x= */ 0, - /* y= */ 0, - bitmap.getWidth(), - bitmap.getHeight(), - flip, - /* filter= */ true); - } -} diff --git a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/ConfigurationActivity.java b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/ConfigurationActivity.java index 06d72f9200..d257f8e23b 100644 --- a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/ConfigurationActivity.java +++ b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/ConfigurationActivity.java @@ -23,12 +23,14 @@ import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; +import android.graphics.Color; 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.EditText; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -59,6 +61,7 @@ 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 FORCE_SILENT_AUDIO = "force_silent_audio"; 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"; @@ -69,8 +72,10 @@ public final class ConfigurationActivity extends AppCompatActivity { public static final String TRIM_END_MS = "trim_end_ms"; public static final String ENABLE_FALLBACK = "enable_fallback"; public static final String ENABLE_DEBUG_PREVIEW = "enable_debug_preview"; + public static final String ABORT_SLOW_TRANSFORMATION = "abort_slow_transformation"; public static final String HDR_MODE = "hdr_mode"; - public static final String DEMO_EFFECTS_SELECTIONS = "demo_effects_selections"; + public static final String AUDIO_EFFECTS_SELECTIONS = "audio_effects_selections"; + public static final String VIDEO_EFFECTS_SELECTIONS = "video_effects_selections"; public static final String PERIODIC_VIGNETTE_CENTER_X = "periodic_vignette_center_x"; public static final String PERIODIC_VIGNETTE_CENTER_Y = "periodic_vignette_center_y"; public static final String PERIODIC_VIGNETTE_INNER_RADIUS = "periodic_vignette_inner_radius"; @@ -83,9 +88,37 @@ public final class ConfigurationActivity extends AppCompatActivity { public static final String HSL_ADJUSTMENTS_HUE = "hsl_adjustments_hue"; public static final String HSL_ADJUSTMENTS_SATURATION = "hsl_adjustments_saturation"; public static final String HSL_ADJUSTMENTS_LIGHTNESS = "hsl_adjustments_lightness"; + public static final String BITMAP_OVERLAY_URI = "bitmap_overlay_uri"; + public static final String BITMAP_OVERLAY_ALPHA = "bitmap_overlay_alpha"; + public static final String TEXT_OVERLAY_TEXT = "text_overlay_text"; + public static final String TEXT_OVERLAY_TEXT_COLOR = "text_overlay_text_color"; + public static final String TEXT_OVERLAY_ALPHA = "text_overlay_alpha"; + + // Video effect selections. + public static final int DIZZY_CROP_INDEX = 0; + public static final int EDGE_DETECTOR_INDEX = 1; + public static final int COLOR_FILTERS_INDEX = 2; + public static final int MAP_WHITE_TO_GREEN_LUT_INDEX = 3; + public static final int RGB_ADJUSTMENTS_INDEX = 4; + public static final int HSL_ADJUSTMENT_INDEX = 5; + public static final int CONTRAST_INDEX = 6; + public static final int PERIODIC_VIGNETTE_INDEX = 7; + public static final int SPIN_3D_INDEX = 8; + public static final int ZOOM_IN_INDEX = 9; + public static final int OVERLAY_LOGO_AND_TIMER_INDEX = 10; + public static final int BITMAP_OVERLAY_INDEX = 11; + public static final int TEXT_OVERLAY_INDEX = 12; + + // Audio effect selections. + public static final int HIGH_PITCHED_INDEX = 0; + public static final int SAMPLE_RATE_INDEX = 1; + public static final int SKIP_SILENCE_INDEX = 2; + + // Color filter options. public static final int COLOR_FILTER_GRAYSCALE = 0; public static final int COLOR_FILTER_INVERTED = 1; public static final int COLOR_FILTER_SEPIA = 2; + public static final int FILE_PERMISSION_REQUEST_CODE = 1; private static final String[] PRESET_FILE_URIS = { "https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4", @@ -102,6 +135,7 @@ public final class ConfigurationActivity extends AppCompatActivity { "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", }; private static final String[] PRESET_FILE_URI_DESCRIPTIONS = { // same order as PRESET_FILE_URIS "720p H264 video and AAC audio", @@ -118,8 +152,12 @@ public final class ConfigurationActivity extends AppCompatActivity { "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", }; - private static final String[] DEMO_EFFECTS = { + private static final String[] AUDIO_EFFECTS = { + "High pitched", "Sample rate of 48000Hz", "Skip silence" + }; + private static final String[] VIDEO_EFFECTS = { "Dizzy crop", "Edge detector (Media Pipe)", "Color filters", @@ -129,24 +167,40 @@ public final class ConfigurationActivity extends AppCompatActivity { "Contrast", "Periodic vignette", "3D spin", - "Overlay logo & timer", "Zoom in start", + "Overlay logo & timer", + "Custom Bitmap Overlay", + "Custom Text Overlay", }; private static final ImmutableMap HDR_MODE_DESCRIPTIONS = new ImmutableMap.Builder() .put("Keep HDR", TransformationRequest.HDR_MODE_KEEP_HDR) - .put("Tone-map HDR to SDR", TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR) + .put( + "MediaCodec tone-map HDR to SDR", + TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC) + .put( + "OpenGL tone-map HDR to SDR", + TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL) .put( "Force Interpret HDR as SDR", TransformationRequest.HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR) .build(); + private static final ImmutableMap OVERLAY_COLORS = + new ImmutableMap.Builder() + .put("BLACK", Color.BLACK) + .put("BLUE", Color.BLUE) + .put("CYAN", Color.CYAN) + .put("DKGRAY", Color.DKGRAY) + .put("GRAY", Color.GRAY) + .put("GREEN", Color.GREEN) + .put("LTGRAY", Color.LTGRAY) + .put("MAGENTA", Color.MAGENTA) + .put("RED", Color.RED) + .put("WHITE", Color.WHITE) + .put("YELLOW", Color.YELLOW) + .build(); private static final String SAME_AS_INPUT_OPTION = "same as input"; - private static final int COLOR_FILTERS_INDEX = 2; - private static final int RGB_ADJUSTMENTS_INDEX = 4; - private static final int HSL_ADJUSTMENT_INDEX = 5; - private static final int CONTRAST_INDEX = 6; - private static final int PERIODIC_VIGNETTE_INDEX = 7; private static final float HALF_DIAGONAL = 1f / (float) Math.sqrt(2); private @MonotonicNonNull ActivityResultLauncher localFilePickerLauncher; @@ -156,6 +210,7 @@ public final class ConfigurationActivity extends AppCompatActivity { private @MonotonicNonNull CheckBox removeAudioCheckbox; private @MonotonicNonNull CheckBox removeVideoCheckbox; private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox; + private @MonotonicNonNull CheckBox forceSilentAudioCheckbox; private @MonotonicNonNull Spinner audioMimeSpinner; private @MonotonicNonNull Spinner videoMimeSpinner; private @MonotonicNonNull Spinner resolutionHeightSpinner; @@ -164,9 +219,12 @@ public final class ConfigurationActivity extends AppCompatActivity { private @MonotonicNonNull CheckBox trimCheckBox; private @MonotonicNonNull CheckBox enableFallbackCheckBox; private @MonotonicNonNull CheckBox enableDebugPreviewCheckBox; + private @MonotonicNonNull CheckBox abortSlowTransformationCheckBox; private @MonotonicNonNull Spinner hdrModeSpinner; - private @MonotonicNonNull Button selectDemoEffectsButton; - private boolean @MonotonicNonNull [] demoEffectsSelections; + private @MonotonicNonNull Button selectAudioEffectsButton; + private @MonotonicNonNull Button selectVideoEffectsButton; + private boolean @MonotonicNonNull [] audioEffectsSelections; + private boolean @MonotonicNonNull [] videoEffectsSelections; private @Nullable Uri localFileUri; private int inputUriPosition; private long trimStartMs; @@ -183,6 +241,11 @@ public final class ConfigurationActivity extends AppCompatActivity { private float periodicVignetteCenterY; private float periodicVignetteInnerRadius; private float periodicVignetteOuterRadius; + private @MonotonicNonNull String bitmapOverlayUri; + private float bitmapOverlayAlpha; + private @MonotonicNonNull String textOverlayText; + private int textOverlayTextColor; + private float textOverlayAlpha; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -191,7 +254,11 @@ public final class ConfigurationActivity extends AppCompatActivity { findViewById(R.id.transform_button).setOnClickListener(this::startTransformation); - flattenForSlowMotionCheckbox = findViewById(R.id.flatten_for_slow_motion_checkbox); + selectPresetFileButton = findViewById(R.id.select_preset_file_button); + selectPresetFileButton.setOnClickListener(this::selectPresetFile); + + selectLocalFileButton = findViewById(R.id.select_local_file_button); + selectLocalFileButton.setOnClickListener(this::selectLocalFile); selectedFileTextView = findViewById(R.id.selected_file_text_view); selectedFileTextView.setText(PRESET_FILE_URI_DESCRIPTIONS[inputUriPosition]); @@ -202,11 +269,9 @@ public final class ConfigurationActivity extends AppCompatActivity { removeVideoCheckbox = findViewById(R.id.remove_video_checkbox); removeVideoCheckbox.setOnClickListener(this::onRemoveVideo); - selectPresetFileButton = findViewById(R.id.select_preset_file_button); - selectPresetFileButton.setOnClickListener(this::selectPresetFile); + flattenForSlowMotionCheckbox = findViewById(R.id.flatten_for_slow_motion_checkbox); - selectLocalFileButton = findViewById(R.id.select_local_file_button); - selectLocalFileButton.setOnClickListener(this::selectLocalFile); + forceSilentAudioCheckbox = findViewById(R.id.force_silent_audio_checkbox); ArrayAdapter audioMimeAdapter = new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item); @@ -257,6 +322,8 @@ public final class ConfigurationActivity extends AppCompatActivity { enableFallbackCheckBox = findViewById(R.id.enable_fallback_checkbox); enableDebugPreviewCheckBox = findViewById(R.id.enable_debug_preview_checkbox); + abortSlowTransformationCheckBox = findViewById(R.id.abort_slow_transformation_checkbox); + ArrayAdapter hdrModeAdapter = new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item); hdrModeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); @@ -264,9 +331,13 @@ public final class ConfigurationActivity extends AppCompatActivity { hdrModeSpinner.setAdapter(hdrModeAdapter); hdrModeAdapter.addAll(HDR_MODE_DESCRIPTIONS.keySet()); - demoEffectsSelections = new boolean[DEMO_EFFECTS.length]; - selectDemoEffectsButton = findViewById(R.id.select_demo_effects_button); - selectDemoEffectsButton.setOnClickListener(this::selectDemoEffects); + audioEffectsSelections = new boolean[AUDIO_EFFECTS.length]; + selectAudioEffectsButton = findViewById(R.id.select_audio_effects_button); + selectAudioEffectsButton.setOnClickListener(this::selectAudioEffects); + + videoEffectsSelections = new boolean[VIDEO_EFFECTS.length]; + selectVideoEffectsButton = findViewById(R.id.select_video_effects_button); + selectVideoEffectsButton.setOnClickListener(this::selectVideoEffects); localFilePickerLauncher = registerForActivityResult( @@ -311,6 +382,7 @@ public final class ConfigurationActivity extends AppCompatActivity { "removeAudioCheckbox", "removeVideoCheckbox", "flattenForSlowMotionCheckbox", + "forceSilentAudioCheckbox", "audioMimeSpinner", "videoMimeSpinner", "resolutionHeightSpinner", @@ -319,8 +391,10 @@ public final class ConfigurationActivity extends AppCompatActivity { "trimCheckBox", "enableFallbackCheckBox", "enableDebugPreviewCheckBox", + "abortSlowTransformationCheckBox", "hdrModeSpinner", - "demoEffectsSelections" + "audioEffectsSelections", + "videoEffectsSelections" }) private void startTransformation(View view) { Intent transformerIntent = new Intent(/* packageContext= */ this, TransformerActivity.class); @@ -328,6 +402,7 @@ public final class ConfigurationActivity extends AppCompatActivity { bundle.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.isChecked()); bundle.putBoolean(SHOULD_REMOVE_VIDEO, removeVideoCheckbox.isChecked()); bundle.putBoolean(SHOULD_FLATTEN_FOR_SLOW_MOTION, flattenForSlowMotionCheckbox.isChecked()); + bundle.putBoolean(FORCE_SILENT_AUDIO, forceSilentAudioCheckbox.isChecked()); String selectedAudioMimeType = String.valueOf(audioMimeSpinner.getSelectedItem()); if (!SAME_AS_INPUT_OPTION.equals(selectedAudioMimeType)) { bundle.putString(AUDIO_MIME_TYPE, selectedAudioMimeType); @@ -357,9 +432,11 @@ public final class ConfigurationActivity extends AppCompatActivity { } bundle.putBoolean(ENABLE_FALLBACK, enableFallbackCheckBox.isChecked()); bundle.putBoolean(ENABLE_DEBUG_PREVIEW, enableDebugPreviewCheckBox.isChecked()); + bundle.putBoolean(ABORT_SLOW_TRANSFORMATION, abortSlowTransformationCheckBox.isChecked()); String selectedhdrMode = String.valueOf(hdrModeSpinner.getSelectedItem()); bundle.putInt(HDR_MODE, checkNotNull(HDR_MODE_DESCRIPTIONS.get(selectedhdrMode))); - bundle.putBooleanArray(DEMO_EFFECTS_SELECTIONS, demoEffectsSelections); + bundle.putBooleanArray(AUDIO_EFFECTS_SELECTIONS, audioEffectsSelections); + bundle.putBooleanArray(VIDEO_EFFECTS_SELECTIONS, videoEffectsSelections); bundle.putInt(COLOR_FILTER_SELECTION, colorFilterSelection); bundle.putFloat(CONTRAST_VALUE, contrastValue); bundle.putFloat(RGB_ADJUSTMENT_RED_SCALE, rgbAdjustmentRedScale); @@ -372,6 +449,11 @@ public final class ConfigurationActivity extends AppCompatActivity { bundle.putFloat(PERIODIC_VIGNETTE_CENTER_Y, periodicVignetteCenterY); bundle.putFloat(PERIODIC_VIGNETTE_INNER_RADIUS, periodicVignetteInnerRadius); bundle.putFloat(PERIODIC_VIGNETTE_OUTER_RADIUS, periodicVignetteOuterRadius); + bundle.putString(BITMAP_OVERLAY_URI, bitmapOverlayUri); + bundle.putFloat(BITMAP_OVERLAY_ALPHA, bitmapOverlayAlpha); + bundle.putString(TEXT_OVERLAY_TEXT, textOverlayText); + bundle.putInt(TEXT_OVERLAY_TEXT_COLOR, textOverlayTextColor); + bundle.putFloat(TEXT_OVERLAY_ALPHA, textOverlayAlpha); transformerIntent.putExtras(bundle); @Nullable Uri intentUri; @@ -432,11 +514,21 @@ public final class ConfigurationActivity extends AppCompatActivity { } } - private void selectDemoEffects(View view) { + private void selectAudioEffects(View view) { new AlertDialog.Builder(/* context= */ this) - .setTitle(R.string.select_demo_effects) + .setTitle(R.string.select_audio_effects) .setMultiChoiceItems( - DEMO_EFFECTS, checkNotNull(demoEffectsSelections), this::selectDemoEffect) + AUDIO_EFFECTS, checkNotNull(audioEffectsSelections), this::selectAudioEffect) + .setPositiveButton(android.R.string.ok, /* listener= */ null) + .create() + .show(); + } + + private void selectVideoEffects(View view) { + new AlertDialog.Builder(/* context= */ this) + .setTitle(R.string.select_video_effects) + .setMultiChoiceItems( + VIDEO_EFFECTS, checkNotNull(videoEffectsSelections), this::selectVideoEffect) .setPositiveButton(android.R.string.ok, /* listener= */ null) .create() .show(); @@ -463,9 +555,14 @@ public final class ConfigurationActivity extends AppCompatActivity { .show(); } - @RequiresNonNull("demoEffectsSelections") - private void selectDemoEffect(DialogInterface dialog, int which, boolean isChecked) { - demoEffectsSelections[which] = isChecked; + @RequiresNonNull("audioEffectsSelections") + private void selectAudioEffect(DialogInterface dialog, int which, boolean isChecked) { + audioEffectsSelections[which] = isChecked; + } + + @RequiresNonNull("videoEffectsSelections") + private void selectVideoEffect(DialogInterface dialog, int which, boolean isChecked) { + videoEffectsSelections[which] = isChecked; if (!isChecked) { return; } @@ -486,6 +583,12 @@ public final class ConfigurationActivity extends AppCompatActivity { case PERIODIC_VIGNETTE_INDEX: controlPeriodicVignetteSettings(); break; + case BITMAP_OVERLAY_INDEX: + controlBitmapOverlaySettings(); + break; + case TEXT_OVERLAY_INDEX: + controlTextOverlaySettings(); + break; } } @@ -588,8 +691,54 @@ public final class ConfigurationActivity extends AppCompatActivity { .show(); } + private void controlBitmapOverlaySettings() { + View dialogView = + getLayoutInflater().inflate(R.layout.bitmap_overlay_options, /* root= */ null); + EditText uriEditText = checkNotNull(dialogView.findViewById(R.id.bitmap_overlay_uri)); + Slider alphaSlider = checkNotNull(dialogView.findViewById(R.id.bitmap_overlay_alpha_slider)); + new AlertDialog.Builder(/* context= */ this) + .setTitle(R.string.bitmap_overlay_settings) + .setView(dialogView) + .setPositiveButton( + android.R.string.ok, + (DialogInterface dialogInterface, int i) -> { + bitmapOverlayUri = uriEditText.getText().toString(); + bitmapOverlayAlpha = alphaSlider.getValue(); + }) + .create() + .show(); + } + + private void controlTextOverlaySettings() { + View dialogView = getLayoutInflater().inflate(R.layout.text_overlay_options, /* root= */ null); + EditText textEditText = checkNotNull(dialogView.findViewById(R.id.text_overlay_text)); + + ArrayAdapter textColorAdapter = + new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item); + textColorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + Spinner textColorSpinner = checkNotNull(dialogView.findViewById(R.id.text_overlay_text_color)); + textColorSpinner.setAdapter(textColorAdapter); + textColorAdapter.addAll(OVERLAY_COLORS.keySet()); + + Slider alphaSlider = checkNotNull(dialogView.findViewById(R.id.text_overlay_alpha_slider)); + new AlertDialog.Builder(/* context= */ this) + .setTitle(R.string.bitmap_overlay_settings) + .setView(dialogView) + .setPositiveButton( + android.R.string.ok, + (DialogInterface dialogInterface, int i) -> { + textOverlayText = textEditText.getText().toString(); + String selectedTextColor = String.valueOf(textColorSpinner.getSelectedItem()); + textOverlayTextColor = checkNotNull(OVERLAY_COLORS.get(selectedTextColor)); + textOverlayAlpha = alphaSlider.getValue(); + }) + .create() + .show(); + } + @RequiresNonNull({ "removeVideoCheckbox", + "forceSilentAudioCheckbox", "audioMimeSpinner", "videoMimeSpinner", "resolutionHeightSpinner", @@ -597,7 +746,8 @@ public final class ConfigurationActivity extends AppCompatActivity { "rotateSpinner", "enableDebugPreviewCheckBox", "hdrModeSpinner", - "selectDemoEffectsButton" + "selectAudioEffectsButton", + "selectVideoEffectsButton" }) private void onRemoveAudio(View view) { if (((CheckBox) view).isChecked()) { @@ -610,6 +760,7 @@ public final class ConfigurationActivity extends AppCompatActivity { @RequiresNonNull({ "removeAudioCheckbox", + "forceSilentAudioCheckbox", "audioMimeSpinner", "videoMimeSpinner", "resolutionHeightSpinner", @@ -617,7 +768,8 @@ public final class ConfigurationActivity extends AppCompatActivity { "rotateSpinner", "enableDebugPreviewCheckBox", "hdrModeSpinner", - "selectDemoEffectsButton" + "selectAudioEffectsButton", + "selectVideoEffectsButton" }) private void onRemoveVideo(View view) { if (((CheckBox) view).isChecked()) { @@ -629,6 +781,7 @@ public final class ConfigurationActivity extends AppCompatActivity { } @RequiresNonNull({ + "forceSilentAudioCheckbox", "audioMimeSpinner", "videoMimeSpinner", "resolutionHeightSpinner", @@ -636,9 +789,11 @@ public final class ConfigurationActivity extends AppCompatActivity { "rotateSpinner", "enableDebugPreviewCheckBox", "hdrModeSpinner", - "selectDemoEffectsButton" + "selectAudioEffectsButton", + "selectVideoEffectsButton" }) private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) { + forceSilentAudioCheckbox.setEnabled(isVideoEnabled); audioMimeSpinner.setEnabled(isAudioEnabled); videoMimeSpinner.setEnabled(isVideoEnabled); resolutionHeightSpinner.setEnabled(isVideoEnabled); @@ -646,7 +801,8 @@ public final class ConfigurationActivity extends AppCompatActivity { rotateSpinner.setEnabled(isVideoEnabled); enableDebugPreviewCheckBox.setEnabled(isVideoEnabled); hdrModeSpinner.setEnabled(isVideoEnabled); - selectDemoEffectsButton.setEnabled(isVideoEnabled); + selectAudioEffectsButton.setEnabled(isAudioEnabled); + selectVideoEffectsButton.setEnabled(isVideoEnabled); findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled); findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled); diff --git a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/PeriodicVignetteProcessor.java b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/PeriodicVignetteProcessor.java index 57209af87c..4293753b06 100644 --- a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/PeriodicVignetteProcessor.java +++ b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/PeriodicVignetteProcessor.java @@ -19,11 +19,11 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument; import android.content.Context; import android.opengl.GLES20; -import android.util.Pair; import com.google.android.exoplayer2.effect.SingleFrameGlTextureProcessor; import com.google.android.exoplayer2.util.FrameProcessingException; import com.google.android.exoplayer2.util.GlProgram; import com.google.android.exoplayer2.util.GlUtil; +import com.google.android.exoplayer2.util.Size; import java.io.IOException; /** @@ -90,8 +90,8 @@ import java.io.IOException; } @Override - public Pair configure(int inputWidth, int inputHeight) { - return Pair.create(inputWidth, inputHeight); + public Size configure(int inputWidth, int inputHeight) { + return new Size(inputWidth, inputHeight); } @Override diff --git a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TimerOverlay.java b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TimerOverlay.java new file mode 100644 index 0000000000..bea0b66b7b --- /dev/null +++ b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TimerOverlay.java @@ -0,0 +1,66 @@ +/* + * Copyright 2022 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 com.google.android.exoplayer2.transformerdemo; + +import android.graphics.Color; +import android.opengl.Matrix; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.effect.OverlaySettings; +import com.google.android.exoplayer2.effect.TextOverlay; +import com.google.android.exoplayer2.effect.TextureOverlay; +import com.google.android.exoplayer2.util.GlUtil; +import java.util.Locale; + +/** + * A {@link TextureOverlay} that displays a "time elapsed" timer in the bottom left corner of the + * frame. + */ +/* package */ final class TimerOverlay extends TextOverlay { + + private final OverlaySettings overlaySettings; + + public TimerOverlay() { + float[] positioningMatrix = GlUtil.create4x4IdentityMatrix(); + Matrix.translateM( + positioningMatrix, /* mOffset= */ 0, /* x= */ -0.7f, /* y= */ -0.95f, /* z= */ 1); + overlaySettings = + new OverlaySettings.Builder() + .setAnchor(/* x= */ -1f, /* y= */ -1f) + .setMatrix(positioningMatrix) + .build(); + } + + @Override + public SpannableString getText(long presentationTimeUs) { + SpannableString text = + new SpannableString( + String.format(Locale.US, "%.02f", presentationTimeUs / (float) C.MICROS_PER_SECOND)); + text.setSpan( + new ForegroundColorSpan(Color.WHITE), + /* start= */ 0, + text.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return text; + } + + @Override + public OverlaySettings getOverlaySettings(long presentationTimeUs) { + return overlaySettings; + } +} diff --git a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java index 0f7a8b31a9..afe31bee74 100644 --- a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java +++ b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java @@ -16,17 +16,24 @@ package com.google.android.exoplayer2.transformerdemo; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_NOT_STARTED; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; +import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.net.Uri; +import android.opengl.Matrix; import android.os.Bundle; import android.os.Handler; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; @@ -40,15 +47,25 @@ import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.audio.AudioProcessor; +import com.google.android.exoplayer2.audio.SilenceSkippingAudioProcessor; +import com.google.android.exoplayer2.audio.SonicAudioProcessor; +import com.google.android.exoplayer2.effect.BitmapOverlay; import com.google.android.exoplayer2.effect.Contrast; +import com.google.android.exoplayer2.effect.DrawableOverlay; import com.google.android.exoplayer2.effect.GlEffect; import com.google.android.exoplayer2.effect.GlTextureProcessor; import com.google.android.exoplayer2.effect.HslAdjustment; +import com.google.android.exoplayer2.effect.OverlayEffect; +import com.google.android.exoplayer2.effect.OverlaySettings; import com.google.android.exoplayer2.effect.RgbAdjustment; import com.google.android.exoplayer2.effect.RgbFilter; import com.google.android.exoplayer2.effect.RgbMatrix; import com.google.android.exoplayer2.effect.SingleColorLut; +import com.google.android.exoplayer2.effect.TextOverlay; +import com.google.android.exoplayer2.effect.TextureOverlay; import com.google.android.exoplayer2.transformer.DefaultEncoderFactory; +import com.google.android.exoplayer2.transformer.DefaultMuxer; import com.google.android.exoplayer2.transformer.ProgressHolder; import com.google.android.exoplayer2.transformer.TransformationException; import com.google.android.exoplayer2.transformer.TransformationRequest; @@ -59,6 +76,7 @@ import com.google.android.exoplayer2.ui.StyledPlayerView; import com.google.android.exoplayer2.util.DebugTextViewHelper; import com.google.android.exoplayer2.util.DebugViewProvider; import com.google.android.exoplayer2.util.Effect; +import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import com.google.android.material.card.MaterialCardView; @@ -179,19 +197,24 @@ public final class TransformerActivity extends AppCompatActivity { Uri uri = checkNotNull(intent.getData()); try { externalCacheFile = createExternalCacheFile("transformer-output.mp4"); - String filePath = externalCacheFile.getAbsolutePath(); - @Nullable Bundle bundle = intent.getExtras(); - MediaItem mediaItem = createMediaItem(bundle, uri); + } catch (IOException e) { + throw new IllegalStateException(e); + } + String filePath = externalCacheFile.getAbsolutePath(); + @Nullable Bundle bundle = intent.getExtras(); + MediaItem mediaItem = createMediaItem(bundle, uri); + try { Transformer transformer = createTransformer(bundle, filePath); transformationStopwatch.start(); transformer.startTransformation(mediaItem, filePath); this.transformer = transformer; - } catch (IOException e) { + } catch (PackageManager.NameNotFoundException e) { throw new IllegalStateException(e); } - informationTextView.setText(R.string.transformation_started); inputCardView.setVisibility(View.GONE); outputPlayerView.setVisibility(View.GONE); + informationTextView.setText(R.string.transformation_started); + progressViewGroup.setVisibility(View.VISIBLE); Handler mainHandler = new Handler(getMainLooper()); ProgressHolder progressHolder = new ProgressHolder(); mainHandler.post( @@ -199,8 +222,7 @@ public final class TransformerActivity extends AppCompatActivity { @Override public void run() { if (transformer != null - && transformer.getProgress(progressHolder) - != Transformer.PROGRESS_STATE_NO_TRANSFORMATION) { + && transformer.getProgress(progressHolder) != PROGRESS_STATE_NOT_STARTED) { progressIndicator.setProgress(progressHolder.progress); informationTextView.setText( getString( @@ -241,7 +263,8 @@ public final class TransformerActivity extends AppCompatActivity { "progressViewGroup", "debugFrame", }) - private Transformer createTransformer(@Nullable Bundle bundle, String filePath) { + private Transformer createTransformer(@Nullable Bundle bundle, String filePath) + throws PackageManager.NameNotFoundException { Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this); if (bundle != null) { TransformationRequest.Builder requestBuilder = new TransformationRequest.Builder(); @@ -274,29 +297,40 @@ public final class TransformerActivity extends AppCompatActivity { .setTransformationRequest(requestBuilder.build()) .setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO)) .setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO)) + .experimentalSetForceSilentAudio( + bundle.getBoolean(ConfigurationActivity.FORCE_SILENT_AUDIO)) .setEncoderFactory( new DefaultEncoderFactory.Builder(this.getApplicationContext()) .setEnableFallback(bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK)) .build()); - transformerBuilder.setVideoEffects(createVideoEffectsListFromBundle(bundle)); + transformerBuilder.setAudioProcessors(createAudioProcessorsFromBundle(bundle)); + transformerBuilder.setVideoEffects(createVideoEffectsFromBundle(bundle)); if (bundle.getBoolean(ConfigurationActivity.ENABLE_DEBUG_PREVIEW)) { transformerBuilder.setDebugViewProvider(new DemoDebugViewProvider()); } + + if (!bundle.getBoolean(ConfigurationActivity.ABORT_SLOW_TRANSFORMATION)) { + transformerBuilder.setMuxerFactory( + new DefaultMuxer.Factory(/* maxDelayBetweenSamplesMs= */ C.TIME_UNSET)); + } } + return transformerBuilder .addListener( new Transformer.Listener() { @Override public void onTransformationCompleted( - MediaItem mediaItem, TransformationResult transformationResult) { + MediaItem mediaItem, TransformationResult result) { TransformerActivity.this.onTransformationCompleted(filePath, mediaItem); } @Override public void onTransformationError( - MediaItem mediaItem, TransformationException exception) { + MediaItem mediaItem, + TransformationResult result, + TransformationException exception) { TransformerActivity.this.onTransformationError(exception); } }) @@ -315,18 +349,48 @@ public final class TransformerActivity extends AppCompatActivity { return file; } - private ImmutableList createVideoEffectsListFromBundle(Bundle bundle) { + private ImmutableList createAudioProcessorsFromBundle(Bundle bundle) { @Nullable - boolean[] selectedEffects = - bundle.getBooleanArray(ConfigurationActivity.DEMO_EFFECTS_SELECTIONS); - if (selectedEffects == null) { + boolean[] selectedAudioEffects = + bundle.getBooleanArray(ConfigurationActivity.AUDIO_EFFECTS_SELECTIONS); + + if (selectedAudioEffects == null) { return ImmutableList.of(); } + + ImmutableList.Builder processors = new ImmutableList.Builder<>(); + + if (selectedAudioEffects[ConfigurationActivity.HIGH_PITCHED_INDEX] + || selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_INDEX]) { + SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); + if (selectedAudioEffects[ConfigurationActivity.HIGH_PITCHED_INDEX]) { + sonicAudioProcessor.setPitch(2f); + } + if (selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_INDEX]) { + sonicAudioProcessor.setOutputSampleRateHz(48_000); + } + processors.add(sonicAudioProcessor); + } + + if (selectedAudioEffects[ConfigurationActivity.SKIP_SILENCE_INDEX]) { + SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = + new SilenceSkippingAudioProcessor(); + silenceSkippingAudioProcessor.setEnabled(true); + processors.add(silenceSkippingAudioProcessor); + } + + return processors.build(); + } + + private ImmutableList createVideoEffectsFromBundle(Bundle bundle) + throws PackageManager.NameNotFoundException { + boolean[] selectedEffects = + checkStateNotNull(bundle.getBooleanArray(ConfigurationActivity.VIDEO_EFFECTS_SELECTIONS)); ImmutableList.Builder effects = new ImmutableList.Builder<>(); - if (selectedEffects[0]) { + if (selectedEffects[ConfigurationActivity.DIZZY_CROP_INDEX]) { effects.add(MatrixTransformationFactory.createDizzyCropEffect()); } - if (selectedEffects[1]) { + if (selectedEffects[ConfigurationActivity.EDGE_DETECTOR_INDEX]) { try { Class clazz = Class.forName("com.google.android.exoplayer2.transformerdemo.MediaPipeProcessor"); @@ -359,7 +423,7 @@ public final class TransformerActivity extends AppCompatActivity { showToast(R.string.no_media_pipe_error); } } - if (selectedEffects[2]) { + if (selectedEffects[ConfigurationActivity.COLOR_FILTERS_INDEX]) { switch (bundle.getInt(ConfigurationActivity.COLOR_FILTER_SELECTION)) { case ConfigurationActivity.COLOR_FILTER_GRAYSCALE: effects.add(RgbFilter.createGrayscaleFilter()); @@ -385,7 +449,7 @@ public final class TransformerActivity extends AppCompatActivity { + bundle.getInt(ConfigurationActivity.COLOR_FILTER_SELECTION)); } } - if (selectedEffects[3]) { + if (selectedEffects[ConfigurationActivity.MAP_WHITE_TO_GREEN_LUT_INDEX]) { int length = 3; int[][][] mapWhiteToGreenLut = new int[length][length][length]; int scale = 255 / (length - 1); @@ -400,7 +464,7 @@ public final class TransformerActivity extends AppCompatActivity { mapWhiteToGreenLut[length - 1][length - 1][length - 1] = Color.GREEN; effects.add(SingleColorLut.createFromCube(mapWhiteToGreenLut)); } - if (selectedEffects[4]) { + if (selectedEffects[ConfigurationActivity.RGB_ADJUSTMENTS_INDEX]) { effects.add( new RgbAdjustment.Builder() .setRedScale(bundle.getFloat(ConfigurationActivity.RGB_ADJUSTMENT_RED_SCALE)) @@ -408,7 +472,7 @@ public final class TransformerActivity extends AppCompatActivity { .setBlueScale(bundle.getFloat(ConfigurationActivity.RGB_ADJUSTMENT_BLUE_SCALE)) .build()); } - if (selectedEffects[5]) { + if (selectedEffects[ConfigurationActivity.HSL_ADJUSTMENT_INDEX]) { effects.add( new HslAdjustment.Builder() .adjustHue(bundle.getFloat(ConfigurationActivity.HSL_ADJUSTMENTS_HUE)) @@ -416,10 +480,10 @@ public final class TransformerActivity extends AppCompatActivity { .adjustLightness(bundle.getFloat(ConfigurationActivity.HSL_ADJUSTMENTS_LIGHTNESS)) .build()); } - if (selectedEffects[6]) { + if (selectedEffects[ConfigurationActivity.CONTRAST_INDEX]) { effects.add(new Contrast(bundle.getFloat(ConfigurationActivity.CONTRAST_VALUE))); } - if (selectedEffects[7]) { + if (selectedEffects[ConfigurationActivity.PERIODIC_VIGNETTE_INDEX]) { effects.add( (GlEffect) (Context context, boolean useHdr) -> @@ -434,18 +498,76 @@ public final class TransformerActivity extends AppCompatActivity { ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS), bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS))); } - if (selectedEffects[8]) { + if (selectedEffects[ConfigurationActivity.SPIN_3D_INDEX]) { effects.add(MatrixTransformationFactory.createSpin3dEffect()); } - if (selectedEffects[9]) { - effects.add((GlEffect) BitmapOverlayProcessor::new); - } - if (selectedEffects[10]) { + if (selectedEffects[ConfigurationActivity.ZOOM_IN_INDEX]) { effects.add(MatrixTransformationFactory.createZoomInTransition()); } + + @Nullable OverlayEffect overlayEffect = createOverlayEffectFromBundle(bundle, selectedEffects); + if (overlayEffect != null) { + effects.add(overlayEffect); + } + return effects.build(); } + @Nullable + private OverlayEffect createOverlayEffectFromBundle(Bundle bundle, boolean[] selectedEffects) + throws PackageManager.NameNotFoundException { + ImmutableList.Builder overlaysBuilder = new ImmutableList.Builder<>(); + if (selectedEffects[ConfigurationActivity.OVERLAY_LOGO_AND_TIMER_INDEX]) { + float[] logoPositioningMatrix = GlUtil.create4x4IdentityMatrix(); + Matrix.translateM( + logoPositioningMatrix, /* mOffset= */ 0, /* x= */ -0.95f, /* y= */ -0.95f, /* z= */ 1); + OverlaySettings logoSettings = + new OverlaySettings.Builder() + .setMatrix(logoPositioningMatrix) + .setAnchor(/* x= */ -1f, /* y= */ -1f) + .build(); + Drawable logo = getPackageManager().getApplicationIcon(getPackageName()); + logo.setBounds( + /* left= */ 0, /* top= */ 0, logo.getIntrinsicWidth(), logo.getIntrinsicHeight()); + TextureOverlay logoOverlay = DrawableOverlay.createStaticDrawableOverlay(logo, logoSettings); + TextureOverlay timerOverlay = new TimerOverlay(); + overlaysBuilder.add(logoOverlay, timerOverlay); + } + if (selectedEffects[ConfigurationActivity.BITMAP_OVERLAY_INDEX]) { + OverlaySettings overlaySettings = + new OverlaySettings.Builder() + .setAlpha( + bundle.getFloat( + ConfigurationActivity.BITMAP_OVERLAY_ALPHA, /* defaultValue= */ 1)) + .build(); + BitmapOverlay bitmapOverlay = + BitmapOverlay.createStaticBitmapOverlay( + Uri.parse(checkNotNull(bundle.getString(ConfigurationActivity.BITMAP_OVERLAY_URI))), + overlaySettings); + overlaysBuilder.add(bitmapOverlay); + } + if (selectedEffects[ConfigurationActivity.TEXT_OVERLAY_INDEX]) { + OverlaySettings overlaySettings = + new OverlaySettings.Builder() + .setAlpha( + bundle.getFloat(ConfigurationActivity.TEXT_OVERLAY_ALPHA, /* defaultValue= */ 1)) + .build(); + SpannableString overlayText = + new SpannableString( + checkNotNull(bundle.getString(ConfigurationActivity.TEXT_OVERLAY_TEXT))); + overlayText.setSpan( + new ForegroundColorSpan(bundle.getInt(ConfigurationActivity.TEXT_OVERLAY_TEXT_COLOR)), + /* start= */ 0, + overlayText.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + TextOverlay textOverlay = TextOverlay.createStaticTextOverlay(overlayText, overlaySettings); + overlaysBuilder.add(textOverlay); + } + + ImmutableList overlays = overlaysBuilder.build(); + return overlays.isEmpty() ? null : new OverlayEffect(overlays); + } + @RequiresNonNull({ "informationTextView", "progressViewGroup", @@ -453,7 +575,9 @@ public final class TransformerActivity extends AppCompatActivity { "transformationStopwatch", }) private void onTransformationError(TransformationException exception) { - transformationStopwatch.stop(); + if (transformationStopwatch.isRunning()) { + transformationStopwatch.stop(); + } informationTextView.setText(R.string.transformation_error); progressViewGroup.setVisibility(View.GONE); debugFrame.removeAllViews(); diff --git a/demos/transformer/src/main/res/layout/bitmap_overlay_options.xml b/demos/transformer/src/main/res/layout/bitmap_overlay_options.xml new file mode 100644 index 0000000000..3213dcdfc3 --- /dev/null +++ b/demos/transformer/src/main/res/layout/bitmap_overlay_options.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + diff --git a/demos/transformer/src/main/res/layout/configuration_activity.xml b/demos/transformer/src/main/res/layout/configuration_activity.xml index f652bb8a5c..09aa1287d1 100644 --- a/demos/transformer/src/main/res/layout/configuration_activity.xml +++ b/demos/transformer/src/main/res/layout/configuration_activity.xml @@ -75,7 +75,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view" - app:layout_constraintBottom_toTopOf="@+id/select_demo_effects_button"> + app:layout_constraintBottom_toTopOf="@+id/select_audio_effects_button"> + + + + @@ -197,6 +206,15 @@ android:layout_gravity="end" android:checked="true"/> + + + + @@ -211,13 +229,24 @@