diff --git a/build.gradle b/build.gradle index cb2c82c01570..ddb737b03c53 100644 --- a/build.gradle +++ b/build.gradle @@ -337,6 +337,8 @@ dependencies { ktlint "com.pinterest:ktlint:0.41.0" implementation 'org.conscrypt:conscrypt-android:2.5.1' + implementation 'com.google.android.exoplayer:exoplayer:2.13.2' + // Shimmer animation implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0' diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index 8fcd69aefd92..fa6ef7f6d8d0 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 232 warnings + Lint Report: 231 warnings diff --git a/src/main/java/com/nextcloud/client/media/Player.kt b/src/main/java/com/nextcloud/client/media/Player.kt index a78b335300ba..ff48be53b743 100644 --- a/src/main/java/com/nextcloud/client/media/Player.kt +++ b/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 diff --git a/src/main/java/com/nextcloud/client/media/PlayerService.kt b/src/main/java/com/nextcloud/client/media/PlayerService.kt index 2693e7c12c31..536562126fcc 100644 --- a/src/main/java/com/nextcloud/client/media/PlayerService.kt +++ b/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) diff --git a/src/main/java/com/nextcloud/client/media/PlayerServiceConnection.kt b/src/main/java/com/nextcloud/client/media/PlayerServiceConnection.kt index 5ffbbd170dbf..88c88dc4e494 100644 --- a/src/main/java/com/nextcloud/client/media/PlayerServiceConnection.kt +++ b/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) diff --git a/src/main/java/com/nextcloud/client/media/PlaylistItem.kt b/src/main/java/com/nextcloud/client/media/PlaylistItem.kt index 91404c28ca4b..b822bf73d984 100644 --- a/src/main/java/com/nextcloud/client/media/PlaylistItem.kt +++ b/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) diff --git a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index 23a7ec5defe7..2f4be4ddbdc4 100644 --- a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -128,7 +128,6 @@ import org.parceler.Parcels; import java.io.File; -import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -559,7 +558,7 @@ private Fragment chooseInitialSecondFragment(OCFile file, User user) { 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)) { @@ -845,7 +844,8 @@ public boolean onOptionsItemSelected(MenuItem item) { second != null && second.getFile() != null || isSearchOpen()) { onBackPressed(); - } else if (getLeftFragment() instanceof FileDetailFragment) { + } else if (getLeftFragment() instanceof FileDetailFragment || + getLeftFragment() instanceof PreviewMediaFragment) { onBackPressed(); } else { openDrawer(); @@ -1898,7 +1898,7 @@ private void onRenameFileOperationFinish(RenameFileOperation operation, 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); @@ -2182,12 +2182,13 @@ public void startImagePreview(OCFile file, VirtualFolderType type, boolean showP * 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 = getUser(); if (!user.isPresent()) { @@ -2362,7 +2363,7 @@ public void onMessageEvent(SyncEventFinished event) { 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), diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java index 758d36e740c1..3aeaa4a8f470 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -31,10 +31,6 @@ 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,16 +44,19 @@ 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.device.DeviceInfo; 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; @@ -80,14 +79,15 @@ /** * 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. + *

+ * 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. */ -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(); @@ -96,6 +96,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"; @@ -103,7 +104,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; @@ -115,6 +116,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene @Inject DeviceInfo deviceInfo; FragmentPreviewMediaBinding binding; LinearLayout emptyListView; + private SimpleExoPlayer exoPlayer; /** * Creates a fragment to preview a file. @@ -124,14 +126,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); @@ -142,11 +146,10 @@ public static PreviewMediaFragment newInstance(OCFile fileToDetail, User user, i /** * Creates an empty fragment for previews. *

- * 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). *

