mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add support for screen recording to the Transformer demo
Screen recording continues even if the transformer activity is backgrounded, to support recording other apps. PiperOrigin-RevId: 726454538
This commit is contained in:
parent
04d9a751c6
commit
9e22f03718
@ -8,7 +8,8 @@
|
|||||||
* ExoPlayer:
|
* ExoPlayer:
|
||||||
* Transformer:
|
* Transformer:
|
||||||
* Add `MediaProjectionAssetLoader`, which provides media from a
|
* Add `MediaProjectionAssetLoader`, which provides media from a
|
||||||
`MediaProjection` for screen recording.
|
`MediaProjection` for screen recording, and add support for screen
|
||||||
|
recording to the Transformer demo app.
|
||||||
* Add `#getInputFormat()` to `Codec` interface.
|
* Add `#getInputFormat()` to `Codec` interface.
|
||||||
* Shift the responsibility to release the `GlObjectsProvider` onto the
|
* Shift the responsibility to release the `GlObjectsProvider` onto the
|
||||||
caller in `DefaultVideoFrameProcessor` and `DefaultVideoCompositor` when
|
caller in `DefaultVideoFrameProcessor` and `DefaultVideoCompositor` when
|
||||||
|
@ -76,6 +76,7 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:' + androidxConstraintLayoutVersion
|
implementation 'androidx.constraintlayout:constraintlayout:' + androidxConstraintLayoutVersion
|
||||||
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion
|
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion
|
||||||
|
implementation 'androidx.window:window:' + androidxWindowVersion
|
||||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||||
implementation project(modulePrefix + 'lib-effect')
|
implementation project(modulePrefix + 'lib-effect')
|
||||||
implementation project(modulePrefix + 'lib-exoplayer')
|
implementation project(modulePrefix + 'lib-exoplayer')
|
||||||
|
@ -24,6 +24,10 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
|
||||||
|
|
||||||
|
<!-- For media projection. -->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@ -64,5 +68,9 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"/>
|
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"/>
|
||||||
|
<service
|
||||||
|
android:name=".TransformerActivity$DemoMediaProjectionService"
|
||||||
|
android:foregroundServiceType="mediaProjection"
|
||||||
|
android:exported="false"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.demo.transformer;
|
package androidx.media3.demo.transformer;
|
||||||
|
|
||||||
|
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
||||||
@ -22,15 +23,25 @@ import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PL
|
|||||||
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED;
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.media.projection.MediaProjection;
|
||||||
|
import android.media.projection.MediaProjectionManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
@ -42,9 +53,13 @@ import android.widget.Button;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.DebugViewProvider;
|
import androidx.media3.common.DebugViewProvider;
|
||||||
import androidx.media3.common.Effect;
|
import androidx.media3.common.Effect;
|
||||||
@ -91,10 +106,13 @@ import androidx.media3.transformer.ExportResult;
|
|||||||
import androidx.media3.transformer.InAppFragmentedMp4Muxer;
|
import androidx.media3.transformer.InAppFragmentedMp4Muxer;
|
||||||
import androidx.media3.transformer.InAppMp4Muxer;
|
import androidx.media3.transformer.InAppMp4Muxer;
|
||||||
import androidx.media3.transformer.JsonUtil;
|
import androidx.media3.transformer.JsonUtil;
|
||||||
|
import androidx.media3.transformer.MediaProjectionAssetLoader;
|
||||||
import androidx.media3.transformer.ProgressHolder;
|
import androidx.media3.transformer.ProgressHolder;
|
||||||
import androidx.media3.transformer.Transformer;
|
import androidx.media3.transformer.Transformer;
|
||||||
|
import androidx.media3.transformer.VideoEncoderSettings;
|
||||||
import androidx.media3.ui.AspectRatioFrameLayout;
|
import androidx.media3.ui.AspectRatioFrameLayout;
|
||||||
import androidx.media3.ui.PlayerView;
|
import androidx.media3.ui.PlayerView;
|
||||||
|
import androidx.window.layout.WindowMetricsCalculator;
|
||||||
import com.google.android.material.card.MaterialCardView;
|
import com.google.android.material.card.MaterialCardView;
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
||||||
import com.google.common.base.Stopwatch;
|
import com.google.common.base.Stopwatch;
|
||||||
@ -116,12 +134,13 @@ import org.json.JSONObject;
|
|||||||
public final class TransformerActivity extends AppCompatActivity {
|
public final class TransformerActivity extends AppCompatActivity {
|
||||||
private static final String TAG = "TransformerActivity";
|
private static final String TAG = "TransformerActivity";
|
||||||
private static final int IMAGE_DURATION_MS = 5_000;
|
private static final int IMAGE_DURATION_MS = 5_000;
|
||||||
private static final int IMAGE_FRAME_RATE_FPS = 30;
|
private static final int DEFAULT_FRAME_RATE_FPS = 30;
|
||||||
private static int LOAD_CONTROL_MIN_BUFFER_MS = 5_000;
|
private static int LOAD_CONTROL_MIN_BUFFER_MS = 5_000;
|
||||||
private static int LOAD_CONTROL_MAX_BUFFER_MS = 5_000;
|
private static int LOAD_CONTROL_MAX_BUFFER_MS = 5_000;
|
||||||
|
|
||||||
private Button displayInputButton;
|
private Button displayInputButton;
|
||||||
private MaterialCardView inputCardView;
|
private MaterialCardView inputCardView;
|
||||||
|
private MaterialCardView outputCardView;
|
||||||
private TextView inputTextView;
|
private TextView inputTextView;
|
||||||
private ImageView inputImageView;
|
private ImageView inputImageView;
|
||||||
private PlayerView inputPlayerView;
|
private PlayerView inputPlayerView;
|
||||||
@ -133,10 +152,13 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
private LinearProgressIndicator progressIndicator;
|
private LinearProgressIndicator progressIndicator;
|
||||||
private Button pauseButton;
|
private Button pauseButton;
|
||||||
private Button resumeButton;
|
private Button resumeButton;
|
||||||
|
private Button stopCaptureButton;
|
||||||
private Stopwatch exportStopwatch;
|
private Stopwatch exportStopwatch;
|
||||||
private AspectRatioFrameLayout debugFrame;
|
private AspectRatioFrameLayout debugFrame;
|
||||||
|
|
||||||
@Nullable private DebugTextViewHelper debugTextViewHelper;
|
@Nullable private DebugTextViewHelper debugTextViewHelper;
|
||||||
|
@Nullable private Intent screenCaptureToken;
|
||||||
|
@Nullable private MediaProjection mediaProjection;
|
||||||
@Nullable private ExoPlayer inputPlayer;
|
@Nullable private ExoPlayer inputPlayer;
|
||||||
@Nullable private ExoPlayer outputPlayer;
|
@Nullable private ExoPlayer outputPlayer;
|
||||||
@Nullable private Transformer transformer;
|
@Nullable private Transformer transformer;
|
||||||
@ -149,6 +171,7 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
setContentView(R.layout.transformer_activity);
|
setContentView(R.layout.transformer_activity);
|
||||||
|
|
||||||
inputCardView = findViewById(R.id.input_card_view);
|
inputCardView = findViewById(R.id.input_card_view);
|
||||||
|
outputCardView = findViewById(R.id.output_card_view);
|
||||||
inputTextView = findViewById(R.id.input_text_view);
|
inputTextView = findViewById(R.id.input_text_view);
|
||||||
inputImageView = findViewById(R.id.input_image_view);
|
inputImageView = findViewById(R.id.input_image_view);
|
||||||
inputPlayerView = findViewById(R.id.input_player_view);
|
inputPlayerView = findViewById(R.id.input_player_view);
|
||||||
@ -162,6 +185,8 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
pauseButton.setOnClickListener(view -> pauseExport());
|
pauseButton.setOnClickListener(view -> pauseExport());
|
||||||
resumeButton = findViewById(R.id.resume_button);
|
resumeButton = findViewById(R.id.resume_button);
|
||||||
resumeButton.setOnClickListener(view -> startExport());
|
resumeButton.setOnClickListener(view -> startExport());
|
||||||
|
stopCaptureButton = findViewById(R.id.stop_capture_button);
|
||||||
|
stopCaptureButton.setOnClickListener(view -> mediaProjection.stop());
|
||||||
debugFrame = findViewById(R.id.debug_aspect_ratio_frame_layout);
|
debugFrame = findViewById(R.id.debug_aspect_ratio_frame_layout);
|
||||||
displayInputButton = findViewById(R.id.display_input_button);
|
displayInputButton = findViewById(R.id.display_input_button);
|
||||||
displayInputButton.setOnClickListener(view -> toggleInputVideoDisplay());
|
displayInputButton.setOnClickListener(view -> toggleInputVideoDisplay());
|
||||||
@ -180,7 +205,10 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
startExport();
|
// Restart exporting, unless this is a capture session which can run in the background.
|
||||||
|
if (!isUsingMediaProjection()) {
|
||||||
|
startExport();
|
||||||
|
}
|
||||||
|
|
||||||
inputPlayerView.onResume();
|
inputPlayerView.onResume();
|
||||||
outputPlayerView.onResume();
|
outputPlayerView.onResume();
|
||||||
@ -192,13 +220,70 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
inputPlayerView.onPause();
|
inputPlayerView.onPause();
|
||||||
outputPlayerView.onPause();
|
outputPlayerView.onPause();
|
||||||
releasePlayers();
|
|
||||||
|
// Keep the capture session going to allow capturing other apps while backgrounded.
|
||||||
|
if (!isUsingMediaProjection()) {
|
||||||
|
releasePlayers();
|
||||||
|
cleanUpExport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
if (isUsingMediaProjection()) {
|
||||||
|
releasePlayers();
|
||||||
|
mediaProjection.stop();
|
||||||
|
mediaProjection = null;
|
||||||
|
screenCaptureToken = null;
|
||||||
|
}
|
||||||
cleanUpExport();
|
cleanUpExport();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startExport() {
|
private void startExport() {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
Uri inputUri = checkNotNull(intent.getData());
|
Uri inputUri = checkNotNull(intent.getData());
|
||||||
|
|
||||||
|
if (inputUri.toString().equals("transformer_surface_asset:media_projection")
|
||||||
|
&& screenCaptureToken == null) {
|
||||||
|
// MediaProjection can only start once the foreground service is running.
|
||||||
|
MediaProjectionManager mediaProjectionManager =
|
||||||
|
(MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
|
||||||
|
Context context = this;
|
||||||
|
LocalBroadcastManager.getInstance(context)
|
||||||
|
.registerReceiver(
|
||||||
|
new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = checkNotNull(intent.getAction());
|
||||||
|
if (action.equals(DemoMediaProjectionService.ACTION_EVENT_STARTED)) {
|
||||||
|
LocalBroadcastManager.getInstance(context)
|
||||||
|
.unregisterReceiver(/* receiver= */ this);
|
||||||
|
// The service has started so media projection can start.
|
||||||
|
startExport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new IntentFilter(DemoMediaProjectionService.ACTION_EVENT_STARTED));
|
||||||
|
registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
activityResult -> {
|
||||||
|
int resultCode = activityResult.getResultCode();
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
screenCaptureToken = activityResult.getData();
|
||||||
|
Intent startServiceIntent = new Intent(context, DemoMediaProjectionService.class);
|
||||||
|
ContextCompat.startForegroundService(context, startServiceIntent);
|
||||||
|
} else if (resultCode == RESULT_CANCELED) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.launch(mediaProjectionManager.createScreenCaptureIntent());
|
||||||
|
inputCardView.setVisibility(View.GONE);
|
||||||
|
outputCardView.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
outputFile =
|
outputFile =
|
||||||
createExternalCacheFile("transformer-output-" + Clock.DEFAULT.elapsedRealtime() + ".mp4");
|
createExternalCacheFile("transformer-output-" + Clock.DEFAULT.elapsedRealtime() + ".mp4");
|
||||||
@ -225,10 +310,20 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
outputVideoTextView.setVisibility(View.GONE);
|
outputVideoTextView.setVisibility(View.GONE);
|
||||||
debugTextView.setVisibility(View.GONE);
|
debugTextView.setVisibility(View.GONE);
|
||||||
informationTextView.setText(R.string.export_started);
|
informationTextView.setText(R.string.export_started);
|
||||||
|
outputCardView.setVisibility(View.VISIBLE);
|
||||||
progressViewGroup.setVisibility(View.VISIBLE);
|
progressViewGroup.setVisibility(View.VISIBLE);
|
||||||
pauseButton.setVisibility(View.VISIBLE);
|
pauseButton.setVisibility(View.VISIBLE);
|
||||||
resumeButton.setVisibility(View.GONE);
|
resumeButton.setVisibility(View.GONE);
|
||||||
progressIndicator.setProgress(0);
|
progressIndicator.setProgress(0);
|
||||||
|
if (isUsingMediaProjection()) {
|
||||||
|
pauseButton.setVisibility(View.GONE);
|
||||||
|
resumeButton.setVisibility(View.GONE);
|
||||||
|
stopCaptureButton.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
pauseButton.setVisibility(View.VISIBLE);
|
||||||
|
resumeButton.setVisibility(View.GONE);
|
||||||
|
stopCaptureButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
Handler mainHandler = new Handler(getMainLooper());
|
Handler mainHandler = new Handler(getMainLooper());
|
||||||
ProgressHolder progressHolder = new ProgressHolder();
|
ProgressHolder progressHolder = new ProgressHolder();
|
||||||
mainHandler.post(
|
mainHandler.post(
|
||||||
@ -294,11 +389,6 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
transformerBuilder.setVideoMimeType(videoMimeType);
|
transformerBuilder.setVideoMimeType(videoMimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
transformerBuilder.setEncoderFactory(
|
|
||||||
new DefaultEncoderFactory.Builder(this.getApplicationContext())
|
|
||||||
.setEnableFallback(bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK))
|
|
||||||
.build());
|
|
||||||
|
|
||||||
if (!bundle.getBoolean(ConfigurationActivity.ABORT_SLOW_EXPORT)) {
|
if (!bundle.getBoolean(ConfigurationActivity.ABORT_SLOW_EXPORT)) {
|
||||||
transformerBuilder.setMaxDelayBetweenMuxerSamplesMs(C.TIME_UNSET);
|
transformerBuilder.setMaxDelayBetweenMuxerSamplesMs(C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
@ -321,6 +411,32 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VideoEncoderSettings videoEncoderSettings = VideoEncoderSettings.DEFAULT;
|
||||||
|
if (screenCaptureToken != null) {
|
||||||
|
MediaProjectionManager mediaProjectionManager =
|
||||||
|
(MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
|
||||||
|
MediaProjection mediaProjection =
|
||||||
|
mediaProjectionManager.getMediaProjection(RESULT_OK, checkNotNull(screenCaptureToken));
|
||||||
|
Rect bounds =
|
||||||
|
WindowMetricsCalculator.getOrCreate()
|
||||||
|
.computeCurrentWindowMetrics(/* activity= */ this)
|
||||||
|
.getBounds();
|
||||||
|
int densityDpi = getResources().getConfiguration().densityDpi;
|
||||||
|
transformerBuilder.setAssetLoaderFactory(
|
||||||
|
new MediaProjectionAssetLoader.Factory(mediaProjection, bounds, densityDpi));
|
||||||
|
this.mediaProjection = mediaProjection;
|
||||||
|
videoEncoderSettings =
|
||||||
|
videoEncoderSettings
|
||||||
|
.buildUpon()
|
||||||
|
.setRepeatPreviousFrameIntervalUs(C.MICROS_PER_SECOND / DEFAULT_FRAME_RATE_FPS)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
transformerBuilder.setEncoderFactory(
|
||||||
|
new DefaultEncoderFactory.Builder(this.getApplicationContext())
|
||||||
|
.setEnableFallback(bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK))
|
||||||
|
.setRequestedVideoEncoderSettings(videoEncoderSettings)
|
||||||
|
.build());
|
||||||
|
|
||||||
return transformerBuilder.build();
|
return transformerBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,7 +455,7 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
private Composition createComposition(MediaItem mediaItem, @Nullable Bundle bundle) {
|
private Composition createComposition(MediaItem mediaItem, @Nullable Bundle bundle) {
|
||||||
EditedMediaItem.Builder editedMediaItemBuilder = new EditedMediaItem.Builder(mediaItem);
|
EditedMediaItem.Builder editedMediaItemBuilder = new EditedMediaItem.Builder(mediaItem);
|
||||||
// For image inputs. Automatically ignored if input is audio/video.
|
// For image inputs. Automatically ignored if input is audio/video.
|
||||||
editedMediaItemBuilder.setFrameRate(IMAGE_FRAME_RATE_FPS);
|
editedMediaItemBuilder.setFrameRate(DEFAULT_FRAME_RATE_FPS);
|
||||||
if (bundle != null) {
|
if (bundle != null) {
|
||||||
ImmutableList<AudioProcessor> audioProcessors = createAudioProcessorsFromBundle(bundle);
|
ImmutableList<AudioProcessor> audioProcessors = createAudioProcessorsFromBundle(bundle);
|
||||||
ImmutableList<Effect> videoEffects = createVideoEffectsFromBundle(bundle);
|
ImmutableList<Effect> videoEffects = createVideoEffectsFromBundle(bundle);
|
||||||
@ -640,6 +756,9 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
informationTextView.setText(R.string.export_error);
|
informationTextView.setText(R.string.export_error);
|
||||||
progressViewGroup.setVisibility(View.GONE);
|
progressViewGroup.setVisibility(View.GONE);
|
||||||
debugFrame.removeAllViews();
|
debugFrame.removeAllViews();
|
||||||
|
if (isUsingMediaProjection()) {
|
||||||
|
mediaProjection.stop();
|
||||||
|
}
|
||||||
Toast.makeText(getApplicationContext(), "Export error: " + exportException, Toast.LENGTH_LONG)
|
Toast.makeText(getApplicationContext(), "Export error: " + exportException, Toast.LENGTH_LONG)
|
||||||
.show();
|
.show();
|
||||||
Log.e(TAG, "Export error", exportException);
|
Log.e(TAG, "Export error", exportException);
|
||||||
@ -717,6 +836,12 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
} catch (ExecutionException | InterruptedException e) {
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
throw new IllegalArgumentException("Failed to load bitmap.", e);
|
throw new IllegalArgumentException("Failed to load bitmap.", e);
|
||||||
}
|
}
|
||||||
|
} else if (isUsingMediaProjection()) {
|
||||||
|
inputCardView.setVisibility(View.GONE);
|
||||||
|
displayInputButton.setVisibility(View.GONE);
|
||||||
|
Intent stopIntent = new Intent(/* context= */ this, DemoMediaProjectionService.class);
|
||||||
|
stopIntent.setAction(DemoMediaProjectionService.ACTION_STOP);
|
||||||
|
ContextCompat.startForegroundService(/* context= */ this, stopIntent);
|
||||||
} else {
|
} else {
|
||||||
inputPlayerView.setVisibility(View.VISIBLE);
|
inputPlayerView.setVisibility(View.VISIBLE);
|
||||||
inputImageView.setVisibility(View.GONE);
|
inputImageView.setVisibility(View.GONE);
|
||||||
@ -829,6 +954,54 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
oldOutputFile = outputFile;
|
oldOutputFile = outputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isUsingMediaProjection() {
|
||||||
|
return mediaProjection != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Foreground service that's required by the media projection APIs. */
|
||||||
|
public static final class DemoMediaProjectionService extends Service {
|
||||||
|
private static final String CHANNEL_ID = "DemoMediaProjectionServiceChannel";
|
||||||
|
private static final String CHANNEL_NAME = "Media projection";
|
||||||
|
private static final int NOTIFICATION_ID = 1;
|
||||||
|
private static final String ACTION_EVENT_STARTED = "started";
|
||||||
|
private static final String ACTION_STOP = "stop";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (ACTION_STOP.equals(intent.getAction())) {
|
||||||
|
stopSelf();
|
||||||
|
} else {
|
||||||
|
Context context = this;
|
||||||
|
Notification notification =
|
||||||
|
new NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setSmallIcon(R.drawable.exo_icon_play)
|
||||||
|
.build();
|
||||||
|
if (Util.SDK_INT >= 26) {
|
||||||
|
NotificationChannel channel =
|
||||||
|
new NotificationChannel(
|
||||||
|
CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||||
|
manager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
if (Util.SDK_INT >= 29) {
|
||||||
|
startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
|
||||||
|
} else {
|
||||||
|
startForeground(NOTIFICATION_ID, notification);
|
||||||
|
}
|
||||||
|
// Notify that the service is started (and it's now safe to set up media projection).
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_EVENT_STARTED));
|
||||||
|
}
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class DemoDebugViewProvider implements DebugViewProvider {
|
private final class DemoDebugViewProvider implements DebugViewProvider {
|
||||||
|
|
||||||
@Nullable private SurfaceView surfaceView;
|
@Nullable private SurfaceView surfaceView;
|
||||||
|
@ -165,6 +165,12 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:text="@string/resume"/>
|
android:text="@string/resume"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/stop_capture_button"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:text="@string/stop_capture"/>
|
||||||
|
|
||||||
<androidx.media3.ui.AspectRatioFrameLayout
|
<androidx.media3.ui.AspectRatioFrameLayout
|
||||||
android:id="@+id/debug_aspect_ratio_frame_layout"
|
android:id="@+id/debug_aspect_ratio_frame_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
<item>HDR (HDR10+) H265 limited range video (encoding may fail)</item>
|
<item>HDR (HDR10+) H265 limited range video (encoding may fail)</item>
|
||||||
<item>HDR (HLG) H265 limited range video (encoding may fail)</item>
|
<item>HDR (HLG) H265 limited range video (encoding may fail)</item>
|
||||||
<item>720p H264 video with no audio (B-frames)</item>
|
<item>720p H264 video with no audio (B-frames)</item>
|
||||||
|
<item>Record screen</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="preset_uris">
|
<string-array name="preset_uris">
|
||||||
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4</item>
|
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4</item>
|
||||||
@ -77,5 +78,6 @@
|
|||||||
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4</item>
|
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4</item>
|
||||||
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4</item>
|
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4</item>
|
||||||
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/sample_video_track_only.mp4</item>
|
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/sample_video_track_only.mp4</item>
|
||||||
|
<item>transformer_surface_asset:media_projection</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
<string name="debug_preview" translatable="false">Debug preview:</string>
|
<string name="debug_preview" translatable="false">Debug preview:</string>
|
||||||
<string name="pause" translatable="false">Pause</string>
|
<string name="pause" translatable="false">Pause</string>
|
||||||
<string name="resume" translatable="false">Resume</string>
|
<string name="resume" translatable="false">Resume</string>
|
||||||
|
<string name="stop_capture" translatable="false">Stop capture</string>
|
||||||
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
|
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
|
||||||
<string name="export_started" translatable="false">Export started</string>
|
<string name="export_started" translatable="false">Export started</string>
|
||||||
<string name="export_timer" translatable="false">Export started %d seconds ago.</string>
|
<string name="export_timer" translatable="false">Export started %d seconds ago.</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user