Quellcode durchsuchen

Merge pull request #8132 from nextcloud/exoPlayer

Replace video player by ExoPlayer
Tobias Kaminsky vor 3 Jahren
Ursprung
Commit
18594f99cb

+ 2 - 0
build.gradle

@@ -325,6 +325,8 @@ dependencies {
     ktlint "com.pinterest:ktlint:0.41.0"
     implementation 'org.conscrypt:conscrypt-android:2.5.2'
 
+    implementation 'com.google.android.exoplayer:exoplayer:2.13.2'
+
     // Shimmer animation
     implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0'
 

+ 1 - 1
scripts/analysis/lint-results.txt

@@ -1,2 +1,2 @@
 DO NOT TOUCH; GENERATED BY DRONE
-      <span class="mdl-layout-title">Lint Report: 228 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 227 warnings</span>

+ 1 - 1
src/main/java/com/nextcloud/client/media/Player.kt

@@ -61,7 +61,7 @@ internal class Player(
     private var enqueuedFile: PlaylistItem? = null
 
     private var playedFile: OCFile? = null
-    private var startPositionMs: Int = 0
+    private var startPositionMs: Long = 0
     private var autoPlay = true
     private var user: User? = null
     private var dataSource: String? = null

+ 1 - 1
src/main/java/com/nextcloud/client/media/PlayerService.kt

@@ -139,7 +139,7 @@ class PlayerService : Service() {
     private fun onActionPlay(intent: Intent) {
         val user: User = intent.getParcelableExtra(EXTRA_USER) as User
         val file: OCFile = intent.getParcelableExtra(EXTRA_FILE) as OCFile
-        val startPos = intent.getIntExtra(EXTRA_START_POSITION_MS, 0)
+        val startPos = intent.getLongExtra(EXTRA_START_POSITION_MS, 0)
         val autoPlay = intent.getBooleanExtra(EXTRA_AUTO_PLAY, true)
         val item = PlaylistItem(file = file, startPositionMs = startPos, autoPlay = autoPlay, user = user)
         player.play(item)

+ 1 - 1
src/main/java/com/nextcloud/client/media/PlayerServiceConnection.kt

@@ -50,7 +50,7 @@ class PlayerServiceConnection(private val context: Context) : MediaController.Me
         }
     }
 
-    fun start(user: User, file: OCFile, playImmediately: Boolean, position: Int) {
+    fun start(user: User, file: OCFile, playImmediately: Boolean, position: Long) {
         val i = Intent(context, PlayerService::class.java)
         i.putExtra(PlayerService.EXTRA_USER, user)
         i.putExtra(PlayerService.EXTRA_FILE, file)

+ 1 - 1
src/main/java/com/nextcloud/client/media/PlaylistItem.kt

@@ -3,4 +3,4 @@ package com.nextcloud.client.media
 import com.nextcloud.client.account.User
 import com.owncloud.android.datamodel.OCFile
 
-data class PlaylistItem(val file: OCFile, val startPositionMs: Int, val autoPlay: Boolean, val user: User)
+data class PlaylistItem(val file: OCFile, val startPositionMs: Long, val autoPlay: Boolean, val user: User)

+ 11 - 10
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -129,7 +129,6 @@ import org.greenrobot.eventbus.ThreadMode;
 import org.parceler.Parcels;
 
 import java.io.File;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -560,7 +559,7 @@ public class FileDisplayActivity extends FileActivity
         Fragment secondFragment = null;
         if (file != null && !file.isFolder()) {
             if (file.isDown() && PreviewMediaFragment.canBePreviewed(file)) {
-                int startPlaybackPosition = getIntent().getIntExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0);
+                long startPlaybackPosition = getIntent().getLongExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0);
                 boolean autoplay = getIntent().getBooleanExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, true);
                 secondFragment = PreviewMediaFragment.newInstance(file, user, startPlaybackPosition, autoplay);
             } else if (file.isDown() && PreviewTextFileFragment.canBePreviewed(file)) {
@@ -846,7 +845,8 @@ public class FileDisplayActivity extends FileActivity
                         second != null && second.getFile() != null ||
                         isSearchOpen()) {
                     onBackPressed();
-                } else if (getLeftFragment() instanceof FileDetailFragment) {
+                } else if (getLeftFragment() instanceof FileDetailFragment ||
+                    getLeftFragment() instanceof PreviewMediaFragment) {
                     onBackPressed();
                 } else {
                     openDrawer();
@@ -1899,7 +1899,7 @@ public class FileDisplayActivity extends FileActivity
                         renamedFile.equals(details.getFile())) {
                     ((PreviewMediaFragment) details).updateFile(renamedFile);
                     if (PreviewMediaFragment.canBePreviewed(renamedFile)) {
-                        int position = ((PreviewMediaFragment) details).getPosition();
+                        long position = ((PreviewMediaFragment) details).getPosition();
                         startMediaPreview(renamedFile, position, true, true, true);
                     } else {
                         getFileOperationsHelper().openFile(renamedFile);
@@ -2183,12 +2183,13 @@ public class FileDisplayActivity extends FileActivity
      * Stars the preview of an already down media {@link OCFile}.
      *
      * @param file                  Media {@link OCFile} to preview.
-     * @param startPlaybackPosition Media position where the playback will be started,
-     *                              in milliseconds.
-     * @param autoplay              When 'true', the playback will start without user
-     *                              interactions.
+     * @param startPlaybackPosition Media position where the playback will be started, in milliseconds.
+     * @param autoplay              When 'true', the playback will start without user interactions.
      */
-    public void startMediaPreview(OCFile file, int startPlaybackPosition, boolean autoplay, boolean showPreview,
+    public void startMediaPreview(OCFile file,
+                                  long startPlaybackPosition,
+                                  boolean autoplay,
+                                  boolean showPreview,
                                   boolean streamMedia) {
         Optional<User> user = getUser();
         if (!user.isPresent()) {
@@ -2363,7 +2364,7 @@ public class FileDisplayActivity extends FileActivity
             startTextPreview((OCFile) bundle.get(EXTRA_FILE), true);
         } else if (bundle.containsKey(PreviewVideoActivity.EXTRA_START_POSITION)) {
             startMediaPreview((OCFile) bundle.get(EXTRA_FILE),
-                              (int) bundle.get(PreviewVideoActivity.EXTRA_START_POSITION),
+                              (long) bundle.get(PreviewVideoActivity.EXTRA_START_POSITION),
                               (boolean) bundle.get(PreviewVideoActivity.EXTRA_AUTOPLAY), true, true);
         } else if (bundle.containsKey(PreviewImageActivity.EXTRA_VIRTUAL_TYPE)) {
             startImagePreview((OCFile)bundle.get(EXTRA_FILE),

+ 124 - 139
src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -31,10 +31,6 @@ import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.media.MediaMetadataRetriever;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.MediaPlayer.OnPreparedListener;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -48,15 +44,18 @@ import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.ui.StyledPlayerControlView;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.di.Injectable;
-import com.nextcloud.client.media.ErrorFormat;
 import com.nextcloud.client.media.PlayerServiceConnection;
 import com.nextcloud.client.network.ClientFactory;
 import com.owncloud.android.R;
 import com.owncloud.android.databinding.FragmentPreviewMediaBinding;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.files.FileMenuFilter;
 import com.owncloud.android.files.StreamMediaFileOperation;
 import com.owncloud.android.lib.common.OwnCloudClient;
@@ -79,14 +78,15 @@ import androidx.drawerlayout.widget.DrawerLayout;
 
 /**
  * This fragment shows a preview of a downloaded media file (audio or video).
- *
- * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will
- * produce an {@link IllegalStateException}.
- *
- * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is
- * generated on instantiation too.
+ * <p>
+ * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link
+ * IllegalStateException}.
+ * <p>
+ * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on
+ * instantiation too.
  */
-public class PreviewMediaFragment extends FileFragment implements OnTouchListener, Injectable {
+public class PreviewMediaFragment extends FileFragment implements OnTouchListener,
+    Injectable, StyledPlayerControlView.OnFullScreenModeChangedListener {
 
     private static final String TAG = PreviewMediaFragment.class.getSimpleName();
 
@@ -95,6 +95,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     private static final String EXTRA_PLAY_POSITION = "PLAY_POSITION";
     private static final String EXTRA_PLAYING = "PLAYING";
     private static final double MIN_DENSITY_RATIO = 24.0;
+    private static final int MENU_FULLSCREEN_ID = 3344;
 
     private static final String FILE = "FILE";
     private static final String USER = "USER";
@@ -102,7 +103,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     private static final String AUTOPLAY = "AUTOPLAY";
 
     private User user;
-    private int savedPlaybackPosition;
+    private long savedPlaybackPosition;
 
     private boolean autoplay;
     private boolean prepared;
@@ -113,6 +114,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     @Inject UserAccountManager accountManager;
     FragmentPreviewMediaBinding binding;
     LinearLayout emptyListView;
+    private SimpleExoPlayer exoPlayer;
 
     /**
      * Creates a fragment to preview a file.
@@ -122,14 +124,16 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
      * @param fileToDetail An {@link OCFile} to preview in the fragment
      * @param user         Currently active user
      */
-    public static PreviewMediaFragment newInstance(OCFile fileToDetail, User user, int startPlaybackPosition,
+    public static PreviewMediaFragment newInstance(OCFile fileToDetail,
+                                                   User user,
+                                                   long startPlaybackPosition,
                                                    boolean autoplay) {
         PreviewMediaFragment previewMediaFragment = new PreviewMediaFragment();
 
         Bundle bundle = new Bundle();
         bundle.putParcelable(FILE, fileToDetail);
         bundle.putParcelable(USER, user);
-        bundle.putInt(PLAYBACK_POSITION, startPlaybackPosition);
+        bundle.putLong(PLAYBACK_POSITION, startPlaybackPosition);
         bundle.putBoolean(AUTOPLAY, autoplay);
 
         previewMediaFragment.setArguments(bundle);
@@ -140,11 +144,10 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     /**
      * Creates an empty fragment for previews.
      * <p/>
-     * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically
-     * (for instance, when the device is turned a aside).
+     * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the
+     * device is turned a aside).
      * <p/>
-     * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful
-     * construction
+     * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction
      */
     public PreviewMediaFragment() {
         super();
@@ -161,7 +164,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
 
         setFile(bundle.getParcelable(FILE));
         user = bundle.getParcelable(USER);
-        savedPlaybackPosition = bundle.getInt(PLAYBACK_POSITION);
+        savedPlaybackPosition = bundle.getLong(PLAYBACK_POSITION);
         autoplay = bundle.getBoolean(AUTOPLAY);
         mediaPlayerServiceConnection = new PlayerServiceConnection(getContext());
     }
@@ -176,8 +179,6 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
 
         emptyListView = binding.emptyView.emptyListView;
 
-        binding.videoPreview.setOnTouchListener(this);
-
         setLoadingView();
         return view;
     }
@@ -220,11 +221,10 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
 
         if (file != null) {
             if (MimeTypeUtil.isVideo(file)) {
-                binding.videoPreview.setVisibility(View.VISIBLE);
+                binding.exoplayerView.setVisibility(View.VISIBLE);
                 binding.imagePreview.setVisibility(View.GONE);
-                prepareVideo();
             } else {
-                binding.videoPreview.setVisibility(View.GONE);
+                binding.exoplayerView.setVisibility(View.GONE);
                 binding.imagePreview.setVisibility(View.VISIBLE);
                 extractAndSetCoverArt(file);
             }
@@ -239,22 +239,37 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
      */
     private void extractAndSetCoverArt(OCFile file) {
         if (MimeTypeUtil.isAudio(file)) {
-            try {
-                MediaMetadataRetriever mmr = new MediaMetadataRetriever();
-                mmr.setDataSource(file.getStoragePath());
-                byte[] data = mmr.getEmbeddedPicture();
-                if (data != null) {
-                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
-                    binding.imagePreview.setImageBitmap(bitmap); //associated cover art in bitmap
-                } else {
+            if (file.getStoragePath() == null) {
+                setThumbnailForAudio(file);
+            } else {
+                try {
+                    MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+                    mmr.setDataSource(file.getStoragePath());
+                    byte[] data = mmr.getEmbeddedPicture();
+                    if (data != null) {
+                        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                        binding.imagePreview.setImageBitmap(bitmap); //associated cover art in bitmap
+                    } else {
+                        setThumbnailForAudio(file);
+                    }
+                } catch (Throwable t) {
                     binding.imagePreview.setImageResource(R.drawable.logo);
                 }
-            } catch (Throwable t) {
-                binding.imagePreview.setImageResource(R.drawable.logo);
             }
         }
     }
 
+    private void setThumbnailForAudio(OCFile file) {
+        Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
+            ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId());
+
+        if (thumbnail != null) {
+            binding.imagePreview.setImageBitmap(thumbnail);
+        } else {
+            binding.imagePreview.setImageResource(R.drawable.logo);
+        }
+    }
+
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
         super.onSaveInstanceState(outState);
@@ -264,13 +279,11 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
         outState.putParcelable(EXTRA_USER, user);
 
         if (MimeTypeUtil.isVideo(getFile())) {
-            if (binding.videoPreview != null) {
-                savedPlaybackPosition = binding.videoPreview.getCurrentPosition();
-                autoplay = binding.videoPreview.isPlaying();
-                outState.putInt(EXTRA_PLAY_POSITION, savedPlaybackPosition);
-                outState.putBoolean(EXTRA_PLAYING, autoplay);
-            }
-        } else if(mediaPlayerServiceConnection.isConnected()) {
+            savedPlaybackPosition = exoPlayer.getCurrentPosition();
+            autoplay = exoPlayer.isPlaying();
+            outState.putLong(EXTRA_PLAY_POSITION, savedPlaybackPosition);
+            outState.putBoolean(EXTRA_PLAYING, autoplay);
+        } else if (mediaPlayerServiceConnection.isConnected()) {
             outState.putInt(EXTRA_PLAY_POSITION, mediaPlayerServiceConnection.getCurrentPosition());
             outState.putBoolean(EXTRA_PLAYING, mediaPlayerServiceConnection.isPlaying());
         }
@@ -285,12 +298,15 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
             // bind to any existing player
             mediaPlayerServiceConnection.bind();
 
+            exoPlayer = new SimpleExoPlayer.Builder(getContext()).build();
+            binding.exoplayerView.setPlayer(exoPlayer);
+
             if (MimeTypeUtil.isAudio(file)) {
                 binding.mediaController.setMediaPlayer(mediaPlayerServiceConnection);
+                binding.mediaController.setVisibility(View.VISIBLE);
                 mediaPlayerServiceConnection.start(user, file, autoplay, savedPlaybackPosition);
                 binding.emptyView.emptyListView.setVisibility(View.GONE);
                 binding.progress.setVisibility(View.GONE);
-                binding.filePreviewContainer.setVisibility(View.VISIBLE);
             } else if (MimeTypeUtil.isVideo(file)) {
                 if (mediaPlayerServiceConnection.isConnected()) {
                     // always stop player
@@ -309,6 +325,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
         super.onCreateOptionsMenu(menu, inflater);
         menu.removeItem(R.id.action_search);
+        menu.add(Menu.NONE, MENU_FULLSCREEN_ID, 99, R.string.fullscreen);
         inflater.inflate(R.menu.item_file, menu);
     }
 
@@ -372,10 +389,10 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
             item.setEnabled(false);
         }
 
-        if(getFile().isSharedWithMe() && !getFile().canReshare()){
+        if (getFile().isSharedWithMe() && !getFile().canReshare()) {
             // additional restriction for this fragment
             item = menu.findItem(R.id.action_send_share_file);
-            if(item != null){
+            if (item != null) {
                 item.setVisible(false);
                 item.setEnabled(false);
             }
@@ -401,6 +418,9 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
         } else if (itemId == R.id.action_sync_file) {
             containerActivity.getFileOperationsHelper().syncFile(getFile());
             return true;
+        } else if (itemId == MENU_FULLSCREEN_ID) {
+            startFullScreenVideo();
+            return true;
         }
         return super.onOptionsItemSelected(item);
     }
@@ -408,7 +428,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     /**
      * Update the file of the fragment with file value
      *
-     * @param file      Replaces the held file with a new one
+     * @param file Replaces the held file with a new one
      */
     public void updateFile(OCFile file) {
         setFile(file);
@@ -424,44 +444,55 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
         containerActivity.getFileOperationsHelper().sendShareFile(getFile());
     }
 
-    private void prepareVideo() {
-        // create helper to get more control on the playback
-        VideoHelper videoHelper = new VideoHelper();
-        binding.videoPreview.setOnPreparedListener(videoHelper);
-        binding.videoPreview.setOnCompletionListener(videoHelper);
-        binding.videoPreview.setOnErrorListener(videoHelper);
-    }
-
     private void playVideo() {
-        // create and prepare control panel for the user
-        binding.mediaController.setMediaPlayer(binding.videoPreview);
-
         // load the video file in the video player
         // when done, VideoHelper#onPrepared() will be called
         if (getFile().isDown()) {
-            binding.videoPreview.setVideoURI(getFile().getStorageUri());
+            binding.progress.setVisibility(View.GONE);
+
+            exoPlayer.addMediaItem(MediaItem.fromUri(getFile().getStorageUri()));
+            exoPlayer.prepare();
+
+            if (savedPlaybackPosition >= 0) {
+                exoPlayer.seekTo(savedPlaybackPosition);
+            }
+            exoPlayer.play();
         } else {
             try {
-                OwnCloudClient client = clientFactory.create(user);
-                new LoadStreamUrl(this, client).execute(getFile().getLocalId());
+                new LoadStreamUrl(this, user, clientFactory).execute(getFile().getLocalId());
             } catch (Exception e) {
                 Log_OC.e(TAG, "Loading stream url not possible: " + e);
             }
         }
     }
 
+    @Override
+    public void onFullScreenModeChanged(boolean isFullScreen) {
+        Log_OC.e(TAG, "Fullscreen: " + isFullScreen);
+    }
+
     private static class LoadStreamUrl extends AsyncTask<String, Void, Uri> {
 
-        private OwnCloudClient client;
-        private WeakReference<PreviewMediaFragment> previewMediaFragmentWeakReference;
+        private final ClientFactory clientFactory;
+        private final User user;
+        private final WeakReference<PreviewMediaFragment> previewMediaFragmentWeakReference;
 
-        public LoadStreamUrl(PreviewMediaFragment previewMediaFragment, OwnCloudClient client) {
-            this.client = client;
+        public LoadStreamUrl(PreviewMediaFragment previewMediaFragment, User user, ClientFactory clientFactory) {
             this.previewMediaFragmentWeakReference = new WeakReference<>(previewMediaFragment);
+            this.user = user;
+            this.clientFactory = clientFactory;
         }
 
         @Override
         protected Uri doInBackground(String... fileId) {
+            OwnCloudClient client;
+            try {
+                client = clientFactory.create(user);
+            } catch (ClientFactory.CreationException e) {
+                Log_OC.e(TAG, "Loading stream url not possible: " + e);
+                return null;
+            }
+
             StreamMediaFileOperation sfo = new StreamMediaFileOperation(fileId[0]);
             RemoteOperationResult result = sfo.execute(client);
 
@@ -479,7 +510,12 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
             if (previewMediaFragment != null && context != null) {
                 if (uri != null) {
                     previewMediaFragment.videoUri = uri;
-                    previewMediaFragment.binding.videoPreview.setVideoURI(uri);
+
+                    previewMediaFragment.binding.progress.setVisibility(View.GONE);
+
+                    previewMediaFragment.exoPlayer.addMediaItem(MediaItem.fromUri(uri));
+                    previewMediaFragment.exoPlayer.prepare();
+                    previewMediaFragment.exoPlayer.play();
                 } else {
                     previewMediaFragment.emptyListView.setVisibility(View.VISIBLE);
                     previewMediaFragment.setVideoErrorMessage(
@@ -492,70 +528,6 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
         }
     }
 
-    private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener {
-
-        /**
-         * Called when the file is ready to be played.
-         * <p/>
-         * Just starts the playback.
-         *
-         * @param   vp    {@link MediaPlayer} instance performing the playback.
-         */
-        @Override
-        public void onPrepared(MediaPlayer vp) {
-            Log_OC.v(TAG, "onPrepared");
-            binding.emptyView.emptyListView.setVisibility(View.GONE);
-            binding.progress.setVisibility(View.GONE);
-            binding.filePreviewContainer.setVisibility(View.VISIBLE);
-            binding.videoPreview.seekTo(savedPlaybackPosition);
-            if (autoplay) {
-                binding.videoPreview.start();
-            }
-            binding.mediaController.setEnabled(true);
-            binding.mediaController.updatePausePlay();
-            prepared = true;
-        }
-
-
-        /**
-         * Called when the file is finished playing.
-         * <p/>
-         * Finishes the activity.
-         *
-         * @param mp {@link MediaPlayer} instance performing the playback.
-         */
-        @Override
-        public void onCompletion(MediaPlayer mp) {
-            Log_OC.v(TAG, "completed");
-            if (mp != null) {
-                binding.videoPreview.seekTo(0);
-            } // else : called from onError()
-            binding.mediaController.updatePausePlay();
-        }
-
-        /**
-         * Called when an error in playback occurs.
-         *
-         * @param mp    {@link MediaPlayer} instance performing the playback.
-         * @param what  Type of error
-         * @param extra Extra code specific to the error
-         */
-        @Override
-        public boolean onError(MediaPlayer mp, int what, int extra) {
-            Log_OC.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra);
-            binding.filePreviewContainer.setVisibility(View.GONE);
-            binding.progress.setVisibility(View.GONE);
-            final Context context = getActivity();
-            if (binding.videoPreview.getWindowToken() != null && context != null) {
-                String message = ErrorFormat.toString(context, what, extra);
-                binding.emptyView.emptyListView.setVisibility(View.VISIBLE);
-                setVideoErrorMessage(message, R.string.preview_sorry);
-            }
-            return true;
-        }
-
-    }
-
     @Override
     public void onPause() {
         Log_OC.v(TAG, "onPause");
@@ -565,6 +537,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     @Override
     public void onResume() {
         super.onResume();
+        autoplay = false;
         Log_OC.v(TAG, "onResume");
     }
 
@@ -584,6 +557,10 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     @Override
     public void onStop() {
         Log_OC.v(TAG, "onStop");
+        if (MimeTypeUtil.isAudio(getFile()) && !mediaPlayerServiceConnection.isPlaying()) {
+            stopAudio();
+        }
+        
         mediaPlayerServiceConnection.unbind();
         toggleDrawerLockMode(containerActivity, DrawerLayout.LOCK_MODE_UNLOCKED);
         super.onStop();
@@ -591,7 +568,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
 
     @Override
     public boolean onTouch(View v, MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_DOWN && v.equals(binding.videoPreview)) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN && v.equals(binding.exoplayerView)) {
             // added a margin on the left to avoid interfering with gesture to open navigation drawer
             if (event.getX() / Resources.getSystem().getDisplayMetrics().density > MIN_DENSITY_RATIO) {
                 startFullScreenVideo();
@@ -605,10 +582,10 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
         Intent intent = new Intent(getActivity(), PreviewVideoActivity.class);
         intent.putExtra(FileActivity.EXTRA_ACCOUNT, user.toPlatformAccount());
         intent.putExtra(FileActivity.EXTRA_FILE, getFile());
-        intent.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, binding.videoPreview.isPlaying());
+        intent.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, exoPlayer.isPlaying());
         intent.putExtra(PreviewVideoActivity.EXTRA_STREAM_URL, videoUri);
-        binding.videoPreview.pause();
-        intent.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, binding.videoPreview.getCurrentPosition());
+        exoPlayer.pause();
+        intent.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, exoPlayer.getCurrentPosition());
         startActivityForResult(intent, FileActivity.REQUEST_CODE__LAST_SHARED + 1);
     }
 
@@ -623,7 +600,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
         Log_OC.v(TAG, "onActivityResult " + this);
         super.onActivityResult(requestCode, resultCode, data);
         if (resultCode == Activity.RESULT_OK) {
-            savedPlaybackPosition = data.getIntExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0);
+            savedPlaybackPosition = data.getLongExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0);
             autoplay = data.getBooleanExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, false);
         }
     }
@@ -638,8 +615,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     }
 
     /**
-     * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment}
-     * to be previewed.
+     * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed.
      *
      * @param file File to test if can be previewed.
      * @return 'True' if the file can be handled by the fragment.
@@ -653,7 +629,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
         if (MimeTypeUtil.isAudio(file) && stopAudio) {
             mediaPlayerServiceConnection.pause();
         } else if (MimeTypeUtil.isVideo(file)) {
-            binding.videoPreview.stopPlayback();
+            exoPlayer.stop(true);
         }
     }
 
@@ -667,9 +643,9 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
         }
     }
 
-    public int getPosition() {
+    public long getPosition() {
         if (prepared) {
-            savedPlaybackPosition = binding.videoPreview.getCurrentPosition();
+            savedPlaybackPosition = exoPlayer.getCurrentPosition();
         }
         Log_OC.v(TAG, "getting position: " + savedPlaybackPosition);
         return savedPlaybackPosition;
@@ -678,4 +654,13 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     private void toggleDrawerLockMode(ContainerActivity containerActivity, int lockMode) {
         ((DrawerActivity) containerActivity).setDrawerLockMode(lockMode);
     }
+
+    @Override
+    public void onDetach() {
+      
+        exoPlayer.stop();
+        exoPlayer.release();
+
+        super.onDetach();
+    }
 }

+ 40 - 52
src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.java

@@ -21,7 +21,6 @@
 package com.owncloud.android.ui.preview;
 
 import android.accounts.Account;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.OnCompletionListener;
@@ -29,9 +28,11 @@ import android.media.MediaPlayer.OnErrorListener;
 import android.media.MediaPlayer.OnPreparedListener;
 import android.net.Uri;
 import android.os.Bundle;
-import android.widget.MediaController;
-import android.widget.VideoView;
 
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.ui.StyledPlayerView;
 import com.nextcloud.client.media.ErrorFormat;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
@@ -62,10 +63,9 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
 
     private static final String TAG = PreviewVideoActivity.class.getSimpleName();
 
-    private int mSavedPlaybackPosition;         // in the unit time handled by MediaPlayer.getCurrentPosition()
+    private long mSavedPlaybackPosition = -1;         // in the unit time handled by MediaPlayer.getCurrentPosition()
     private boolean mAutoplay;                  // when 'true', the playback starts immediately with the activity
-    private VideoView mVideoPlayer;             // view to play the file; both performs and show the playback
-    private MediaController mMediaController;   // panel control used by the user to control the playback
+    private ExoPlayer exoPlayer;             // view to play the file; both performs and show the playback
     private Uri mStreamUri;
 
     /**
@@ -85,26 +85,25 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
 
         setContentView(R.layout.video_layout);
 
-        if (savedInstanceState == null) {
-            Bundle extras = getIntent().getExtras();
-            mSavedPlaybackPosition = extras.getInt(EXTRA_START_POSITION);
+        Bundle extras = getIntent().getExtras();
+
+        if (savedInstanceState == null && extras != null) {
+            mSavedPlaybackPosition = extras.getLong(EXTRA_START_POSITION);
             mAutoplay = extras.getBoolean(EXTRA_AUTOPLAY);
             mStreamUri = (Uri) extras.get(EXTRA_STREAM_URL);
-        } else {
-            mSavedPlaybackPosition = savedInstanceState.getInt(EXTRA_START_POSITION);
+        } else if (savedInstanceState != null) {
+            mSavedPlaybackPosition = savedInstanceState.getLong(EXTRA_START_POSITION);
             mAutoplay = savedInstanceState.getBoolean(EXTRA_AUTOPLAY);
             mStreamUri = (Uri) savedInstanceState.get(EXTRA_STREAM_URL);
         }
 
-        mVideoPlayer = findViewById(R.id.videoPlayer);
-
-        // set listeners to get more control on the playback
-        mVideoPlayer.setOnPreparedListener(this);
-        mVideoPlayer.setOnCompletionListener(this);
-        mVideoPlayer.setOnErrorListener(this);
+        StyledPlayerView playerView = findViewById(R.id.videoPlayer);
+        exoPlayer = new SimpleExoPlayer.Builder(this).build();
+        playerView.setPlayer(exoPlayer);
 
-        // keep the screen on while the playback is performed (prevents screen off by battery save)
-        mVideoPlayer.setKeepScreenOn(true);
+        if (mSavedPlaybackPosition >= 0) {
+            exoPlayer.seekTo(mSavedPlaybackPosition);
+        }
 
         if (getSupportActionBar() != null) {
             getSupportActionBar().hide();
@@ -117,8 +116,8 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
         super.onSaveInstanceState(outState);
-        outState.putInt(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition());
-        outState.putBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY , mVideoPlayer.isPlaying());
+        outState.putLong(PreviewVideoActivity.EXTRA_START_POSITION, exoPlayer.getCurrentPosition());
+        outState.putBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY, exoPlayer.isPlaying());
         outState.putParcelable(PreviewVideoActivity.EXTRA_STREAM_URL, mStreamUri);
     }
 
@@ -127,9 +126,13 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
     public void onBackPressed() {
         Log_OC.v(TAG, "onBackPressed");
         Intent i = new Intent();
-        i.putExtra(EXTRA_AUTOPLAY, mVideoPlayer.isPlaying());
-        i.putExtra(EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition());
+        i.putExtra(EXTRA_AUTOPLAY, exoPlayer.isPlaying());
+        i.putExtra(EXTRA_START_POSITION, exoPlayer.getCurrentPosition());
         setResult(RESULT_OK, i);
+
+        exoPlayer.stop();
+        exoPlayer.release();
+
         super.onBackPressed();
     }
 
@@ -144,11 +147,10 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
     @Override
     public void onPrepared(MediaPlayer mp) {
         Log_OC.v(TAG, "onPrepare");
-        mVideoPlayer.seekTo(mSavedPlaybackPosition);
+        exoPlayer.seekTo(mSavedPlaybackPosition);
         if (mAutoplay) {
-            mVideoPlayer.start();
+            exoPlayer.play();
         }
-        mMediaController.show(5000);
     }
 
 
@@ -161,7 +163,7 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
      */
     @Override
     public void onCompletion(MediaPlayer  mp) {
-        mVideoPlayer.seekTo(0);
+        exoPlayer.seekTo(0);
     }
 
 
@@ -176,23 +178,13 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
     public boolean onError(MediaPlayer mp, int what, int extra) {
         Log_OC.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra);
 
-        if (mMediaController != null) {
-            mMediaController.hide();
-        }
-
-        if (mVideoPlayer.getWindowToken() != null) {
-            String message = ErrorFormat.toString(this, what, extra);
-            new AlertDialog.Builder(this)
-                    .setMessage(message)
-                    .setPositiveButton(android.R.string.VideoView_error_button,
-                            new DialogInterface.OnClickListener() {
-                                public void onClick(DialogInterface dialog, int whichButton) {
-                                    PreviewVideoActivity.this.onCompletion(null);
-                                }
-                            })
-                    .setCancelable(false)
-                    .show();
-        }
+        String message = ErrorFormat.toString(this, what, extra);
+        new AlertDialog.Builder(this)
+            .setMessage(message)
+            .setPositiveButton(android.R.string.VideoView_error_button,
+                               (dialog, whichButton) -> PreviewVideoActivity.this.onCompletion(null))
+            .setCancelable(false)
+            .show();
         return true;
     }
 
@@ -211,17 +203,13 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
             file = getStorageManager().getFileById(file.getFileId());
             if (file != null) {
                 if (file.isDown()) {
-                    mVideoPlayer.setVideoURI(file.getStorageUri());
+                    exoPlayer.addMediaItem(MediaItem.fromUri(file.getStorageUri()));
                 } else {
-                    mVideoPlayer.setVideoURI(mStreamUri);
+                    exoPlayer.addMediaItem(MediaItem.fromUri(mStreamUri));
                 }
 
-                // create and prepare control panel for the user
-                mMediaController = new MediaController(this);
-                mMediaController.setMediaPlayer(mVideoPlayer);
-                mMediaController.setAnchorView(mVideoPlayer);
-                mVideoPlayer.setMediaController(mMediaController);
-
+                exoPlayer.prepare();
+                exoPlayer.play();
             } else {
                 finish();
             }

+ 23 - 38
src/main/res/layout/fragment_preview_media.xml

@@ -20,53 +20,38 @@
 -->
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/top"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:gravity="center"
     tools:context=".ui.preview.PreviewMediaFragment">
 
-    <RelativeLayout
-        android:id="@+id/file_preview_container"
+    <ImageView
+        android:id="@+id/image_preview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:layout_margin="@dimen/standard_margin"
+        android:contentDescription="@string/preview_image_description"
+        android:src="@drawable/logo" />
+
+
+    <com.google.android.exoplayer2.ui.StyledPlayerView
+        android:id="@+id/exoplayer_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:visibility="invisible"
-        android:background="@color/background_color_inverse">
-
-        <FrameLayout
-            android:id="@+id/visual_area"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_above="@+id/media_controller"
-            android:layout_alignParentTop="true">
-
-            <ImageView
-                android:id="@+id/image_preview"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                android:layout_margin="@dimen/standard_margin"
-                android:contentDescription="@string/preview_image_description"
-                android:src="@drawable/logo"/>
-
-            <VideoView
-                android:id="@+id/video_preview"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                android:visibility="gone" />
-
-        </FrameLayout>
-
-        <com.owncloud.android.media.MediaControlView
-            android:id="@id/media_controller"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            android:layout_margin="@dimen/standard_margin" />
-    </RelativeLayout>
+        android:layout_gravity="center"
+        app:show_buffering="when_playing" />
+
+    <com.owncloud.android.media.MediaControlView
+        android:id="@+id/media_controller"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_margin="@dimen/standard_margin"
+        android:visibility="gone" />
 
     <FrameLayout
         android:id="@+id/progress"

+ 18 - 22
src/main/res/layout/video_layout.xml

@@ -1,30 +1,26 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ownCloud Android client application
-
-  Copyright (C) 2015 ownCloud Inc.
+<?xml version="1.0" encoding="utf-8"?><!--
+  Nextcloud Android client application
 
+  @author Tobias Kaminsky
+  Copyright (C) 2021 Tobias Kaminsky
+  Copyright (C) 2021 Nextcloud GmbH
+ 
   This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License version 2,
-  as published by the Free Software Foundation.
-
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+ 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  GNU Affero General Public License for more details.
+ 
+  You should have received a copy of the GNU Affero General Public License
+  along with this program. If not, see <https://www.gnu.org/licenses/>.
 -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.google.android.exoplayer2.ui.StyledPlayerView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/videoPlayer"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/black">
-
-    <VideoView
-        android:id="@+id/videoPlayer"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center" />
-
-</FrameLayout>
+    android:layout_gravity="center"
+    android:background="@color/black" />

+ 1 - 0
src/main/res/values/strings.xml

@@ -962,4 +962,5 @@
     <string name="select_one_template">Please select one template</string>
     <string name="choose_template_helper_text">Please choose a template and enter a file name.</string>
     <string name="strict_mode">Strict mode: no http connection allowed!</string>
+    <string name="fullscreen">Fullscreen</string>
 </resources>