Merge "Skip next row id backup for devices with adoptable storage" into tm-dev
diff --git a/apex/framework/java/android/provider/CloudMediaProvider.java b/apex/framework/java/android/provider/CloudMediaProvider.java
index 33e913f..50e18e7 100644
--- a/apex/framework/java/android/provider/CloudMediaProvider.java
+++ b/apex/framework/java/android/provider/CloudMediaProvider.java
@@ -54,6 +54,7 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteCallback;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
@@ -446,15 +447,24 @@
public final AssetFileDescriptor openTypedAssetFile(
@NonNull Uri uri, @NonNull String mimeTypeFilter, @Nullable Bundle opts,
@Nullable CancellationSignal signal) throws FileNotFoundException {
- String mediaId = uri.getLastPathSegment();
- final boolean wantsThumb = (opts != null) && opts.containsKey(ContentResolver.EXTRA_SIZE)
- && mimeTypeFilter.startsWith("image/");
- if (wantsThumb) {
- Point point = (Point) opts.getParcelable(ContentResolver.EXTRA_SIZE);
- return onOpenPreview(mediaId, point, opts, signal);
+ final String mediaId = uri.getLastPathSegment();
+ final boolean previewThumbnail = (opts != null)
+ && opts.containsKey(CloudMediaProviderContract.EXTRA_PREVIEW_THUMBNAIL);
+ final DisplayMetrics screenMetrics = getContext().getResources().getDisplayMetrics();
+
+ final Bundle bundle = new Bundle();
+ int minPreviewLength = Math.min(screenMetrics.widthPixels, screenMetrics.heightPixels);
+ if (previewThumbnail) {
+ bundle.putBoolean(CloudMediaProviderContract.EXTRA_PREVIEW_THUMBNAIL, true);
+ minPreviewLength = minPreviewLength / 2;
}
- return new AssetFileDescriptor(onOpenMedia(mediaId, opts, signal), 0 /* startOffset */,
- AssetFileDescriptor.UNKNOWN_LENGTH);
+
+ Point previewSize = (opts != null) ? (Point) opts.getParcelable(ContentResolver.EXTRA_SIZE)
+ : null;
+ if (previewSize == null) {
+ previewSize = new Point(minPreviewLength, minPreviewLength);
+ }
+ return onOpenPreview(mediaId, previewSize, bundle, signal);
}
/**
diff --git a/src/com/android/providers/media/metrics/MPUiEventLoggerImpl.java b/src/com/android/providers/media/metrics/MPUiEventLoggerImpl.java
index 6969d47..fadb70c 100644
--- a/src/com/android/providers/media/metrics/MPUiEventLoggerImpl.java
+++ b/src/com/android/providers/media/metrics/MPUiEventLoggerImpl.java
@@ -71,7 +71,8 @@
/* event_id = 1 */ eventID,
/* package_name = 2 */ packageName,
/* instance_id = 3 */ 0,
- /* position_picked = 4 */ position);
+ /* position_picked = 4 */ position,
+ /* is_pinned = 5 */ false);
}
}
@@ -84,7 +85,8 @@
/* event_id = 1 */ eventID,
/* package_name = 2 */ packageName,
/* instance_id = 3 */ instance.getId(),
- /* position_picked = 4 */ position);
+ /* position_picked = 4 */ position,
+ /* is_pinned = 5 */ false);
} else {
logWithPosition(event, uid, packageName, position);
}
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerProvider.java b/src/com/android/providers/media/photopicker/PhotoPickerProvider.java
index f2954df..3d93189 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerProvider.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerProvider.java
@@ -16,34 +16,21 @@
package com.android.providers.media.photopicker;
-import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_BUFFERING;
-import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_COMPLETED;
-import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_MEDIA_SIZE_CHANGED;
-import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_PAUSED;
-import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_READY;
-import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_STARTED;
import static android.provider.CloudMediaProviderContract.EXTRA_LOOPING_PLAYBACK_ENABLED;
import static android.provider.CloudMediaProviderContract.EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED;
-import android.annotation.DurationMillisLong;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Point;
-import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.Looper;
import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
import android.provider.CloudMediaProvider;
import android.provider.MediaStore;
-import android.util.Log;
-import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -53,24 +40,8 @@
import com.android.providers.media.photopicker.data.CloudProviderQueryExtras;
import com.android.providers.media.photopicker.data.ExternalDbFacade;
import com.android.providers.media.photopicker.ui.remotepreview.RemotePreviewHandler;
+import com.android.providers.media.photopicker.ui.remotepreview.RemoteSurfaceController;
-import com.google.android.exoplayer2.DefaultLoadControl;
-import com.google.android.exoplayer2.DefaultRenderersFactory;
-import com.google.android.exoplayer2.ExoPlayer;
-import com.google.android.exoplayer2.LoadControl;
-import com.google.android.exoplayer2.MediaItem;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Player.State;
-import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector;
-import com.google.android.exoplayer2.source.MediaParserExtractorAdapter;
-import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.upstream.ContentDataSource;
-import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
-import com.google.android.exoplayer2.util.Clock;
-import com.google.android.exoplayer2.video.VideoSize;
-
-import java.io.File;
import java.io.FileNotFoundException;
/**
@@ -165,8 +136,7 @@
boolean enableLoop = config.getBoolean(EXTRA_LOOPING_PLAYBACK_ENABLED, false);
boolean muteAudio = config.getBoolean(EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED,
false);
- return new CloudMediaSurfaceControllerImpl(getContext(), enableLoop, muteAudio,
- callback);
+ return new RemoteSurfaceController(getContext(), enableLoop, muteAudio, callback);
}
return null;
}
@@ -184,247 +154,4 @@
return MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL,
Long.parseLong(mediaId));
}
-
- private static final class CloudMediaSurfaceControllerImpl extends CloudMediaSurfaceController {
-
- // The minimum duration of media that the player will attempt to ensure is buffered at all
- // times.
- private static final int MIN_BUFFER_MS = 1000;
- // The maximum duration of media that the player will attempt to buffer.
- private static final int MAX_BUFFER_MS = 2000;
- // The duration of media that must be buffered for playback to start or resume following a
- // user action such as a seek.
- private static final int BUFFER_FOR_PLAYBACK_MS = 1000;
- // The default duration of media that must be buffered for playback to resume after a
- // rebuffer.
- private static final int BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 1000;
- private static final LoadControl sLoadControl = new DefaultLoadControl.Builder()
- .setBufferDurationsMs(
- MIN_BUFFER_MS,
- MAX_BUFFER_MS,
- BUFFER_FOR_PLAYBACK_MS,
- BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS).build();
-
- private final Context mContext;
- private final CloudMediaSurfaceStateChangedCallback mCallback;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Player.Listener mEventListener = new Player.Listener() {
- @Override
- public void onPlaybackStateChanged(@State int state) {
- Log.d(TAG, "Received player event " + state);
-
- switch (state) {
- case Player.STATE_READY:
- mCallback.setPlaybackState(mCurrentSurfaceId, PLAYBACK_STATE_READY,
- null);
- return;
- case Player.STATE_BUFFERING:
- mCallback.setPlaybackState(mCurrentSurfaceId, PLAYBACK_STATE_BUFFERING,
- null);
- return;
- case Player.STATE_ENDED:
- mCallback.setPlaybackState(mCurrentSurfaceId, PLAYBACK_STATE_COMPLETED,
- null);
- return;
- default:
- }
- }
-
- @Override
- public void onIsPlayingChanged(boolean isPlaying) {
- mCallback.setPlaybackState(mCurrentSurfaceId, isPlaying ? PLAYBACK_STATE_STARTED :
- PLAYBACK_STATE_PAUSED, null);
- }
-
- @Override
- public void onVideoSizeChanged(VideoSize videoSize) {
- Point size = new Point(videoSize.width, videoSize.height);
- Bundle bundle = new Bundle();
- bundle.putParcelable(ContentResolver.EXTRA_SIZE, size);
- mCallback.setPlaybackState(mCurrentSurfaceId, PLAYBACK_STATE_MEDIA_SIZE_CHANGED,
- bundle);
- }
- };
-
- private boolean mEnableLoop;
- private boolean mMuteAudio;
- private ExoPlayer mPlayer;
- private int mCurrentSurfaceId = -1;
-
- CloudMediaSurfaceControllerImpl(Context context, boolean enableLoop, boolean muteAudio,
- CloudMediaSurfaceStateChangedCallback callback) {
- mCallback = callback;
- mContext = context;
- mEnableLoop = enableLoop;
- mMuteAudio = muteAudio;
- Log.d(TAG, "Surface controller created.");
- }
-
- @Override
- public void onPlayerCreate() {
- mHandler.post(() -> {
- mPlayer = createExoPlayer();
- mPlayer.addListener(mEventListener);
- updateLoopingPlaybackStatus();
- updateAudioMuteStatus();
- Log.d(TAG, "Player created.");
- });
- }
-
- @Override
- public void onPlayerRelease() {
- mHandler.post(() -> {
- mPlayer.removeListener(mEventListener);
- mPlayer.release();
- mPlayer = null;
- Log.d(TAG, "Player released.");
- });
- }
-
- @Override
- public void onSurfaceCreated(int surfaceId, @NonNull Surface surface,
- @NonNull String mediaId) {
- mHandler.post(() -> {
- try {
- // onSurfaceCreated may get called while the player is already rendering on a
- // different surface. In that case, pause the player before preparing it for
- // rendering on the new surface.
- // Unfortunately, Exoplayer#stop doesn't seem to work here. If we call stop(),
- // as soon as the player becomes ready again, it automatically starts to play
- // the new media. The reason is that Exoplayer treats play/pause as calls to
- // the method Exoplayer#setPlayWhenReady(boolean) with true and false
- // respectively. So, if we don't pause(), then since the previous play() call
- // had set setPlayWhenReady to true, the player would start the playback as soon
- // as it gets ready with the new media item.
- if (mPlayer.isPlaying()) {
- mPlayer.pause();
- }
-
- mCurrentSurfaceId = surfaceId;
-
- final Uri mediaUri =
- Uri.parse(
- MediaStore.Files.getContentUri(
- MediaStore.VOLUME_EXTERNAL)
- + File.separator + mediaId);
- mPlayer.setMediaItem(MediaItem.fromUri(mediaUri));
- mPlayer.setVideoSurface(surface);
- mPlayer.prepare();
-
- Log.d(TAG, "Surface prepared: " + surfaceId + ". Surface: " + surface
- + ". MediaId: " + mediaId);
- } catch (RuntimeException e) {
- Log.e(TAG, "Error preparing player with surface.", e);
- }
- });
- }
-
- @Override
- public void onSurfaceChanged(int surfaceId, int format, int width, int height) {
- Log.d(TAG, "Surface changed: " + surfaceId + ". Format: " + format + ". Width: "
- + width + ". Height: " + height);
- }
-
- @Override
- public void onSurfaceDestroyed(int surfaceId) {
- mHandler.post(() -> {
- if (mCurrentSurfaceId != surfaceId) {
- // This means that the player is already using some other surface, hence
- // nothing to do.
- return;
- }
- if (mPlayer.isPlaying()) {
- mPlayer.stop();
- }
- mPlayer.clearVideoSurface();
- mCurrentSurfaceId = -1;
-
- Log.d(TAG, "Surface released: " + surfaceId);
- });
- }
-
- @Override
- public void onMediaPlay(int surfaceId) {
- mHandler.post(() -> {
- mPlayer.play();
- Log.d(TAG, "Media played: " + surfaceId);
- });
- }
-
- @Override
- public void onMediaPause(int surfaceId) {
- mHandler.post(() -> {
- if (mPlayer.isPlaying()) {
- mPlayer.pause();
- Log.d(TAG, "Media paused: " + surfaceId);
- }
- });
- }
-
- @Override
- public void onMediaSeekTo(int surfaceId, @DurationMillisLong long timestampMillis) {
- mHandler.post(() -> {
- mPlayer.seekTo((int) timestampMillis);
- Log.d(TAG, "Media seeked: " + surfaceId + ". Timestamp: " + timestampMillis);
- });
- }
-
- @Override
- public void onConfigChange(@NonNull Bundle config) {
- final boolean enableLoop = config.getBoolean(EXTRA_LOOPING_PLAYBACK_ENABLED,
- mEnableLoop);
- final boolean muteAudio = config.getBoolean(EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED,
- mMuteAudio);
- mHandler.post(() -> {
- if (mEnableLoop != enableLoop) {
- mEnableLoop = enableLoop;
- updateLoopingPlaybackStatus();
- }
-
- if (mMuteAudio != muteAudio) {
- mMuteAudio = muteAudio;
- updateAudioMuteStatus();
- }
- });
- Log.d(TAG, "Config changed. Updated config params: " + config);
- }
-
- @Override
- public void onDestroy() {
- Log.d(TAG, "Surface controller destroyed.");
- }
-
- private ExoPlayer createExoPlayer() {
- // ProgressiveMediaFactory will be enough for video playback of videos on the device.
- // This also reduces apk size.
- ProgressiveMediaSource.Factory mediaSourceFactory = new ProgressiveMediaSource.Factory(
- () -> new ContentDataSource(mContext), MediaParserExtractorAdapter.FACTORY);
-
- return new ExoPlayer.Builder(mContext,
- new DefaultRenderersFactory(mContext),
- mediaSourceFactory,
- new DefaultTrackSelector(mContext),
- sLoadControl,
- DefaultBandwidthMeter.getSingletonInstance(mContext),
- new DefaultAnalyticsCollector(Clock.DEFAULT)).build();
- }
-
- private void updateLoopingPlaybackStatus() {
- mPlayer.setRepeatMode(mEnableLoop ? Player.REPEAT_MODE_ONE : Player.REPEAT_MODE_OFF);
- }
-
- private void updateAudioMuteStatus() {
- if (mMuteAudio) {
- mPlayer.setVolume(0f);
- } else {
- AudioManager audioManager = mContext.getSystemService(AudioManager.class);
- if (audioManager == null) {
- Log.e(TAG, "Couldn't find AudioManager while trying to set volume,"
- + " unable to set volume");
- return;
- }
- mPlayer.setVolume(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
- }
- }
- }
}
diff --git a/src/com/android/providers/media/photopicker/ui/remotepreview/RemoteSurfaceController.java b/src/com/android/providers/media/photopicker/ui/remotepreview/RemoteSurfaceController.java
new file mode 100644
index 0000000..bc1f5fb
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/ui/remotepreview/RemoteSurfaceController.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 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.android.providers.media.photopicker.ui.remotepreview;
+
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceController;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_BUFFERING;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_COMPLETED;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_MEDIA_SIZE_CHANGED;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_PAUSED;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_READY;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_STARTED;
+import static android.provider.CloudMediaProviderContract.EXTRA_LOOPING_PLAYBACK_ENABLED;
+import static android.provider.CloudMediaProviderContract.EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED;
+
+import android.annotation.DurationMillisLong;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.Point;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.exoplayer2.DefaultLoadControl;
+import com.google.android.exoplayer2.DefaultRenderersFactory;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.LoadControl;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Player.State;
+import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector;
+import com.google.android.exoplayer2.source.MediaParserExtractorAdapter;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
+import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
+import com.google.android.exoplayer2.upstream.ContentDataSource;
+import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
+import com.google.android.exoplayer2.util.Clock;
+import com.google.android.exoplayer2.video.VideoSize;
+
+import java.io.File;
+
+/**
+ * Implements a {@link CloudMediaSurfaceController} for a cloud provider authority and initializes
+ * an ExoPlayer instance to render cloud media to {@link Surface} instances.
+ */
+public class RemoteSurfaceController extends CloudMediaSurfaceController {
+ private static final String TAG = "RemoteSurfaceController";
+
+ // The minimum duration of media that the player will attempt to ensure is buffered at all
+ // times.
+ private static final int MIN_BUFFER_MS = 1000;
+ // The maximum duration of media that the player will attempt to buffer.
+ private static final int MAX_BUFFER_MS = 2000;
+ // The duration of media that must be buffered for playback to start or resume following a
+ // user action such as a seek.
+ private static final int BUFFER_FOR_PLAYBACK_MS = 1000;
+ // The default duration of media that must be buffered for playback to resume after a
+ // rebuffer.
+ private static final int BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 1000;
+ private static final LoadControl sLoadControl = new DefaultLoadControl.Builder()
+ .setBufferDurationsMs(
+ MIN_BUFFER_MS,
+ MAX_BUFFER_MS,
+ BUFFER_FOR_PLAYBACK_MS,
+ BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS).build();
+
+ private final Context mContext;
+ private final CloudMediaSurfaceStateChangedCallback mCallback;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Player.Listener mEventListener = new Player.Listener() {
+ @Override
+ public void onPlaybackStateChanged(@State int state) {
+ Log.d(TAG, "Received player event " + state);
+
+ switch (state) {
+ case Player.STATE_READY:
+ mCallback.setPlaybackState(mCurrentSurfaceId, PLAYBACK_STATE_READY,
+ null);
+ return;
+ case Player.STATE_BUFFERING:
+ mCallback.setPlaybackState(mCurrentSurfaceId, PLAYBACK_STATE_BUFFERING,
+ null);
+ return;
+ case Player.STATE_ENDED:
+ mCallback.setPlaybackState(mCurrentSurfaceId, PLAYBACK_STATE_COMPLETED,
+ null);
+ return;
+ default:
+ }
+ }
+
+ @Override
+ public void onIsPlayingChanged(boolean isPlaying) {
+ mCallback.setPlaybackState(mCurrentSurfaceId, isPlaying ? PLAYBACK_STATE_STARTED :
+ PLAYBACK_STATE_PAUSED, null);
+ }
+
+ @Override
+ public void onVideoSizeChanged(VideoSize videoSize) {
+ Point size = new Point(videoSize.width, videoSize.height);
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(ContentResolver.EXTRA_SIZE, size);
+ mCallback.setPlaybackState(mCurrentSurfaceId, PLAYBACK_STATE_MEDIA_SIZE_CHANGED,
+ bundle);
+ }
+ };
+
+ private boolean mEnableLoop;
+ private boolean mMuteAudio;
+ private ExoPlayer mPlayer;
+ private int mCurrentSurfaceId = -1;
+
+ public RemoteSurfaceController(Context context, boolean enableLoop, boolean muteAudio,
+ CloudMediaSurfaceStateChangedCallback callback) {
+ mCallback = callback;
+ mContext = context;
+ mEnableLoop = enableLoop;
+ mMuteAudio = muteAudio;
+ Log.d(TAG, "Surface controller created.");
+ }
+
+ @Override
+ public void onPlayerCreate() {
+ mHandler.post(() -> {
+ mPlayer = createExoPlayer();
+ mPlayer.addListener(mEventListener);
+ updateLoopingPlaybackStatus();
+ updateAudioMuteStatus();
+ Log.d(TAG, "Player created.");
+ });
+ }
+
+ @Override
+ public void onPlayerRelease() {
+ mHandler.post(() -> {
+ mPlayer.removeListener(mEventListener);
+ mPlayer.release();
+ mPlayer = null;
+ Log.d(TAG, "Player released.");
+ });
+ }
+
+ @Override
+ public void onSurfaceCreated(int surfaceId, @NonNull Surface surface,
+ @NonNull String mediaId) {
+ mHandler.post(() -> {
+ try {
+ // onSurfaceCreated may get called while the player is already rendering on a
+ // different surface. In that case, pause the player before preparing it for
+ // rendering on the new surface.
+ // Unfortunately, Exoplayer#stop doesn't seem to work here. If we call stop(),
+ // as soon as the player becomes ready again, it automatically starts to play
+ // the new media. The reason is that Exoplayer treats play/pause as calls to
+ // the method Exoplayer#setPlayWhenReady(boolean) with true and false
+ // respectively. So, if we don't pause(), then since the previous play() call
+ // had set setPlayWhenReady to true, the player would start the playback as soon
+ // as it gets ready with the new media item.
+ if (mPlayer.isPlaying()) {
+ mPlayer.pause();
+ }
+
+ mCurrentSurfaceId = surfaceId;
+
+ final Uri mediaUri =
+ Uri.parse(
+ MediaStore.Files.getContentUri(
+ MediaStore.VOLUME_EXTERNAL)
+ + File.separator + mediaId);
+ mPlayer.setMediaItem(MediaItem.fromUri(mediaUri));
+ mPlayer.setVideoSurface(surface);
+ mPlayer.prepare();
+
+ Log.d(TAG, "Surface prepared: " + surfaceId + ". Surface: " + surface
+ + ". MediaId: " + mediaId);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error preparing player with surface.", e);
+ }
+ });
+ }
+
+ @Override
+ public void onSurfaceChanged(int surfaceId, int format, int width, int height) {
+ Log.d(TAG, "Surface changed: " + surfaceId + ". Format: " + format + ". Width: "
+ + width + ". Height: " + height);
+ }
+
+ @Override
+ public void onSurfaceDestroyed(int surfaceId) {
+ mHandler.post(() -> {
+ if (mCurrentSurfaceId != surfaceId) {
+ // This means that the player is already using some other surface, hence
+ // nothing to do.
+ return;
+ }
+ if (mPlayer.isPlaying()) {
+ mPlayer.stop();
+ }
+ mPlayer.clearVideoSurface();
+ mCurrentSurfaceId = -1;
+
+ Log.d(TAG, "Surface released: " + surfaceId);
+ });
+ }
+
+ @Override
+ public void onMediaPlay(int surfaceId) {
+ mHandler.post(() -> {
+ mPlayer.play();
+ Log.d(TAG, "Media played: " + surfaceId);
+ });
+ }
+
+ @Override
+ public void onMediaPause(int surfaceId) {
+ mHandler.post(() -> {
+ if (mPlayer.isPlaying()) {
+ mPlayer.pause();
+ Log.d(TAG, "Media paused: " + surfaceId);
+ }
+ });
+ }
+
+ @Override
+ public void onMediaSeekTo(int surfaceId, @DurationMillisLong long timestampMillis) {
+ mHandler.post(() -> {
+ mPlayer.seekTo((int) timestampMillis);
+ Log.d(TAG, "Media seeked: " + surfaceId + ". Timestamp: " + timestampMillis);
+ });
+ }
+
+ @Override
+ public void onConfigChange(@NonNull Bundle config) {
+ final boolean enableLoop = config.getBoolean(EXTRA_LOOPING_PLAYBACK_ENABLED,
+ mEnableLoop);
+ final boolean muteAudio = config.getBoolean(EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED,
+ mMuteAudio);
+ mHandler.post(() -> {
+ if (mEnableLoop != enableLoop) {
+ mEnableLoop = enableLoop;
+ updateLoopingPlaybackStatus();
+ }
+
+ if (mMuteAudio != muteAudio) {
+ mMuteAudio = muteAudio;
+ updateAudioMuteStatus();
+ }
+ });
+ Log.d(TAG, "Config changed. Updated config params: " + config);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "Surface controller destroyed.");
+ }
+
+ private ExoPlayer createExoPlayer() {
+ // ProgressiveMediaFactory will be enough for video playback of videos on the device.
+ // This also reduces apk size.
+ ProgressiveMediaSource.Factory mediaSourceFactory = new ProgressiveMediaSource.Factory(
+ () -> new ContentDataSource(mContext), MediaParserExtractorAdapter.FACTORY);
+
+ return new ExoPlayer.Builder(mContext,
+ new DefaultRenderersFactory(mContext),
+ mediaSourceFactory,
+ new DefaultTrackSelector(mContext),
+ sLoadControl,
+ DefaultBandwidthMeter.getSingletonInstance(mContext),
+ new DefaultAnalyticsCollector(Clock.DEFAULT)).build();
+ }
+
+ private void updateLoopingPlaybackStatus() {
+ mPlayer.setRepeatMode(mEnableLoop ? Player.REPEAT_MODE_ONE : Player.REPEAT_MODE_OFF);
+ }
+
+ private void updateAudioMuteStatus() {
+ if (mMuteAudio) {
+ mPlayer.setVolume(0f);
+ } else {
+ AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+ if (audioManager == null) {
+ Log.e(TAG, "Couldn't find AudioManager while trying to set volume,"
+ + " unable to set volume");
+ return;
+ }
+ mPlayer.setVolume(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+ }
+ }
+}