- * 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(); @@ -163,7 +166,7 @@ public void onCreate(Bundle savedInstanceState) { 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()); } @@ -178,8 +181,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, emptyListView = binding.emptyView.emptyListView; - binding.videoPreview.setOnTouchListener(this); - setLoadingView(); return view; } @@ -222,11 +223,10 @@ public void onActivityCreated(Bundle savedInstanceState) { 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); } @@ -241,22 +241,37 @@ public void onActivityCreated(Bundle savedInstanceState) { */ 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); @@ -266,13 +281,11 @@ public void onSaveInstanceState(@NonNull Bundle outState) { 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()); } @@ -287,12 +300,15 @@ public void onStart() { // 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 @@ -311,6 +327,7 @@ private void stopAudio() { 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); } @@ -375,10 +392,10 @@ public void onPrepareOptionsMenu(@NonNull Menu menu) { 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); } @@ -404,6 +421,9 @@ public boolean onOptionsItemSelected(MenuItem item) { } 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); } @@ -411,7 +431,7 @@ public boolean onOptionsItemSelected(MenuItem item) { /** * 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); @@ -427,44 +447,55 @@ private void sendShareFile() { 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 { - private OwnCloudClient client; - private WeakReference previewMediaFragmentWeakReference; + private final ClientFactory clientFactory; + private final User user; + private final WeakReference 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); @@ -482,7 +513,12 @@ protected void onPostExecute(Uri uri) { 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( @@ -495,70 +531,6 @@ protected void onPostExecute(Uri uri) { } } - private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener { - - /** - * Called when the file is ready to be played. - *

- * 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. - *

- * 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"); @@ -568,6 +540,7 @@ public void onPause() { @Override public void onResume() { super.onResume(); + autoplay = false; Log_OC.v(TAG, "onResume"); } @@ -587,6 +560,10 @@ public void onDestroyView() { @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(); @@ -594,7 +571,7 @@ public void onStop() { @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(); @@ -608,10 +585,10 @@ private void startFullScreenVideo() { 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); } @@ -626,7 +603,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { 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); } } @@ -641,8 +618,7 @@ private void openFile() { } /** - * 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. @@ -656,7 +632,7 @@ public void stopPreview(boolean stopAudio) { if (MimeTypeUtil.isAudio(file) && stopAudio) { mediaPlayerServiceConnection.pause(); } else if (MimeTypeUtil.isVideo(file)) { - binding.videoPreview.stopPlayback(); + exoPlayer.stop(true); } } @@ -670,9 +646,9 @@ private void finishPreview() { } } - public int getPosition() { + public long getPosition() { if (prepared) { - savedPlaybackPosition = binding.videoPreview.getCurrentPosition(); + savedPlaybackPosition = exoPlayer.getCurrentPosition(); } Log_OC.v(TAG, "getting position: " + savedPlaybackPosition); return savedPlaybackPosition; @@ -681,4 +657,13 @@ public int getPosition() { private void toggleDrawerLockMode(ContainerActivity containerActivity, int lockMode) { ((DrawerActivity) containerActivity).setDrawerLockMode(lockMode); } + + @Override + public void onDetach() { + + exoPlayer.stop(); + exoPlayer.release(); + + super.onDetach(); + } } diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.java b/src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.java index dad19f4f3d36..d4496be2c5df 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.java +++ b/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.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 void onCreate(Bundle savedInstanceState) { 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 void onCreate(Bundle savedInstanceState) { @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 void onSaveInstanceState(@NonNull Bundle outState) { 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 void onBackPressed() { @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 void onPrepared(MediaPlayer mp) { */ @Override public void onCompletion(MediaPlayer mp) { - mVideoPlayer.seekTo(0); + exoPlayer.seekTo(0); } @@ -176,23 +178,13 @@ public void onCompletion(MediaPlayer mp) { 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 @@ protected void onStart() { 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(); } diff --git a/src/main/res/layout/fragment_preview_media.xml b/src/main/res/layout/fragment_preview_media.xml index e8456adc2ee1..47bbb85e4e4a 100644 --- a/src/main/res/layout/fragment_preview_media.xml +++ b/src/main/res/layout/fragment_preview_media.xml @@ -20,53 +20,38 @@ --> - + + + - - - - - - - - - - - + android:layout_gravity="center" + app:show_buffering="when_playing" /> + + - - - - - - + android:layout_gravity="center" + android:background="@color/black" /> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 831dd11e19b2..18b289e8445c 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -958,4 +958,5 @@ Please select one template Please choose a template and enter a file name. Strict mode: no http connection allowed! + Fullscreen