diff --git a/app/build.gradle b/app/build.gradle
index 063fae79d9..6a79df8219 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -333,6 +333,7 @@ dependencies {
implementation 'org.conscrypt:conscrypt-android:2.5.3'
implementation "androidx.media3:media3-ui:$androidxMediaVersion"
+ implementation "androidx.media3:media3-session:$androidxMediaVersion"
implementation "androidx.media3:media3-exoplayer:$androidxMediaVersion"
implementation "androidx.media3:media3-datasource-okhttp:$androidxMediaVersion"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e16471d7ff..b8acfd40db 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -127,7 +127,14 @@
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute"
tools:replace="android:allowBackup">
-
+
+
+
+
+
diff --git a/app/src/main/java/com/nextcloud/client/di/AppComponent.java b/app/src/main/java/com/nextcloud/client/di/AppComponent.java
index da9d7cb33d..1cdb220298 100644
--- a/app/src/main/java/com/nextcloud/client/di/AppComponent.java
+++ b/app/src/main/java/com/nextcloud/client/di/AppComponent.java
@@ -17,6 +17,8 @@ import com.nextcloud.client.integrations.IntegrationsModule;
import com.nextcloud.client.jobs.JobsModule;
import com.nextcloud.client.jobs.download.FileDownloadHelper;
import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.media.BackgroundPlayerService;
+import com.nextcloud.client.media.CustomExoPlayer;
import com.nextcloud.client.network.NetworkModule;
import com.nextcloud.client.onboarding.OnboardingModule;
import com.nextcloud.client.preferences.PreferencesModule;
@@ -46,7 +48,7 @@ import dagger.android.support.AndroidSupportInjectionModule;
ThemeModule.class,
DatabaseModule.class,
DispatcherModule.class,
- VariantModule.class
+ VariantModule.class,
})
@Singleton
public interface AppComponent {
@@ -54,6 +56,8 @@ public interface AppComponent {
void inject(MainApp app);
void inject(MediaControlView mediaControlView);
+ void inject(CustomExoPlayer customExoPlayer);
+ void inject(BackgroundPlayerService backgroundPlayerService);
void inject(ThemeableSwitchPreference switchPreference);
diff --git a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
index 8225e60be2..82d2d32615 100644
--- a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
+++ b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
@@ -17,6 +17,9 @@ import com.nextcloud.client.jobs.transfer.FileTransferService;
import com.nextcloud.client.jobs.upload.FileUploadHelper;
import com.nextcloud.client.logger.ui.LogsActivity;
import com.nextcloud.client.logger.ui.LogsViewModel;
+import com.nextcloud.client.media.BackgroundPlayerService;
+import com.nextcloud.client.media.CustomExoPlayer;
+import com.nextcloud.client.media.NextcloudExoPlayer;
import com.nextcloud.client.media.PlayerService;
import com.nextcloud.client.migrations.Migrations;
import com.nextcloud.client.onboarding.FirstRunActivity;
@@ -481,7 +484,12 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract TestJob testJob();
-
+
@ContributesAndroidInjector
abstract InternalTwoWaySyncActivity internalTwoWaySyncActivity();
+
+
+ @ContributesAndroidInjector
+ abstract BackgroundPlayerService backgroundPlayerService();
+
}
diff --git a/app/src/main/java/com/nextcloud/client/media/BackgroundPlayerService.kt b/app/src/main/java/com/nextcloud/client/media/BackgroundPlayerService.kt
new file mode 100644
index 0000000000..8699a46a6a
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/media/BackgroundPlayerService.kt
@@ -0,0 +1,82 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Your Name
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.media
+
+import android.content.Intent
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.session.MediaSession
+import androidx.media3.session.MediaSessionService
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.media.NextcloudExoPlayer.createNextcloudExoplayer
+import com.nextcloud.client.network.ClientFactory
+import com.nextcloud.common.NextcloudClient
+import com.owncloud.android.MainApp
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+class BackgroundPlayerService : MediaSessionService(), Injectable {
+
+ @Inject
+ lateinit var clientFactory: ClientFactory
+
+ @Inject
+ lateinit var userAccountManager: UserAccountManager
+ lateinit var exoPlayer: ExoPlayer
+ private var mediaSession: MediaSession? = null
+
+ override fun onCreate() {
+ super.onCreate()
+ MainApp.getAppComponent().inject(this)
+ initNextcloudExoPlayer()
+ }
+
+ private fun initNextcloudExoPlayer() {
+ runBlocking {
+ var nextcloudClient: NextcloudClient
+ withContext(Dispatchers.IO) {
+ nextcloudClient = clientFactory.createNextcloudClient(userAccountManager.user)
+ }
+ nextcloudClient?.let {
+ exoPlayer = createNextcloudExoplayer(this@BackgroundPlayerService, nextcloudClient)
+ println(exoPlayer)
+ mediaSession =
+ MediaSession.Builder(applicationContext, exoPlayer).setCallback(object : MediaSession.Callback {
+ override fun onDisconnected(session: MediaSession, controller: MediaSession.ControllerInfo) {
+ stopSelf()
+ }
+ }).build()
+ }
+ println("created client $nextcloudClient")
+ }
+ }
+
+ override fun onTaskRemoved(rootIntent: Intent?) {
+ val player = mediaSession?.player
+ if (player!!.playWhenReady) {
+ // Make sure the service is not in foreground.
+ player.pause()
+ }
+ stopSelf()
+ }
+
+ override fun onDestroy() {
+ mediaSession?.run {
+ player.release()
+ release()
+ mediaSession = null
+ }
+ super.onDestroy()
+ }
+
+ override fun onGetSession(p0: MediaSession.ControllerInfo): MediaSession? {
+ return mediaSession
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/client/media/CustomExoPlayer.kt b/app/src/main/java/com/nextcloud/client/media/CustomExoPlayer.kt
new file mode 100644
index 0000000000..57d1c1bc7a
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/media/CustomExoPlayer.kt
@@ -0,0 +1,22 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Your Name
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.media
+
+import android.accounts.AccountManager
+import android.content.Context
+import androidx.media3.exoplayer.ExoPlayer
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.account.UserAccountManagerImpl
+import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.network.ClientFactory
+import com.owncloud.android.MainApp
+import javax.inject.Inject
+
+class CustomExoPlayer {
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/nextcloud/client/media/NextcloudExoPlayer.kt b/app/src/main/java/com/nextcloud/client/media/NextcloudExoPlayer.kt
index 283f4e56ba..62523f9d0a 100644
--- a/app/src/main/java/com/nextcloud/client/media/NextcloudExoPlayer.kt
+++ b/app/src/main/java/com/nextcloud/client/media/NextcloudExoPlayer.kt
@@ -15,8 +15,11 @@ import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.okhttp.OkHttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.network.ClientFactory
import com.nextcloud.common.NextcloudClient
import com.owncloud.android.MainApp
+import javax.inject.Inject
object NextcloudExoPlayer {
private const val FIVE_SECONDS_IN_MILLIS = 5000L
@@ -45,4 +48,5 @@ object NextcloudExoPlayer {
.setSeekForwardIncrementMs(FIVE_SECONDS_IN_MILLIS)
.build()
}
+
}
diff --git a/app/src/main/java/com/owncloud/android/media/MediaControlView.kt b/app/src/main/java/com/owncloud/android/media/MediaControlView.kt
index 25747c3f1b..76cfb216b5 100644
--- a/app/src/main/java/com/owncloud/android/media/MediaControlView.kt
+++ b/app/src/main/java/com/owncloud/android/media/MediaControlView.kt
@@ -23,10 +23,10 @@ import android.view.View
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.LinearLayout
-import android.widget.MediaController.MediaPlayerControl
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.core.content.ContextCompat
+import androidx.media3.common.Player
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.databinding.MediaControlBinding
@@ -50,7 +50,7 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
View.OnClickListener,
OnSeekBarChangeListener {
- private var playerControl: MediaPlayerControl? = null
+ private var playerControl: Player? = null
private var binding: MediaControlBinding
private var isDragging = false
@@ -62,7 +62,7 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
@Suppress("MagicNumber")
- fun setMediaPlayer(player: MediaPlayerControl?) {
+ fun setMediaPlayer(player: Player?) {
playerControl = player
handler.sendEmptyMessage(SHOW_PROGRESS)
@@ -104,14 +104,16 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
*/
private fun disableUnsupportedButtons() {
try {
- if (playerControl?.canPause() == false) {
- binding.playBtn.setEnabled(false)
+ //TODO: should we check for nullability && see if we need try catch block
+ if (playerControl!!.isCommandAvailable(Player.COMMAND_PLAY_PAUSE).not()) {
+ binding.playBtn.isEnabled = false
}
- if (playerControl?.canSeekBackward() == false) {
- binding.rewindBtn.setEnabled(false)
+
+ if (playerControl!!.isCommandAvailable(Player.COMMAND_SEEK_BACK).not()) {
+ binding.rewindBtn.isEnabled = false
}
- if (playerControl?.canSeekForward() == false) {
- binding.forwardBtn.setEnabled(false)
+ if (playerControl!!.isCommandAvailable(Player.COMMAND_SEEK_FORWARD).not()) {
+ binding.forwardBtn.isEnabled = false
}
} catch (ex: IncompatibleClassChangeError) {
// We were given an old version of the interface, that doesn't have
@@ -149,7 +151,7 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
@Suppress("MagicNumber")
- private fun formatTime(timeMs: Int): String {
+ private fun formatTime(timeMs: Long): String {
val totalSeconds = timeMs / 1000
val seconds = totalSeconds % 60
val minutes = totalSeconds / 60 % 60
@@ -164,8 +166,8 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
@Suppress("MagicNumber")
- private fun setProgress(): Int {
- var position = 0
+ private fun setProgress(): Long {
+ var position = 0L
if (playerControl == null || isDragging) {
position = 0
}
@@ -178,7 +180,7 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
val pos = 1000L * position / duration
binding.progressBar.progress = pos.toInt()
}
- val percent = playerControl.bufferPercentage
+ val percent = playerControl.bufferedPercentage
binding.progressBar.setSecondaryProgress(percent * 10)
val endTime = if (duration > 0) formatTime(duration) else "--:--"
binding.totalTimeText.text = endTime
@@ -202,22 +204,25 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
return true
}
+
KeyEvent.KEYCODE_MEDIA_PLAY -> {
- if (uniqueDown && playerControl?.isPlaying == false) {
- playerControl?.start()
+ if (uniqueDown && playerControl?.playWhenReady == false) {
+ playerControl?.play()
updatePausePlay()
}
return true
}
+
KeyEvent.KEYCODE_MEDIA_STOP,
KeyEvent.KEYCODE_MEDIA_PAUSE
-> {
- if (uniqueDown && playerControl?.isPlaying == true) {
+ if (uniqueDown && playerControl?.playWhenReady == true) {
playerControl?.pause()
updatePausePlay()
}
return true
}
+
else -> return super.dispatchKeyEvent(event)
}
}
@@ -225,18 +230,21 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
fun updatePausePlay() {
binding.playBtn.icon = ContextCompat.getDrawable(
context,
+ // isPlaying reflects if the playback is actually moving forward, If the media is buffering and it will play when ready
+ // it would still return that it is not playing. So, in case of buffering it will show the pause icon which would show that
+ // media is loading, when user has not paused but moved the progress to a different position this works as a buffering signal.
if (playerControl?.isPlaying == true) {
R.drawable.ic_pause
} else {
R.drawable.ic_play
}
)
- binding.forwardBtn.visibility = if (playerControl?.canSeekForward() == true) {
+ binding.forwardBtn.visibility = if (playerControl!!.isCommandAvailable(Player.COMMAND_SEEK_FORWARD)) {
VISIBLE
} else {
INVISIBLE
}
- binding.rewindBtn.visibility = if (playerControl?.canSeekBackward() == true) {
+ binding.rewindBtn.visibility = if (playerControl!!.isCommandAvailable(Player.COMMAND_SEEK_BACK)) {
VISIBLE
} else {
INVISIBLE
@@ -245,10 +253,10 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
private fun doPauseResume() {
playerControl?.run {
- if (isPlaying) {
+ if (playWhenReady) {
pause()
} else {
- start()
+ play()
}
}
updatePausePlay()
@@ -267,16 +275,17 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
@Suppress("MagicNumber")
override fun onClick(v: View) {
- var pos: Int
+ var pos: Long
playerControl?.let { playerControl ->
- val playing = playerControl.isPlaying
+ val playing = playerControl.playWhenReady
val id = v.id
when (id) {
R.id.playBtn -> {
doPauseResume()
}
+
R.id.rewindBtn -> {
pos = playerControl.currentPosition
pos -= 5000
@@ -286,6 +295,7 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
setProgress()
}
+
R.id.forwardBtn -> {
pos = playerControl.currentPosition
pos += 15000
@@ -315,8 +325,8 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
playerControl?.let { playerControl ->
val duration = playerControl.duration.toLong()
val newPosition = duration * progress / 1000L
- playerControl.seekTo(newPosition.toInt())
- binding.currentTimeText.text = formatTime(newPosition.toInt())
+ playerControl.seekTo(newPosition)
+ binding.currentTimeText.text = formatTime(newPosition)
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
index 9f4726d402..07eb775c72 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
@@ -1832,6 +1832,7 @@ public class FileDisplayActivity extends FileActivity
((PreviewMediaFragment) fileFragment).updateFile(renamedFile);
if (PreviewMediaFragment.canBePreviewed(renamedFile)) {
long position = ((PreviewMediaFragment) fileFragment).getPosition();
+ System.out.println("Start Fragment Media Preview");
startMediaPreview(renamedFile, position, true, true, true, false);
} else {
getFileOperationsHelper().openFile(renamedFile);
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
index 2b9b793b87..b449bf57c9 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
@@ -13,15 +13,11 @@
package com.owncloud.android.ui.preview
import android.app.Activity
-import android.content.BroadcastReceiver
-import android.content.Context
+import android.content.ComponentName
import android.content.Intent
-import android.content.IntentFilter
import android.content.res.Configuration
-import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable
-import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.AsyncTask
import android.os.Build
@@ -45,20 +41,24 @@ import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.marginBottom
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.media3.common.MediaItem
+import androidx.media3.common.MediaMetadata
+import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.session.MediaController
+import androidx.media3.session.SessionToken
import androidx.media3.ui.DefaultTimeBar
import androidx.media3.ui.PlayerView
+import com.google.common.util.concurrent.MoreExecutors
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.jobs.download.FileDownloadHelper
+import com.nextcloud.client.media.BackgroundPlayerService
import com.nextcloud.client.media.ExoplayerListener
import com.nextcloud.client.media.NextcloudExoPlayer.createNextcloudExoplayer
-import com.nextcloud.client.media.PlayerService
import com.nextcloud.client.media.PlayerServiceConnection
import com.nextcloud.client.network.ClientFactory
import com.nextcloud.client.network.ClientFactory.CreationException
@@ -71,7 +71,6 @@ import com.nextcloud.utils.extensions.statusBarHeight
import com.owncloud.android.R
import com.owncloud.android.databinding.ActivityPreviewMediaBinding
import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.datamodel.ThumbnailsCacheManager
import com.owncloud.android.files.StreamMediaFileOperation
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener
@@ -88,7 +87,6 @@ import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment
import com.owncloud.android.ui.dialog.SendShareDialog
import com.owncloud.android.ui.fragment.FileFragment
import com.owncloud.android.ui.fragment.OCFileListFragment
-import com.owncloud.android.utils.BitmapUtils
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.ErrorMessageAdapter
import com.owncloud.android.utils.MimeTypeUtil
@@ -119,7 +117,7 @@ class PreviewMediaActivity :
private var autoplay = true
private val prepared = false
private var mediaPlayerServiceConnection: PlayerServiceConnection? = null
- private var videoUri: Uri? = null
+ private var streamUri: Uri? = null
@Inject
lateinit var clientFactory: ClientFactory
@@ -132,13 +130,15 @@ class PreviewMediaActivity :
private lateinit var binding: ActivityPreviewMediaBinding
private var emptyListView: ViewGroup? = null
- private var exoPlayer: ExoPlayer? = null
+ private var videoPlayer: ExoPlayer? = null
+ private var audioMediaController: MediaController? = null
private var nextcloudClient: NextcloudClient? = null
private lateinit var windowInsetsController: WindowInsetsControllerCompat
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) {
setTheme(R.style.Theme_ownCloud_Toolbar)
}
@@ -156,6 +156,13 @@ class PreviewMediaActivity :
emptyListView = binding.emptyView.emptyListView
showProgressLayout()
addMarginForEmptyView()
+ if (file == null) {
+ return
+ }
+ if (MimeTypeUtil.isAudio(file)) {
+ setGenericThumbnail()
+ initializeAudioPlayer()
+ }
}
private fun addMarginForEmptyView() {
@@ -173,25 +180,6 @@ class PreviewMediaActivity :
emptyListView?.layoutParams = layoutParams
}
- private fun registerMediaControlReceiver() {
- val filter = IntentFilter(MEDIA_CONTROL_READY_RECEIVER)
- LocalBroadcastManager.getInstance(this).registerReceiver(mediaControlReceiver, filter)
- }
-
- private val mediaControlReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- intent.getBooleanExtra(PlayerService.IS_MEDIA_CONTROL_LAYOUT_READY, false).run {
- if (this) {
- hideProgressLayout()
- mediaPlayerServiceConnection?.bind()
- setupAudioPlayerServiceConnection()
- } else {
- showProgressLayout()
- }
- }
- }
- }
-
private fun initArguments(savedInstanceState: Bundle?) {
intent?.let {
initWithIntent(it)
@@ -203,20 +191,6 @@ class PreviewMediaActivity :
} else {
initWithBundle(savedInstanceState)
}
-
- if (MimeTypeUtil.isAudio(file)) {
- preparePreviewForAudioFile()
- }
- }
-
- private fun preparePreviewForAudioFile() {
- registerMediaControlReceiver()
-
- if (file.isDown) {
- return
- }
-
- requestForDownload(file)
}
private fun initWithIntent(intent: Intent) {
@@ -245,8 +219,6 @@ class PreviewMediaActivity :
if (isFileVideo) {
binding.root.setBackgroundColor(resources.getColor(R.color.black, null))
- } else {
- extractAndSetCoverArt(file)
}
}
@@ -265,13 +237,13 @@ class PreviewMediaActivity :
private fun showProgressLayout() {
binding.progress.visibility = View.VISIBLE
- binding.mediaController.visibility = View.GONE
+ binding.audioControllerView.visibility = View.GONE
binding.emptyView.emptyListView.visibility = View.GONE
}
private fun hideProgressLayout() {
binding.progress.visibility = View.GONE
- binding.mediaController.visibility = View.VISIBLE
+ binding.audioControllerView.visibility = View.VISIBLE
binding.emptyView.emptyListView.visibility = View.VISIBLE
}
@@ -287,48 +259,6 @@ class PreviewMediaActivity :
}
}
- /**
- * tries to read the cover art from the audio file and sets it as cover art.
- *
- * @param file audio file with potential cover art
- */
- @Suppress("TooGenericExceptionCaught", "NestedBlockDepth")
- private fun extractAndSetCoverArt(file: OCFile) {
- if (!MimeTypeUtil.isAudio(file)) {
- return
- }
-
- val bitmap = if (file.storagePath == null) {
- getAudioThumbnail(file)
- } else {
- getThumbnail(file.storagePath) ?: getAudioThumbnail(file)
- }
-
- if (bitmap != null) {
- binding.imagePreview.setImageBitmap(bitmap)
- } else {
- setGenericThumbnail()
- }
- }
-
- @Suppress("TooGenericExceptionCaught")
- private fun getThumbnail(storagePath: String?): Bitmap? {
- return try {
- MediaMetadataRetriever().run {
- setDataSource(storagePath)
- BitmapFactory.decodeByteArray(embeddedPicture, 0, embeddedPicture?.size ?: 0)
- }
- } catch (t: Throwable) {
- BitmapUtils.drawableToBitmap(genericThumbnail())
- }
- }
-
- private fun getAudioThumbnail(file: OCFile): Bitmap? {
- return ThumbnailsCacheManager.getBitmapFromDiskCache(
- ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.remoteId
- )
- }
-
private fun setGenericThumbnail() {
binding.imagePreview.setImageDrawable(genericThumbnail())
}
@@ -356,8 +286,8 @@ class PreviewMediaActivity :
private fun saveMediaInstanceState(bundle: Bundle) {
bundle.run {
- if (MimeTypeUtil.isVideo(file) && exoPlayer != null) {
- exoPlayer?.let {
+ if (MimeTypeUtil.isVideo(file) && audioMediaController != null) {
+ audioMediaController?.let {
savedPlaybackPosition = it.currentPosition
autoplay = it.isPlaying
}
@@ -375,42 +305,13 @@ class PreviewMediaActivity :
Log_OC.v(TAG, "onStart")
- if (file == null) {
- return
- }
-
- mediaPlayerServiceConnection?.bind()
-
- if (MimeTypeUtil.isAudio(file)) {
- setupAudioPlayerServiceConnection()
- } else if (MimeTypeUtil.isVideo(file)) {
- if (mediaPlayerServiceConnection?.isConnected == true) {
- stopAudio()
- }
-
- if (exoPlayer != null) {
- playVideo()
- } else {
- initNextcloudExoPlayer()
- }
+ if (MimeTypeUtil.isVideo(file)) {
+ //TODO: should we somehow release any previous audio sessions?
+ initializeVideoPlayer()
}
}
- private fun setupAudioPlayerServiceConnection() {
- binding.mediaController.run {
- setMediaPlayer(mediaPlayerServiceConnection)
- visibility = View.VISIBLE
- }
-
- user?.let {
- mediaPlayerServiceConnection?.start(it, file, autoplay, savedPlaybackPosition)
- }
-
- binding.emptyView.emptyListView.visibility = View.GONE
- binding.progress.visibility = View.GONE
- }
-
- private fun initNextcloudExoPlayer() {
+ private fun initializeVideoPlayer() {
val handler = Handler(Looper.getMainLooper())
Executors.newSingleThreadExecutor().execute {
try {
@@ -418,9 +319,10 @@ class PreviewMediaActivity :
nextcloudClient?.let { client ->
handler.post {
- exoPlayer = createNextcloudExoplayer(this, client)
+ videoPlayer = createNextcloudExoplayer(this, client)
- exoPlayer?.let { player ->
+
+ videoPlayer?.let { player ->
player.addListener(
ExoplayerListener(
this,
@@ -439,6 +341,72 @@ class PreviewMediaActivity :
}
}
+ private fun releaseVideoPlayer() {
+ videoPlayer?.let {
+ savedPlaybackPosition = it.currentPosition
+ autoplay = it.playWhenReady
+ it.release()
+ }
+ videoPlayer = null
+ }
+
+ private fun initializeAudioPlayer() {
+ val sessionToken = SessionToken(this, ComponentName(this, BackgroundPlayerService::class.java))
+ val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
+ controllerFuture.addListener(
+ {
+ try {
+ audioMediaController = controllerFuture.get()
+ playAudio()
+ binding.audioControllerView.setMediaPlayer(audioMediaController)
+ } catch (e: Exception) {
+ println("exception raised while getting the media controller ${e.message}")
+ }
+ },
+ MoreExecutors.directExecutor()
+ )
+ }
+
+ private fun playAudio() {
+ if (file.isDown) {
+ prepareAudioPlayer(file.storageUri)
+ } else {
+ try {
+ LoadStreamUrl(this, user, clientFactory).execute(file.localId)
+ } catch (e: Exception) {
+ Log_OC.e(TAG, "Loading stream url not possible: $e")
+ }
+ }
+ }
+
+ private fun prepareAudioPlayer(uri: Uri) {
+ hideProgressLayout()
+ audioMediaController?.let { audioPlayer ->
+ audioPlayer.addListener(object : Player.Listener {
+ override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
+ val artworkBitmap = mediaMetadata.artworkData?.let { bytes: ByteArray ->
+ BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
+ }
+ if (artworkBitmap != null) {
+ binding.imagePreview.setImageBitmap(artworkBitmap)
+ }
+ }
+ })
+ audioPlayer.setMediaItem(MediaItem.fromUri(uri))
+ audioPlayer.playWhenReady = autoplay
+ audioPlayer.seekTo(savedPlaybackPosition)
+ audioPlayer.prepare()
+ }
+ }
+
+ private fun releaseAudioPlayer() {
+ audioMediaController?.let { audioPlayer ->
+ audioPlayer.stop()
+ audioPlayer.release()
+ }
+ audioMediaController = null
+ }
+
private fun initWindowInsetsController() {
windowInsetsController = WindowCompat.getInsetsController(
window,
@@ -495,7 +463,7 @@ class PreviewMediaActivity :
}
}
)
- it.player = exoPlayer
+ it.player = videoPlayer
}
}
@@ -567,7 +535,7 @@ class PreviewMediaActivity :
}
R.id.action_remove_file -> {
- exoPlayer?.stop()
+ videoPlayer?.pause()
val dialog = RemoveFilesDialogFragment.newInstance(file)
dialog.show(supportFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION)
}
@@ -614,6 +582,7 @@ class PreviewMediaActivity :
val removedFile = operation.file
val fileAvailable: Boolean = storageManager.fileExists(removedFile.fileId)
if (!fileAvailable && removedFile == file) {
+ releaseAudioPlayer()
finish()
}
} else if (operation is SynchronizeFileOperation) {
@@ -670,7 +639,7 @@ class PreviewMediaActivity :
setupVideoView()
if (file.isDown) {
- playVideoUri(file.storageUri)
+ prepareVideoPlayer(file.storageUri)
} else {
try {
LoadStreamUrl(this, user, clientFactory).execute(file.localId)
@@ -680,20 +649,15 @@ class PreviewMediaActivity :
}
}
- private fun playVideoUri(uri: Uri) {
+ private fun prepareVideoPlayer(uri: Uri) {
binding.progress.visibility = View.GONE
-
- exoPlayer?.run {
- setMediaItem(MediaItem.fromUri(uri))
+ val videoMediaItem = MediaItem.fromUri(uri)
+ videoPlayer?.run {
+ setMediaItem(videoMediaItem)
playWhenReady = autoplay
+ seekTo(savedPlaybackPosition)
prepare()
-
- if (savedPlaybackPosition >= 0) {
- seekTo(savedPlaybackPosition)
- }
}
-
- autoplay = false
}
private class LoadStreamUrl(
@@ -728,8 +692,12 @@ class PreviewMediaActivity :
val weakReference = previewMediaActivityWeakReference.get()
weakReference?.apply {
if (uri != null) {
- videoUri = uri
- playVideoUri(uri)
+ streamUri = uri
+ if (MimeTypeUtil.isVideo(file)) {
+ prepareVideoPlayer(uri)
+ } else if (MimeTypeUtil.isAudio(file)) {
+ prepareAudioPlayer(uri)
+ }
} else {
emptyListView?.visibility = View.VISIBLE
setVideoErrorMessage(
@@ -754,29 +722,15 @@ class PreviewMediaActivity :
}
override fun onDestroy() {
- Log_OC.v(TAG, "onDestroy")
-
- LocalBroadcastManager.getInstance(this).unregisterReceiver(mediaControlReceiver)
-
super.onDestroy()
- exoPlayer?.run {
- stop()
- release()
- }
+
+ Log_OC.v(TAG, "onDestroy")
}
override fun onStop() {
Log_OC.v(TAG, "onStop")
- file?.let {
- if (MimeTypeUtil.isVideo(it) && exoPlayer != null && exoPlayer?.isPlaying == true) {
- savedPlaybackPosition = exoPlayer?.currentPosition ?: 0L
- }
- }
-
- exoPlayer?.pause()
- stopAudio()
- mediaPlayerServiceConnection?.unbind()
+ releaseVideoPlayer()
super.onStop()
}
@@ -829,23 +783,15 @@ class PreviewMediaActivity :
}
private fun stopPreview(stopAudio: Boolean) {
+ //TODO: stop removes the media item attached but not release the player
+ // do we want to keep this behaviour or release the player too just like in onStop?
if (MimeTypeUtil.isAudio(file) && stopAudio) {
- mediaPlayerServiceConnection?.pause()
+ audioMediaController?.pause()
} else if (MimeTypeUtil.isVideo(file)) {
- savedPlaybackPosition = exoPlayer?.currentPosition ?: 0
- exoPlayer?.stop()
+ releaseVideoPlayer()
}
}
- val position: Long
- get() {
- if (prepared) {
- savedPlaybackPosition = exoPlayer?.currentPosition ?: 0
- }
- Log_OC.v(TAG, "getting position: $savedPlaybackPosition")
- return savedPlaybackPosition
- }
-
companion object {
private val TAG = PreviewMediaActivity::class.java.simpleName
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.kt
index f939dbf8ec..51cac35b2c 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.kt
@@ -330,7 +330,7 @@ class PreviewMediaFragment : FileFragment(), OnTouchListener, Injectable {
}
private fun prepareForAudio() {
- binding.mediaController.setMediaPlayer(mediaPlayerServiceConnection)
+ binding.mediaController.setMediaPlayer(null)
binding.mediaController.visibility = View.VISIBLE
mediaPlayerServiceConnection?.start(user!!, file, autoplay, savedPlaybackPosition)
binding.emptyView.emptyListView.visibility = View.GONE
diff --git a/app/src/main/res/layout/activity_preview_media.xml b/app/src/main/res/layout/activity_preview_media.xml
index b61873086f..7cc161f133 100644
--- a/app/src/main/res/layout/activity_preview_media.xml
+++ b/app/src/main/res/layout/activity_preview_media.xml
@@ -42,7 +42,7 @@
app:show_buffering="always" />
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ >
+
+
+
+
+
+
+
+
@@ -9718,6 +9726,11 @@
+
+
+
+
+
@@ -11041,6 +11054,11 @@
+
+
+
+
+
@@ -11091,6 +11109,11 @@
+
+
+
+
+
@@ -11115,6 +11138,11 @@
+
+
+
+
+