Sfoglia il codice sorgente

Merge pull request #9945 from nextcloud/fix/exoplayer-http

Exoplayer: use NextcloudClient's OkHttp client for connections
Tobias Kaminsky 3 anni fa
parent
commit
8a2c587aea

+ 3 - 1
build.gradle

@@ -75,6 +75,7 @@ ext {
     workRuntime = "2.7.1"
     fidoVersion = "4.1.0"
     checkerVersion = "3.21.2"
+    exoplayerVersion = "2.17.0"
 
     ciBuild = System.getenv("CI") == "true"
 }
@@ -300,7 +301,8 @@ dependencies {
     ktlint "com.pinterest:ktlint:0.44.0"
     implementation 'org.conscrypt:conscrypt-android:2.5.2'
 
-    implementation 'com.google.android.exoplayer:exoplayer:2.17.0'
+    implementation "com.google.android.exoplayer:exoplayer:$exoplayerVersion"
+    implementation "com.google.android.exoplayer:extension-okhttp:$exoplayerVersion"
 
     // Shimmer animation
     implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0'

+ 49 - 0
src/main/java/com/nextcloud/client/media/NextcloudExoPlayer.kt

@@ -0,0 +1,49 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Álvaro Brey Vilas
+ * Copyright (C) 2022 Álvaro Brey Vilas
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.client.media
+
+import android.content.Context
+import com.google.android.exoplayer2.ExoPlayer
+import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
+import com.google.android.exoplayer2.upstream.DefaultDataSource
+import com.nextcloud.common.NextcloudClient
+
+object NextcloudExoPlayer {
+
+    /**
+     * Creates an [ExoPlayer] that uses [NextcloudClient] for HTTP connections, thus respecting redirections,
+     * IP versions and certificates.
+     *
+     */
+    @JvmStatic
+    fun createNextcloudExoplayer(context: Context, nextcloudClient: NextcloudClient): ExoPlayer {
+        val okHttpDataSourceFactory = OkHttpDataSource.Factory(nextcloudClient.client)
+        val mediaSourceFactory = DefaultMediaSourceFactory(
+            DefaultDataSource.Factory(
+                context,
+                okHttpDataSourceFactory
+            )
+        )
+        return ExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build()
+    }
+}

+ 43 - 25
src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -34,6 +34,7 @@ import android.media.MediaMetadataRetriever;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -51,8 +52,10 @@ 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.NextcloudExoPlayer;
 import com.nextcloud.client.media.PlayerServiceConnection;
 import com.nextcloud.client.network.ClientFactory;
+import com.nextcloud.common.NextcloudClient;
 import com.owncloud.android.R;
 import com.owncloud.android.databinding.FragmentPreviewMediaBinding;
 import com.owncloud.android.datamodel.OCFile;
@@ -65,13 +68,13 @@ import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.ui.activity.DrawerActivity;
 import com.owncloud.android.ui.activity.FileActivity;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
-import com.owncloud.android.ui.activity.ToolbarActivity;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.utils.MimeTypeUtil;
 
 import java.lang.ref.WeakReference;
+import java.util.concurrent.Executors;
 
 import javax.inject.Inject;
 
@@ -305,29 +308,6 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
             // bind to any existing player
             mediaPlayerServiceConnection.bind();
 
-            if (exoPlayer == null) {
-                exoPlayer = new ExoPlayer.Builder(requireContext()).build();
-            }
-            binding.exoplayerView.setPlayer(exoPlayer);
-
-
-            LinearLayout linearLayout = binding.exoplayerView.findViewById(R.id.exo_center_controls);
-
-            if (linearLayout.getChildCount() == 5) {
-                AppCompatImageButton fullScreenButton = new AppCompatImageButton(requireContext());
-                fullScreenButton.setImageResource(R.drawable.exo_styled_controls_fullscreen_exit);
-                fullScreenButton.setLayoutParams(new LinearLayout.LayoutParams(143, 143));
-                fullScreenButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
-                fullScreenButton.setBackgroundColor(Color.TRANSPARENT);
-
-                fullScreenButton.setOnClickListener(l -> {
-                    startFullScreenVideo();
-                });
-
-                linearLayout.addView(fullScreenButton);
-                linearLayout.invalidate();
-            }
-
             if (MimeTypeUtil.isAudio(file)) {
                 binding.mediaController.setMediaPlayer(mediaPlayerServiceConnection);
                 binding.mediaController.setVisibility(View.VISIBLE);
@@ -339,11 +319,48 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
                     // always stop player
                     stopAudio();
                 }
-                playVideo();
+                if (exoPlayer != null) {
+                    playVideo();
+                } else {
+                    final Handler handler = new Handler();
+                    Executors.newSingleThreadExecutor().execute(() -> {
+                        try {
+                            final NextcloudClient client = clientFactory.createNextcloudClient(accountManager.getUser());
+                            handler.post(() ->{
+                                exoPlayer = NextcloudExoPlayer.createNextcloudExoplayer(requireContext(), client);
+                                playVideo();
+                            });
+                        } catch (ClientFactory.CreationException e) {
+                            handler.post(() -> {
+                                Log_OC.e(TAG, "error setting up ExoPlayer", e);
+                            });
+                        }
+                    });
+                }
             }
         }
     }
 
+    private void setupVideoView() {
+        binding.exoplayerView.setPlayer(exoPlayer);
+        LinearLayout linearLayout = binding.exoplayerView.findViewById(R.id.exo_center_controls);
+
+        if (linearLayout.getChildCount() == 5) {
+            AppCompatImageButton fullScreenButton = new AppCompatImageButton(requireContext());
+            fullScreenButton.setImageResource(R.drawable.exo_styled_controls_fullscreen_exit);
+            fullScreenButton.setLayoutParams(new LinearLayout.LayoutParams(143, 143));
+            fullScreenButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
+            fullScreenButton.setBackgroundColor(Color.TRANSPARENT);
+
+            fullScreenButton.setOnClickListener(l -> {
+                startFullScreenVideo();
+            });
+
+            linearLayout.addView(fullScreenButton);
+            linearLayout.invalidate();
+        }
+    }
+
     private void stopAudio() {
         mediaPlayerServiceConnection.stop();
     }
@@ -470,6 +487,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     }
 
     private void playVideo() {
+        setupVideoView();
         // load the video file in the video player
         // when done, VideoHelper#onPrepared() will be called
         if (getFile().isDown()) {

+ 42 - 10
src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.kt

@@ -31,13 +31,21 @@ import androidx.appcompat.app.AlertDialog
 import com.google.android.exoplayer2.ExoPlayer
 import com.google.android.exoplayer2.MediaItem
 import com.google.android.exoplayer2.Player
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.di.Injectable
 import com.nextcloud.client.media.ErrorFormat.toString
+import com.nextcloud.client.media.NextcloudExoPlayer.createNextcloudExoplayer
+import com.nextcloud.client.network.ClientFactory
 import com.owncloud.android.R
 import com.owncloud.android.databinding.ActivityPreviewVideoBinding
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.lib.common.utils.Log_OC
 import com.owncloud.android.ui.activity.FileActivity
 import com.owncloud.android.utils.MimeTypeUtil
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import javax.inject.Inject
 
 /**
  * Activity implementing a basic video player.
@@ -50,7 +58,14 @@ class PreviewVideoActivity :
     OnCompletionListener,
     OnPreparedListener,
     MediaPlayer.OnErrorListener,
-    Player.Listener {
+    Player.Listener,
+    Injectable {
+
+    @Inject
+    lateinit var clientFactory: ClientFactory
+
+    @Inject
+    lateinit var accountManager: UserAccountManager
 
     private var mSavedPlaybackPosition: Long = -1 // in the unit time handled by MediaPlayer.getCurrentPosition()
     private var mAutoplay = false // when 'true', the playback starts immediately with the activity
@@ -81,7 +96,10 @@ class PreviewVideoActivity :
             mStreamUri = savedInstanceState[EXTRA_STREAM_URL] as Uri?
         }
 
-        exoPlayer = ExoPlayer.Builder(this).build()
+        supportActionBar?.hide()
+    }
+
+    private fun setupPlayerView() {
         binding.videoPlayer.player = exoPlayer
         exoPlayer!!.addListener(this)
 
@@ -96,8 +114,6 @@ class PreviewVideoActivity :
         if (mSavedPlaybackPosition >= 0) {
             exoPlayer?.seekTo(mSavedPlaybackPosition)
         }
-
-        supportActionBar?.hide()
     }
 
     override fun onIsPlayingChanged(isPlaying: Boolean) {
@@ -181,6 +197,12 @@ class PreviewVideoActivity :
         return true
     }
 
+    private fun play(item: MediaItem) {
+        exoPlayer?.addMediaItem(item)
+        exoPlayer?.prepare()
+        exoPlayer?.play()
+    }
+
     override fun onStart() {
         super.onStart()
         if (account != null) {
@@ -188,18 +210,28 @@ class PreviewVideoActivity :
             require(file != null) { throw IllegalStateException("Instanced with a NULL OCFile") }
             var fileToPlay: OCFile? = file
 
-            // / Validate handled file  (first image to preview)
+            // Validate handled file  (first image to preview)
             require(MimeTypeUtil.isVideo(fileToPlay)) { "Non-video file passed as argument" }
 
             fileToPlay = storageManager.getFileById(fileToPlay!!.fileId)
             if (fileToPlay != null) {
-                if (fileToPlay.isDown) {
-                    exoPlayer?.addMediaItem(MediaItem.fromUri(fileToPlay.storageUri))
+                val mediaItem = when {
+                    fileToPlay.isDown -> MediaItem.fromUri(fileToPlay.storageUri)
+                    else -> MediaItem.fromUri(mStreamUri!!)
+                }
+                if (exoPlayer != null) {
+                    play(mediaItem)
                 } else {
-                    exoPlayer?.addMediaItem(MediaItem.fromUri(mStreamUri!!))
+                    val context = this
+                    CoroutineScope(Dispatchers.IO).launch {
+                        val client = clientFactory.createNextcloudClient(accountManager.user)
+                        CoroutineScope(Dispatchers.Main).launch {
+                            exoPlayer = createNextcloudExoplayer(context, client)
+                            setupPlayerView()
+                            play(mediaItem)
+                        }
+                    }
                 }
-                exoPlayer?.prepare()
-                exoPlayer?.play()
             } else {
                 finish()
             }