diff --git a/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt b/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt
index 97b2dad00005..16e19db0f9a5 100644
--- a/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt
+++ b/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt
@@ -12,6 +12,7 @@ import android.os.SystemClock
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
+import com.nextcloud.test.Flaky
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import org.junit.Assert.assertEquals
@@ -33,6 +34,7 @@ class LauncherActivityIT : AbstractIT() {
}
@Test
+ @Flaky(reason = "Opening FileDisplayActivity can keep the main thread busy past the launch timeout")
fun testSplashScreenWithTitlesShouldShowTitles() {
val activity = launchLauncherActivity()
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bef21720c2e5..9020a664b936 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -391,10 +391,9 @@
android:launchMode="singleTask"
android:theme="@style/Theme.ownCloud.Overlay" />
Unit)? = null
-
private var videoSurfaceView: SurfaceView? = null
private var cachedCurrentFiles: List? = null
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/PlayerLauncher.kt b/app/src/main/java/com/nextcloud/client/player/media3/PlaybackQueueLoader.kt
similarity index 53%
rename from app/src/main/java/com/nextcloud/client/player/ui/PlayerLauncher.kt
rename to app/src/main/java/com/nextcloud/client/player/media3/PlaybackQueueLoader.kt
index 9306da9035e1..d57e951f6fcf 100644
--- a/app/src/main/java/com/nextcloud/client/player/ui/PlayerLauncher.kt
+++ b/app/src/main/java/com/nextcloud/client/player/media3/PlaybackQueueLoader.kt
@@ -5,13 +5,11 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-package com.nextcloud.client.player.ui
+package com.nextcloud.client.player.media3
-import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.nextcloud.client.logger.Logger
-import com.nextcloud.client.player.media3.PlaybackModel
import com.nextcloud.client.player.media3.resumption.PlaybackResumptionConfigStore
import com.nextcloud.client.player.model.file.PlaybackCollection
import com.nextcloud.client.player.model.file.PlaybackFileType
@@ -26,55 +24,33 @@ import kotlinx.coroutines.launch
import java.util.concurrent.CancellationException
import javax.inject.Inject
-class PlayerLauncher @Inject constructor(
+class PlaybackQueueLoader @Inject constructor(
private val playbackResumptionConfigStore: PlaybackResumptionConfigStore,
private val playbackFilesRepository: PlaybackFilesRepository,
private val playbackModel: PlaybackModel,
private val logger: Logger
) {
- private var currentLaunchJob: Job? = null
-
- /**
- * Starts playback and opens [PlayerActivity] on top of [activity].
- */
- fun launch(activity: AppCompatActivity, file: OCFile, collection: PlaybackCollection) {
- run(activity) {
- val fileType = prepareQueue(file, collection)
- playbackModel.play()
- activity.startActivity(PlayerActivity.createIntent(activity, fileType))
- }
- }
-
- /**
- * Loads [file] and the media of its collection into the player without opening [PlayerActivity], so that a host
- * screen can render the playback itself.
- */
- fun prepare(owner: LifecycleOwner, file: OCFile, collection: PlaybackCollection, autoplay: Boolean = false) {
- run(owner) {
- prepareQueue(file, collection)
- if (autoplay) {
- playbackModel.play()
- }
- }
- }
-
- private fun run(owner: LifecycleOwner, block: suspend () -> Unit) {
- currentLaunchJob?.cancel()
- currentLaunchJob = owner.lifecycleScope.launch {
- runCatching { block() }.onFailure {
+ private var loadJob: Job? = null
+
+ fun load(owner: LifecycleOwner, file: OCFile, collection: PlaybackCollection, onLoaded: () -> Unit = {}) {
+ loadJob?.cancel()
+ loadJob = owner.lifecycleScope.launch {
+ runCatching {
+ loadQueue(file, collection)
+ onLoaded()
+ }.onFailure {
if (it is CancellationException) throw it
- logger.e(PlayerLauncher::class.java.simpleName, "Error launching player", it)
+ logger.e(PlaybackQueueLoader::class.java.simpleName, "Error loading playback queue", it)
}
}
}
- private suspend fun prepareQueue(file: OCFile, collection: PlaybackCollection): PlaybackFileType {
+ private suspend fun loadQueue(file: OCFile, collection: PlaybackCollection) {
val fileType = PlaybackFileType.ofMimeType(file.resolveMimeType())
playbackResumptionConfigStore.saveConfig(file.localId.toString(), file.parentId, fileType, collection)
playbackModel.start()
playbackModel.setFiles(PlaybackFiles(listOf(file.toPlaybackFile()), PlaybackFilesComparator.NONE))
playbackModel.setFilesFlow(playbackFilesRepository.observe(file.parentId, fileType, collection))
- return fileType
}
}
diff --git a/app/src/main/java/com/nextcloud/client/player/media3/PlaybackService.kt b/app/src/main/java/com/nextcloud/client/player/media3/PlaybackService.kt
index b84e1e2ba14f..184f8904bd7e 100644
--- a/app/src/main/java/com/nextcloud/client/player/media3/PlaybackService.kt
+++ b/app/src/main/java/com/nextcloud/client/player/media3/PlaybackService.kt
@@ -11,14 +11,19 @@ import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import android.os.IBinder
-import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSession.ControllerInfo
import androidx.media3.session.MediaSessionService
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.player.media3.resumption.PlaybackResumptionConfigStore
+import com.nextcloud.client.player.model.file.PlaybackFile
import com.nextcloud.client.player.model.file.PlaybackFileType
-import com.nextcloud.client.player.ui.PlayerActivity
+import com.nextcloud.client.player.model.file.toVirtualFolderType
+import com.nextcloud.client.player.ui.audio.AudioPlayerActivity
import com.nextcloud.client.player.util.PlayerUtil.playbackFile
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.ui.preview.PreviewImageActivity
import dagger.android.AndroidInjection
import javax.inject.Inject
@@ -32,9 +37,15 @@ class PlaybackService : MediaSessionService() {
@Inject
lateinit var playbackModel: PlaybackModel
+ @Inject
+ lateinit var userAccountManager: UserAccountManager
+
+ @Inject
+ lateinit var playbackResumptionConfigStore: PlaybackResumptionConfigStore
+
private var bindingCount: Int = 0
- private var sessionActivityFileType: PlaybackFileType? = null
+ private var sessionActivityMediaId: String? = null
override fun onCreate() {
super.onCreate()
@@ -77,22 +88,37 @@ class PlaybackService : MediaSessionService() {
}
private fun updateSessionActivity(session: MediaSession) {
- val fileType = session.player.currentMediaItem.playbackFileType() ?: return
- if (fileType == sessionActivityFileType) {
- return
+ val mediaItem = session.player.currentMediaItem?.takeIf { it.mediaId != sessionActivityMediaId } ?: return
+ val intent = mediaItem.mediaMetadata.playbackFile?.let(::createSessionIntent) ?: return
+ sessionActivityMediaId = mediaItem.mediaId
+ session.setSessionActivity(createSessionActivity(intent))
+ }
+
+ private fun createSessionIntent(playbackFile: PlaybackFile): Intent? {
+ val fileType = PlaybackFileType.entries.firstOrNull {
+ playbackFile.mimeType.startsWith(it.value, ignoreCase = true)
}
- sessionActivityFileType = fileType
- session.setSessionActivity(createSessionActivity(fileType))
+ return when (fileType) {
+ PlaybackFileType.AUDIO -> AudioPlayerActivity.createIntent(this)
+ PlaybackFileType.VIDEO -> createVideoPreviewIntent(playbackFile)
+ null -> null
+ }
}
- private fun MediaItem?.playbackFileType(): PlaybackFileType? {
- val mimeType = this?.mediaMetadata?.playbackFile?.mimeType ?: return null
- return PlaybackFileType.entries.firstOrNull { mimeType.startsWith(it.value, ignoreCase = true) }
+ private fun createVideoPreviewIntent(playbackFile: PlaybackFile): Intent? {
+ val user = userAccountManager.user
+ val file = playbackFile.id.toLongOrNull()
+ ?.let { FileDataStorageManager(user, contentResolver).getFileByLocalId(it) }
+ ?: return null
+ val virtualFolderType = playbackResumptionConfigStore.loadConfig()?.collection?.toVirtualFolderType()
+
+ return PreviewImageActivity.previewFileIntent(this, user, file).apply {
+ putExtra(PreviewImageActivity.EXTRA_VIRTUAL_TYPE, virtualFolderType)
+ }
}
- private fun createSessionActivity(fileType: PlaybackFileType): PendingIntent {
- val intent = PlayerActivity.createIntent(this, fileType)
+ private fun createSessionActivity(intent: Intent): PendingIntent {
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
diff --git a/app/src/main/java/com/nextcloud/client/player/model/file/PlaybackCollection.kt b/app/src/main/java/com/nextcloud/client/player/model/file/PlaybackCollection.kt
index 7155f2f16e98..c7f525b0b77d 100644
--- a/app/src/main/java/com/nextcloud/client/player/model/file/PlaybackCollection.kt
+++ b/app/src/main/java/com/nextcloud/client/player/model/file/PlaybackCollection.kt
@@ -31,3 +31,10 @@ fun VirtualFolderType?.toPlaybackCollection(): PlaybackCollection = when (this)
VirtualFolderType.ALBUM -> PlaybackCollection.ALBUM
else -> PlaybackCollection.FOLDER
}
+
+fun PlaybackCollection.toVirtualFolderType(): VirtualFolderType = when (this) {
+ PlaybackCollection.FAVORITES -> VirtualFolderType.FAVORITE
+ PlaybackCollection.GALLERY -> VirtualFolderType.GALLERY
+ PlaybackCollection.ALBUM -> VirtualFolderType.ALBUM
+ PlaybackCollection.FOLDER, PlaybackCollection.SHARED -> VirtualFolderType.NONE
+}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/PlayerActivity.kt b/app/src/main/java/com/nextcloud/client/player/ui/PlayerActivity.kt
deleted file mode 100644
index 01e39248a9bc..000000000000
--- a/app/src/main/java/com/nextcloud/client/player/ui/PlayerActivity.kt
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2025 STRATO GmbH.
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-package com.nextcloud.client.player.ui
-
-import android.content.Context
-import android.content.Intent
-import android.content.res.Configuration
-import android.media.AudioManager
-import android.os.Bundle
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.addCallback
-import androidx.activity.enableEdgeToEdge
-import androidx.activity.viewModels
-import androidx.core.view.ViewCompat
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.flowWithLifecycle
-import androidx.lifecycle.lifecycleScope
-import com.nextcloud.client.di.Injectable
-import com.nextcloud.client.di.ViewModelFactory
-import com.nextcloud.client.player.model.file.PlaybackFileType
-import com.nextcloud.client.player.ui.audio.AudioPlayerView
-import com.nextcloud.client.player.ui.video.VideoPlayerView
-import com.nextcloud.ui.fileactions.FileActionsBottomSheet
-import com.nextcloud.utils.SnackbarUtil
-import com.nextcloud.utils.extensions.getSerializableArgument
-import com.owncloud.android.R
-import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.ui.activity.FileActivity
-import com.owncloud.android.ui.activity.FileDisplayActivity
-import com.owncloud.android.ui.dialog.ConfirmationDialogFragment
-import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import javax.inject.Inject
-
-class PlayerActivity :
- FileActivity(),
- Injectable {
-
- companion object {
- private const val PLAYBACK_FILE_TYPE: String = "PLAYBACK_FILE_TYPE"
-
- fun createIntent(context: Context, playbackFileType: PlaybackFileType): Intent =
- Intent(context, PlayerActivity::class.java).apply {
- putExtra(PLAYBACK_FILE_TYPE, playbackFileType)
- addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
- }
- }
-
- @Inject
- lateinit var viewModelFactory: ViewModelFactory
-
- private val viewModel by viewModels { viewModelFactory }
-
- private lateinit var playbackFileType: PlaybackFileType
-
- private lateinit var playerView: PlayerView
-
- private val pictureInPicture by lazy { VideoPictureInPicture(this, playbackModel, autoEnter = true) }
-
- private var onBackPressedCallback: OnBackPressedCallback? = null
-
- private var keepPlaybackAliveOnFinish = false
-
- override fun onCreate(savedInstanceState: Bundle?) {
- enableEdgeToEdge()
- super.onCreate(savedInstanceState)
- ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, windowInsets -> windowInsets }
-
- playbackFileType = intent.getPlaybackFileType()
- createPlayerView()
-
- viewModel.eventFlow
- .flowWithLifecycle(lifecycle)
- .onEach { handleEvent(it) }
- .launchIn(lifecycleScope)
-
- onBackPressedCallback = onBackPressedDispatcher.addCallback(this) {
- if (canUsePictureInPictureMode() && pictureInPicture.enter(playerView)) {
- return@addCallback
- }
-
- file = file?.parentId?.let { storageManager.getFileById(it) }
- finish()
- }
-
- volumeControlStream = AudioManager.STREAM_MUSIC
- }
-
- override fun onNewIntent(intent: Intent) {
- super.onNewIntent(intent)
- playbackFileType = intent.getPlaybackFileType()
- recreatePlayerView()
- }
-
- private fun createPlayerView() {
- playerView = when (playbackFileType) {
- PlaybackFileType.AUDIO -> AudioPlayerView(this)
- PlaybackFileType.VIDEO -> VideoPlayerView(this)
- }
- playerView.onMoreClick = { viewModel.onMoreButtonClick() }
- setContentView(playerView)
- }
-
- private fun recreatePlayerView() {
- playerView.onStop()
- playerView.release()
- createPlayerView()
- playerView.onStart()
- }
-
- private fun Intent.getPlaybackFileType(): PlaybackFileType =
- getSerializableArgument(PLAYBACK_FILE_TYPE, PlaybackFileType::class.java)
- ?: throw IllegalStateException("Playback file type was not defined")
-
- override fun onStart() {
- super.onStart()
- playerView.onStart()
- }
-
- override fun onStop() {
- super.onStop()
- playerView.onStop()
- }
-
- override fun onDestroy() {
- super.onDestroy()
- playbackModel.onPictureInPictureClose = null
- if (isFinishing && !keepPlaybackAliveOnFinish && playbackFileType == PlaybackFileType.VIDEO) {
- playbackModel.release()
- }
- }
-
- override fun onUserLeaveHint() {
- super.onUserLeaveHint()
- if (canUsePictureInPictureMode()) {
- pictureInPicture.enter(playerView)
- }
- }
-
- override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
- super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
-
- playbackModel.onPictureInPictureClose = if (isInPictureInPictureMode) {
- {
- keepPlaybackAliveOnFinish = true
- finish()
- }
- } else {
- null
- }
-
- // Finish the activity if the user closes the PIP window
- if (!isInPictureInPictureMode && lifecycle.currentState == Lifecycle.State.CREATED) {
- finish()
- return
- }
- }
-
- private fun canUsePictureInPictureMode(): Boolean =
- playbackFileType == PlaybackFileType.VIDEO && pictureInPicture.isAllowed
-
- private fun handleEvent(event: PlayerScreenEvent) {
- when (event) {
- is PlayerScreenEvent.ShowFileActions -> showFileActions(event.file, event.actionsToHide)
- is PlayerScreenEvent.ShowFileDetails -> showFileDetails(event.file)
- is PlayerScreenEvent.ShowFileExportStartedMessage -> showFileExportStartedMessage()
- is PlayerScreenEvent.ShowShareFileDialog -> fileOperationsHelper.sendShareFile(event.file)
- is PlayerScreenEvent.ShowRemoveFileDialog -> showRemoveFileDialog(event.file)
- is PlayerScreenEvent.LaunchOpenFileIntent -> fileOperationsHelper.openFile(event.file)
- is PlayerScreenEvent.LaunchStreamFileIntent -> fileOperationsHelper.streamMediaFile(event.file)
- is PlayerScreenEvent.ToggleFileLock -> fileOperationsHelper.toggleFileLock(event.file, event.shouldBeLocked)
- is PlayerScreenEvent.AddFileToAlbum -> fileOperationsHelper.addFileToAlbum(listOf(event.file))
- }
- }
-
- private fun showFileActions(file: OCFile, actionsToHide: List) {
- FileActionsBottomSheet.newInstance(file, false, actionsToHide)
- .setResultListener(supportFragmentManager, this) { viewModel.onFileActionChosen(file, it) }
- .show(supportFragmentManager, "actions")
- }
-
- private fun showFileDetails(file: OCFile) {
- val intent = Intent(this, FileDisplayActivity::class.java).apply {
- action = FileDisplayActivity.ACTION_DETAILS
- putExtra(EXTRA_FILE, file)
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- }
- startActivity(intent)
- finish()
- }
-
- private fun showFileExportStartedMessage() {
- val message = resources.getQuantityString(R.plurals.export_start, 1, 1)
- SnackbarUtil.show(playerView, message)
- }
-
- private fun showRemoveFileDialog(file: OCFile) {
- RemoveFilesDialogFragment.newInstance(file)
- .show(supportFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION)
- }
-}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/PlayerScreenEvent.kt b/app/src/main/java/com/nextcloud/client/player/ui/PlayerScreenEvent.kt
deleted file mode 100644
index 7f8f56c57202..000000000000
--- a/app/src/main/java/com/nextcloud/client/player/ui/PlayerScreenEvent.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2025 STRATO GmbH.
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-package com.nextcloud.client.player.ui
-
-import com.owncloud.android.datamodel.OCFile
-
-sealed interface PlayerScreenEvent {
-
- data class ShowFileActions(val file: OCFile, val actionsToHide: List) : PlayerScreenEvent
-
- data class ShowFileDetails(val file: OCFile) : PlayerScreenEvent
-
- data object ShowFileExportStartedMessage : PlayerScreenEvent
-
- data class ShowShareFileDialog(val file: OCFile) : PlayerScreenEvent
-
- data class ShowRemoveFileDialog(val file: OCFile) : PlayerScreenEvent
-
- data class LaunchOpenFileIntent(val file: OCFile) : PlayerScreenEvent
-
- data class LaunchStreamFileIntent(val file: OCFile) : PlayerScreenEvent
-
- data class ToggleFileLock(val file: OCFile, val shouldBeLocked: Boolean) : PlayerScreenEvent
-
- data class AddFileToAlbum(val file: OCFile) : PlayerScreenEvent
-}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/PlayerView.kt b/app/src/main/java/com/nextcloud/client/player/ui/PlayerView.kt
deleted file mode 100644
index db771fe61b37..000000000000
--- a/app/src/main/java/com/nextcloud/client/player/ui/PlayerView.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2025 STRATO GmbH.
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-package com.nextcloud.client.player.ui
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.LinearLayout
-import androidx.annotation.CallSuper
-import androidx.annotation.LayoutRes
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.graphics.Insets
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
-import com.google.android.material.appbar.MaterialToolbar
-import com.nextcloud.client.account.UserAccountManager
-import com.nextcloud.client.jobs.download.FileDownloadHelper
-import com.nextcloud.client.player.media3.PlaybackModel
-import com.nextcloud.client.player.model.error.SourceException
-import com.nextcloud.client.player.model.file.PlaybackFile
-import com.nextcloud.client.player.model.state.PlaybackState
-import com.nextcloud.client.player.ui.control.PlayerControlView
-import com.nextcloud.client.player.ui.pager.PlayerPager
-import com.nextcloud.client.player.util.WindowWrapper
-import com.nextcloud.utils.SnackbarUtil
-import com.owncloud.android.R
-import com.owncloud.android.datamodel.FileDataStorageManager
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
-import com.owncloud.android.lib.common.utils.Log_OC
-import com.owncloud.android.operations.DownloadFileOperation
-import dagger.android.HasAndroidInjector
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import javax.inject.Inject
-
-abstract class PlayerView @JvmOverloads constructor(
- private val context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0
-) : LinearLayout(context, attrs, defStyleAttr),
- PlaybackModel.Listener {
-
- companion object {
- private const val TAG = "PlayerView"
- }
-
- @Inject
- lateinit var playbackModel: PlaybackModel
-
- @Inject
- lateinit var userAccountManager: UserAccountManager
-
- @get:LayoutRes
- protected abstract val layoutRes: Int
-
- protected abstract val createFragment: (PlaybackFile) -> Fragment
-
- protected val activity: AppCompatActivity by lazy { context as AppCompatActivity }
- protected val windowWrapper: WindowWrapper by lazy { WindowWrapper(activity.window) }
-
- protected val topBar: MaterialToolbar by lazy { findViewById(R.id.topBar) }
- protected val playerPager: PlayerPager by lazy { findViewById(R.id.playerPager) }
- protected val playerControlView: PlayerControlView by lazy { findViewById(R.id.playerControlView) }
-
- var onMoreClick: (() -> Unit)? = null
-
- init {
- inflate(context, layoutRes, this)
- if (!isInEditMode) {
- (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
- playerPager.initialize(activity.supportFragmentManager, activity.lifecycle, createFragment)
- playerPager.onItemSelected = { playbackModel.switchToFile(it) }
- playerControlView.navigator = playerPager
- topBar.setNavigationOnClickListener { activity.onBackPressedDispatcher.onBackPressed() }
- topBar.setOnMenuItemClickListener { menuItem ->
- val isMore = menuItem.itemId == R.id.action_more
- if (isMore) {
- onMoreClick?.invoke()
- }
- isMore
- }
- }
- }
-
- protected fun applyTopBarInsets(insets: Insets) {
- val actionInset = resources.getDimensionPixelSize(R.dimen.standard_quarter_padding)
- topBar.setPadding(insets.left, insets.top, insets.right + actionInset, 0)
- }
-
- @CallSuper
- open fun onStart() {
- val state = playbackModel.state
- if (state == null) {
- activity.finish()
- return
- }
-
- render(state)
- playbackModel.addListener(this)
- playerControlView.onStart()
- }
-
- @CallSuper
- open fun onStop() {
- playbackModel.removeListener(this)
- playerControlView.onStop()
- }
-
- @CallSuper
- open fun release() {
- playerPager.release()
- }
-
- override fun onPlaybackUpdate(state: PlaybackState) {
- render(state)
- }
-
- override fun onPlaybackError(error: Throwable) {
- if (error is SourceException) {
- downloadFile()
- } else {
- SnackbarUtil.show(this, R.string.common_error_unknown)
- }
- }
-
- private fun downloadFile() {
- val currentFile = playbackModel.state?.currentItemState?.file
- val storageManager = FileDataStorageManager(userAccountManager.user, context.contentResolver)
- val file = currentFile?.id?.toLong()?.let { storageManager.getFileByLocalId(it) } ?: return
-
- activity.lifecycleScope.launch(Dispatchers.IO) {
- val operation = DownloadFileOperation(userAccountManager.user, file, context)
- val client = OwnCloudClientManagerFactory.getDefaultSingleton()
- .getClientFor(userAccountManager.currentOwnCloudAccount, context)
- val result = operation.execute(client)
- if (result.isSuccess) {
- Log_OC.d(TAG, "file is successfully downloaded")
- val helper = FileDownloadHelper()
- helper.saveFile(file, operation, storageManager)
- } else {
- Log_OC.e(TAG, "cannot download file")
- withContext(Dispatchers.Main) {
- SnackbarUtil.show(this@PlayerView, R.string.player_error_source_not_found)
- }
- }
- }
- }
-
- private fun render(state: PlaybackState) {
- val currentFiles = state.currentFiles
- if (state.currentFiles.isEmpty()) {
- activity.finish()
- return
- }
-
- if (playerPager.getItems() != currentFiles) {
- playerPager.setItems(currentFiles)
- }
-
- if (state.currentItemState != null) {
- val file = state.currentItemState.file
- topBar.title = file.getNameWithoutExtension()
- playerPager.setCurrentItem(file)
- } else {
- topBar.title = ""
- }
- }
-}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/VideoPictureInPicture.kt b/app/src/main/java/com/nextcloud/client/player/ui/VideoPictureInPicture.kt
index 0946de7a6b19..05b4dbb6ad99 100644
--- a/app/src/main/java/com/nextcloud/client/player/ui/VideoPictureInPicture.kt
+++ b/app/src/main/java/com/nextcloud/client/player/ui/VideoPictureInPicture.kt
@@ -22,11 +22,7 @@ private const val ASPECT_RATIO_HEIGHT = 9
private const val MIN_ASPECT_RATIO = 0.42f
private const val MAX_ASPECT_RATIO = 2.39f
-class VideoPictureInPicture(
- private val activity: Activity,
- private val playbackModel: PlaybackModel,
- private val autoEnter: Boolean
-) {
+class VideoPictureInPicture(private val activity: Activity, private val playbackModel: PlaybackModel) {
private val defaultAspectRatio = Rational(ASPECT_RATIO_WIDTH, ASPECT_RATIO_HEIGHT)
@@ -39,7 +35,7 @@ class VideoPictureInPicture(
return PictureInPictureParams.Builder().let {
it.setAspectRatio(aspectRatio)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- it.setAutoEnterEnabled(autoEnter)
+ it.setAutoEnterEnabled(false)
}
it.setSourceRectHint(getSourceRectHint(sourceView, aspectRatio))
it.build()
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerActivity.kt b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerActivity.kt
new file mode 100644
index 000000000000..eb9b8e00888c
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerActivity.kt
@@ -0,0 +1,133 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO GmbH.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.player.ui.audio
+
+import android.content.Context
+import android.content.Intent
+import android.media.AudioManager
+import android.os.Bundle
+import androidx.activity.addCallback
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.core.view.ViewCompat
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.di.ViewModelFactory
+import com.nextcloud.ui.fileactions.FileActionsBottomSheet
+import com.nextcloud.utils.SnackbarUtil
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.ui.activity.FileActivity
+import com.owncloud.android.ui.activity.FileDisplayActivity
+import com.owncloud.android.ui.dialog.ConfirmationDialogFragment
+import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
+
+class AudioPlayerActivity :
+ FileActivity(),
+ Injectable {
+
+ companion object {
+ fun createIntent(context: Context): Intent = Intent(context, AudioPlayerActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
+ }
+ }
+
+ @Inject
+ lateinit var viewModelFactory: ViewModelFactory
+
+ private val viewModel by viewModels { viewModelFactory }
+
+ private lateinit var audioPlayerView: AudioPlayerView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+ ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, windowInsets -> windowInsets }
+
+ audioPlayerView = AudioPlayerView(this)
+ audioPlayerView.onMoreClick = { viewModel.onMoreButtonClick() }
+ setContentView(audioPlayerView)
+
+ viewModel.eventFlow
+ .flowWithLifecycle(lifecycle)
+ .onEach { handleEvent(it) }
+ .launchIn(lifecycleScope)
+
+ onBackPressedDispatcher.addCallback(this) {
+ file = file?.parentId?.let { storageManager.getFileById(it) }
+ finish()
+ }
+
+ volumeControlStream = AudioManager.STREAM_MUSIC
+ }
+
+ override fun onStart() {
+ super.onStart()
+ audioPlayerView.onStart()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ audioPlayerView.onStop()
+ }
+
+ private fun handleEvent(event: AudioPlayerScreenEvent) {
+ when (event) {
+ is AudioPlayerScreenEvent.ShowFileActions -> showFileActions(event.file, event.actionsToHide)
+
+ is AudioPlayerScreenEvent.ShowFileDetails -> showFileDetails(event.file)
+
+ is AudioPlayerScreenEvent.ShowFileExportStartedMessage -> showFileExportStartedMessage()
+
+ is AudioPlayerScreenEvent.ShowShareFileDialog -> fileOperationsHelper.sendShareFile(event.file)
+
+ is AudioPlayerScreenEvent.ShowRemoveFileDialog -> showRemoveFileDialog(event.file)
+
+ is AudioPlayerScreenEvent.LaunchOpenFileIntent -> fileOperationsHelper.openFile(event.file)
+
+ is AudioPlayerScreenEvent.LaunchStreamFileIntent -> fileOperationsHelper.streamMediaFile(event.file)
+
+ is AudioPlayerScreenEvent.ToggleFileLock -> fileOperationsHelper.toggleFileLock(
+ event.file,
+ event.shouldBeLocked
+ )
+
+ is AudioPlayerScreenEvent.AddFileToAlbum -> fileOperationsHelper.addFileToAlbum(listOf(event.file))
+ }
+ }
+
+ private fun showFileActions(file: OCFile, actionsToHide: List) {
+ FileActionsBottomSheet.newInstance(file, false, actionsToHide)
+ .setResultListener(supportFragmentManager, this) { viewModel.onFileActionChosen(file, it) }
+ .show(supportFragmentManager, "actions")
+ }
+
+ private fun showFileDetails(file: OCFile) {
+ val intent = Intent(this, FileDisplayActivity::class.java).apply {
+ action = FileDisplayActivity.ACTION_DETAILS
+ putExtra(EXTRA_FILE, file)
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ }
+ startActivity(intent)
+ finish()
+ }
+
+ private fun showFileExportStartedMessage() {
+ val message = resources.getQuantityString(R.plurals.export_start, 1, 1)
+ SnackbarUtil.show(audioPlayerView, message)
+ }
+
+ private fun showRemoveFileDialog(file: OCFile) {
+ RemoveFilesDialogFragment.newInstance(file)
+ .show(supportFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION)
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerLauncher.kt b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerLauncher.kt
new file mode 100644
index 000000000000..b0ba5aa26993
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerLauncher.kt
@@ -0,0 +1,27 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.player.ui.audio
+
+import androidx.appcompat.app.AppCompatActivity
+import com.nextcloud.client.player.media3.PlaybackModel
+import com.nextcloud.client.player.media3.PlaybackQueueLoader
+import com.nextcloud.client.player.model.file.PlaybackCollection
+import com.owncloud.android.datamodel.OCFile
+import javax.inject.Inject
+
+class AudioPlayerLauncher @Inject constructor(
+ private val playbackQueueLoader: PlaybackQueueLoader,
+ private val playbackModel: PlaybackModel
+) {
+ fun launch(activity: AppCompatActivity, file: OCFile, collection: PlaybackCollection) {
+ playbackQueueLoader.load(activity, file, collection) {
+ playbackModel.play()
+ activity.startActivity(AudioPlayerActivity.createIntent(activity))
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/pager/PlayerPager.kt b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerPager.kt
similarity index 82%
rename from app/src/main/java/com/nextcloud/client/player/ui/pager/PlayerPager.kt
rename to app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerPager.kt
index 455a84e9f03b..cec309ca6661 100644
--- a/app/src/main/java/com/nextcloud/client/player/ui/pager/PlayerPager.kt
+++ b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerPager.kt
@@ -6,7 +6,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-package com.nextcloud.client.player.ui.pager
+package com.nextcloud.client.player.ui.audio
import android.annotation.SuppressLint
import android.content.Context
@@ -14,7 +14,6 @@ import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.widget.LinearLayout
-import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.widget.ViewPager2
@@ -28,16 +27,16 @@ private const val NO_POSITION = -1
private const val FIRST_ENTITY_POSITION = 1
private const val OFFSCREEN_PAGE_LIMIT = 1
-class PlayerPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+class AudioPlayerPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs),
MediaNavigator {
@SuppressLint("WrongConstant")
private val viewPager = ViewPager2(context).apply {
- id = R.id.player_view_pager
+ id = R.id.player_audio_pager
offscreenPageLimit = OFFSCREEN_PAGE_LIMIT
}
- private lateinit var adapter: PlayerPagerAdapter
+ private lateinit var adapter: AudioPlayerPagerAdapter
private var shift = NO_SHIFT
private var restoredShift = NO_SHIFT
@@ -63,28 +62,23 @@ class PlayerPager @JvmOverloads constructor(context: Context, attrs: AttributeSe
addView(viewPager, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
}
- fun initialize(fragmentManager: FragmentManager, lifecycle: Lifecycle, createFragment: (PlaybackFile) -> Fragment) {
- adapter = PlayerPagerAdapter(fragmentManager, lifecycle, createFragment)
+ fun initialize(fragmentManager: FragmentManager, lifecycle: Lifecycle) {
+ adapter = AudioPlayerPagerAdapter(fragmentManager, lifecycle)
viewPager.adapter = adapter
}
override fun onSaveInstanceState(): Parcelable {
- val state = PlayerPagerState(super.onSaveInstanceState())
+ val state = AudioPlayerPagerState(super.onSaveInstanceState())
state.shiftedPosition = shift
return state
}
override fun onRestoreInstanceState(state: Parcelable?) {
- val restoredState = state as PlayerPagerState
+ val restoredState = state as AudioPlayerPagerState
super.onRestoreInstanceState(restoredState.superState)
restoredShift = restoredState.shiftedPosition
}
- fun release() {
- viewPager.unregisterOnPageChangeCallback(onPageChangeCallback)
- viewPager.adapter = null
- }
-
fun getItems(): List = adapter.getEntities()
fun setItems(items: List) {
@@ -137,7 +131,7 @@ class PlayerPager @JvmOverloads constructor(context: Context, attrs: AttributeSe
return items.rotate(shift)
}
- class PlayerPagerState : BaseSavedState {
+ class AudioPlayerPagerState : BaseSavedState {
var shiftedPosition: Int = 0
constructor(superState: Parcelable?) : super(superState)
@@ -153,11 +147,11 @@ class PlayerPager @JvmOverloads constructor(context: Context, attrs: AttributeSe
companion object {
@JvmField
- val CREATOR = object : Parcelable.Creator {
+ val CREATOR = object : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): PlayerPagerState = PlayerPagerState(parcel)
+ override fun createFromParcel(parcel: Parcel): AudioPlayerPagerState = AudioPlayerPagerState(parcel)
- override fun newArray(size: Int): Array = arrayOfNulls(size)
+ override fun newArray(size: Int): Array = arrayOfNulls(size)
}
}
}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/pager/PlayerPagerAdapter.kt b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerPagerAdapter.kt
similarity index 83%
rename from app/src/main/java/com/nextcloud/client/player/ui/pager/PlayerPagerAdapter.kt
rename to app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerPagerAdapter.kt
index cdac8a4f7624..1689e83cf381 100644
--- a/app/src/main/java/com/nextcloud/client/player/ui/pager/PlayerPagerAdapter.kt
+++ b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerPagerAdapter.kt
@@ -5,7 +5,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-package com.nextcloud.client.player.ui.pager
+package com.nextcloud.client.player.ui.audio
import android.annotation.SuppressLint
import androidx.fragment.app.Fragment
@@ -16,13 +16,10 @@ import com.nextcloud.client.player.model.file.PlaybackFile
/**
* Pads the item list with a copy of the last item at the front and a copy of the first item at the end, so that
- * [PlayerPager] can wrap around and give the impression of an endless pager.
+ * [AudioPlayerPager] can wrap around and give the impression of an endless pager.
*/
-class PlayerPagerAdapter(
- fragmentManager: FragmentManager,
- lifecycle: Lifecycle,
- private val fragmentFactory: (PlaybackFile) -> Fragment
-) : FragmentStateAdapter(fragmentManager, lifecycle) {
+class AudioPlayerPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
+ FragmentStateAdapter(fragmentManager, lifecycle) {
private var paddedEntities = mutableListOf()
@@ -45,7 +42,7 @@ class PlayerPagerAdapter(
override fun getItemCount(): Int = paddedEntities.size
- override fun createFragment(position: Int): Fragment = fragmentFactory(paddedEntities[position])
+ override fun createFragment(position: Int): Fragment = AudioFileFragment.createInstance(paddedEntities[position])
override fun getItemId(position: Int): Long = itemIdAt(position)
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerScreenEvent.kt b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerScreenEvent.kt
new file mode 100644
index 000000000000..d11f95c5fdcd
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerScreenEvent.kt
@@ -0,0 +1,31 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO GmbH.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.player.ui.audio
+
+import com.owncloud.android.datamodel.OCFile
+
+sealed interface AudioPlayerScreenEvent {
+
+ data class ShowFileActions(val file: OCFile, val actionsToHide: List) : AudioPlayerScreenEvent
+
+ data class ShowFileDetails(val file: OCFile) : AudioPlayerScreenEvent
+
+ data object ShowFileExportStartedMessage : AudioPlayerScreenEvent
+
+ data class ShowShareFileDialog(val file: OCFile) : AudioPlayerScreenEvent
+
+ data class ShowRemoveFileDialog(val file: OCFile) : AudioPlayerScreenEvent
+
+ data class LaunchOpenFileIntent(val file: OCFile) : AudioPlayerScreenEvent
+
+ data class LaunchStreamFileIntent(val file: OCFile) : AudioPlayerScreenEvent
+
+ data class ToggleFileLock(val file: OCFile, val shouldBeLocked: Boolean) : AudioPlayerScreenEvent
+
+ data class AddFileToAlbum(val file: OCFile) : AudioPlayerScreenEvent
+}
diff --git a/app/src/main/java/com/nextcloud/client/player/util/WindowWrapper.kt b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerSystemBars.kt
similarity index 80%
rename from app/src/main/java/com/nextcloud/client/player/util/WindowWrapper.kt
rename to app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerSystemBars.kt
index 0407bd97974f..9b6b00ab1314 100644
--- a/app/src/main/java/com/nextcloud/client/player/util/WindowWrapper.kt
+++ b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerSystemBars.kt
@@ -5,7 +5,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-package com.nextcloud.client.player.util
+package com.nextcloud.client.player.ui.audio
import android.view.Gravity
import android.view.View
@@ -21,41 +21,35 @@ import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updateLayoutParams
import com.owncloud.android.R
private const val LUMINANCE_THRESHOLD = 0.5
-class WindowWrapper(private val window: Window) {
+class AudioPlayerSystemBars(private val window: Window) {
private val context = window.context
private val insetsController = WindowCompat.getInsetsController(window, window.decorView)
- fun showSystemBars() {
+ fun show() {
insetsController.show(WindowInsetsCompat.Type.systemBars())
}
- fun hideSystemBars() {
- insetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
- insetsController.hide(WindowInsetsCompat.Type.systemBars())
- }
-
- fun setupStatusBar(@ColorRes backgroundColorRes: Int) {
+ fun setStatusBarBackground(@ColorRes backgroundColorRes: Int) {
val backgroundColor = ContextCompat.getColor(context, backgroundColorRes)
insetsController.isAppearanceLightStatusBars = isLightColor(backgroundColor)
drawSystemBarBackground(
- R.id.player_status_bar_background,
+ R.id.player_audio_status_bar_background,
Gravity.TOP,
backgroundColor,
systemBarInsets(WindowInsetsCompat.Type.statusBars()).top
)
}
- fun setupNavigationBar(@ColorRes backgroundColorRes: Int) {
+ fun setNavigationBarBackground(@ColorRes backgroundColorRes: Int) {
val backgroundColor = ContextCompat.getColor(context, backgroundColorRes)
insetsController.isAppearanceLightNavigationBars = isLightColor(backgroundColor)
drawSystemBarBackground(
- R.id.player_navigation_bar_background,
+ R.id.player_audio_navigation_bar_background,
Gravity.BOTTOM,
backgroundColor,
systemBarInsets(WindowInsetsCompat.Type.navigationBars()).bottom
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerView.kt b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerView.kt
index 13f27a139a55..b9efb45e16f9 100644
--- a/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerView.kt
+++ b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerView.kt
@@ -9,35 +9,151 @@ package com.nextcloud.client.player.ui.audio
import android.content.Context
import android.view.WindowInsets
+import android.widget.LinearLayout
+import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type
-import androidx.fragment.app.Fragment
-import com.nextcloud.client.player.model.file.PlaybackFile
-import com.nextcloud.client.player.ui.PlayerView
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.appbar.MaterialToolbar
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.jobs.download.FileDownloadHelper
+import com.nextcloud.client.player.media3.PlaybackModel
+import com.nextcloud.client.player.model.error.SourceException
+import com.nextcloud.client.player.model.state.PlaybackState
+import com.nextcloud.client.player.ui.control.PlayerControlView
+import com.nextcloud.utils.SnackbarUtil
import com.owncloud.android.R
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.operations.DownloadFileOperation
+import dagger.android.HasAndroidInjector
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
-class AudioPlayerView(context: Context) : PlayerView(context) {
+class AudioPlayerView(context: Context) :
+ LinearLayout(context),
+ PlaybackModel.Listener {
- override val layoutRes get() = R.layout.player_audio_view
+ companion object {
+ private const val TAG = "AudioPlayerView"
+ }
+
+ private val activity = context as AppCompatActivity
+
+ @Inject
+ lateinit var playbackModel: PlaybackModel
+
+ @Inject
+ lateinit var userAccountManager: UserAccountManager
+
+ private val systemBars by lazy { AudioPlayerSystemBars(activity.window) }
+
+ private val topBar: MaterialToolbar by lazy { findViewById(R.id.topBar) }
+ private val playerPager: AudioPlayerPager by lazy { findViewById(R.id.playerPager) }
+ private val playerControlView: PlayerControlView by lazy { findViewById(R.id.playerControlView) }
- override val createFragment: (PlaybackFile) -> Fragment get() = { AudioFileFragment.createInstance(it) }
+ var onMoreClick: (() -> Unit)? = null
- override fun onStart() {
- super.onStart()
- windowWrapper.showSystemBars()
+ init {
+ inflate(activity, R.layout.player_audio_view, this)
+ (activity.applicationContext as HasAndroidInjector).androidInjector().inject(this)
+ playerPager.initialize(activity.supportFragmentManager, activity.lifecycle)
+ playerPager.onItemSelected = { playbackModel.switchToFile(it) }
+ playerControlView.navigator = playerPager
+ topBar.setNavigationOnClickListener { activity.onBackPressedDispatcher.onBackPressed() }
+ topBar.setOnMenuItemClickListener { menuItem ->
+ val isMore = menuItem.itemId == R.id.action_more
+ if (isMore) {
+ onMoreClick?.invoke()
+ }
+ isMore
+ }
}
override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
val windowInsetsCompat = WindowInsetsCompat.toWindowInsetsCompat(windowInsets)
val insets = windowInsetsCompat.getInsets(Type.systemBars() or Type.displayCutout())
- applyTopBarInsets(insets)
+ val actionInset = resources.getDimensionPixelSize(R.dimen.standard_quarter_padding)
+ topBar.setPadding(insets.left, insets.top, insets.right + actionInset, 0)
playerPager.setPadding(insets.left, 0, insets.right, 0)
playerControlView.setPadding(insets.left, 0, insets.right, insets.bottom)
- windowWrapper.setupStatusBar(R.color.player_background_color)
- windowWrapper.setupNavigationBar(R.color.player_background_color)
+ systemBars.setStatusBarBackground(R.color.player_background_color)
+ systemBars.setNavigationBarBackground(R.color.player_background_color)
return WindowInsetsCompat.CONSUMED.toWindowInsets()
}
+
+ fun onStart() {
+ val state = playbackModel.state
+ if (state == null) {
+ activity.finish()
+ return
+ }
+
+ systemBars.show()
+ render(state)
+ playbackModel.addListener(this)
+ playerControlView.onStart()
+ }
+
+ fun onStop() {
+ playbackModel.removeListener(this)
+ playerControlView.onStop()
+ }
+
+ override fun onPlaybackUpdate(state: PlaybackState) {
+ render(state)
+ }
+
+ override fun onPlaybackError(error: Throwable) {
+ if (error is SourceException) {
+ downloadFile()
+ } else {
+ SnackbarUtil.show(this, R.string.common_error_unknown)
+ }
+ }
+
+ private fun downloadFile() {
+ val currentFile = playbackModel.state?.currentItemState?.file
+ val storageManager = FileDataStorageManager(userAccountManager.user, context.contentResolver)
+ val file = currentFile?.id?.toLong()?.let { storageManager.getFileByLocalId(it) } ?: return
+
+ activity.lifecycleScope.launch(Dispatchers.IO) {
+ val operation = DownloadFileOperation(userAccountManager.user, file, context)
+ val client = OwnCloudClientManagerFactory.getDefaultSingleton()
+ .getClientFor(userAccountManager.currentOwnCloudAccount, context)
+ val result = operation.execute(client)
+ if (result.isSuccess) {
+ Log_OC.d(TAG, "file is successfully downloaded")
+ val helper = FileDownloadHelper()
+ helper.saveFile(file, operation, storageManager)
+ } else {
+ Log_OC.e(TAG, "cannot download file")
+ withContext(Dispatchers.Main) {
+ SnackbarUtil.show(this@AudioPlayerView, R.string.player_error_source_not_found)
+ }
+ }
+ }
+ }
+
+ private fun render(state: PlaybackState) {
+ val currentFiles = state.currentFiles
+ if (currentFiles.isEmpty()) {
+ activity.finish()
+ return
+ }
+
+ if (playerPager.getItems() != currentFiles) {
+ playerPager.setItems(currentFiles)
+ }
+
+ val file = state.currentItemState?.file
+ topBar.title = file?.getNameWithoutExtension().orEmpty()
+ file?.let(playerPager::setCurrentItem)
+ }
}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/PlayerViewModel.kt b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerViewModel.kt
similarity index 71%
rename from app/src/main/java/com/nextcloud/client/player/ui/PlayerViewModel.kt
rename to app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerViewModel.kt
index 640a32f02e21..231ad3123f9f 100644
--- a/app/src/main/java/com/nextcloud/client/player/ui/PlayerViewModel.kt
+++ b/app/src/main/java/com/nextcloud/client/player/ui/audio/AudioPlayerViewModel.kt
@@ -5,7 +5,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-package com.nextcloud.client.player.ui
+package com.nextcloud.client.player.ui.audio
import androidx.core.text.isDigitsOnly
import androidx.lifecycle.ViewModel
@@ -28,7 +28,7 @@ import kotlinx.coroutines.withContext
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
-class PlayerViewModel @Inject constructor(
+class AudioPlayerViewModel @Inject constructor(
private val playbackModel: PlaybackModel,
private val storageManager: FileDataStorageManager,
private val userAccountManager: UserAccountManager,
@@ -36,42 +36,42 @@ class PlayerViewModel @Inject constructor(
private val logger: Logger
) : ViewModel() {
- private val eventChannel = Channel(Channel.BUFFERED)
- val eventFlow: Flow = eventChannel.receiveAsFlow()
+ private val eventChannel = Channel(Channel.BUFFERED)
+ val eventFlow: Flow = eventChannel.receiveAsFlow()
fun onMoreButtonClick() {
viewModelScope.launch {
val file = getCurrentOCFile() ?: return@launch
val actionsToHide = FileAction.getFilePreviewActions(file)
- eventChannel.trySend(PlayerScreenEvent.ShowFileActions(file, actionsToHide))
+ eventChannel.trySend(AudioPlayerScreenEvent.ShowFileActions(file, actionsToHide))
}
}
fun onFileActionChosen(file: OCFile, actionId: Int) {
when (actionId) {
- R.id.action_see_details -> eventChannel.trySend(PlayerScreenEvent.ShowFileDetails(file))
+ R.id.action_see_details -> eventChannel.trySend(AudioPlayerScreenEvent.ShowFileDetails(file))
R.id.action_download_file -> startFileDownloading(file)
R.id.action_export_file -> startFileExport(file)
- R.id.action_send_share_file -> eventChannel.trySend(PlayerScreenEvent.ShowShareFileDialog(file))
+ R.id.action_send_share_file -> eventChannel.trySend(AudioPlayerScreenEvent.ShowShareFileDialog(file))
- R.id.action_remove_file -> eventChannel.trySend(PlayerScreenEvent.ShowRemoveFileDialog(file))
+ R.id.action_remove_file -> eventChannel.trySend(AudioPlayerScreenEvent.ShowRemoveFileDialog(file))
R.id.action_open_file_with -> onOpenFileWithClick(file)
R.id.action_stream_media -> onStreamFileClick(file)
R.id.action_lock_file -> eventChannel.trySend(
- PlayerScreenEvent.ToggleFileLock(file, shouldBeLocked = true)
+ AudioPlayerScreenEvent.ToggleFileLock(file, shouldBeLocked = true)
)
R.id.action_unlock_file -> eventChannel.trySend(
- PlayerScreenEvent.ToggleFileLock(file, shouldBeLocked = false)
+ AudioPlayerScreenEvent.ToggleFileLock(file, shouldBeLocked = false)
)
- R.id.action_add_to_album -> eventChannel.trySend(PlayerScreenEvent.AddFileToAlbum(file))
+ R.id.action_add_to_album -> eventChannel.trySend(AudioPlayerScreenEvent.AddFileToAlbum(file))
}
}
@@ -87,7 +87,7 @@ class PlayerViewModel @Inject constructor(
storageManager.getFileByLocalId(localId)
}.getOrElse {
if (it is CancellationException) throw it
- logger.e(PlayerViewModel::class.java.simpleName, "Failed to get file by localId: $localId", it)
+ logger.e(AudioPlayerViewModel::class.java.simpleName, "Failed to get file by localId: $localId", it)
null
}
}
@@ -99,16 +99,16 @@ class PlayerViewModel @Inject constructor(
private fun startFileExport(file: OCFile) {
backgroundJobManager.startImmediateFilesExportJob(listOf(file))
- eventChannel.trySend(PlayerScreenEvent.ShowFileExportStartedMessage)
+ eventChannel.trySend(AudioPlayerScreenEvent.ShowFileExportStartedMessage)
}
private fun onOpenFileWithClick(file: OCFile) {
playbackModel.pause()
- eventChannel.trySend(PlayerScreenEvent.LaunchOpenFileIntent(file))
+ eventChannel.trySend(AudioPlayerScreenEvent.LaunchOpenFileIntent(file))
}
private fun onStreamFileClick(file: OCFile) {
playbackModel.pause()
- eventChannel.trySend(PlayerScreenEvent.LaunchStreamFileIntent(file))
+ eventChannel.trySend(AudioPlayerScreenEvent.LaunchStreamFileIntent(file))
}
}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/video/VideoFileFragment.kt b/app/src/main/java/com/nextcloud/client/player/ui/video/VideoFileFragment.kt
deleted file mode 100644
index ee2860be96de..000000000000
--- a/app/src/main/java/com/nextcloud/client/player/ui/video/VideoFileFragment.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2025 STRATO GmbH.
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-package com.nextcloud.client.player.ui.video
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.core.view.isVisible
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
-import com.nextcloud.client.player.media3.PlaybackModel
-import com.nextcloud.client.player.model.PlayerThumbnailLoader
-import com.nextcloud.client.player.model.file.PlaybackFile
-import com.nextcloud.client.player.model.state.PlaybackState
-import com.nextcloud.client.player.model.state.VideoSize
-import com.nextcloud.client.player.util.PlayerUtil.applyVideoSize
-import com.nextcloud.client.player.util.PlayerUtil.getPlaybackFile
-import com.nextcloud.client.player.util.PlayerUtil.ownsPlayback
-import com.nextcloud.client.player.util.PlayerUtil.putPlaybackFile
-import com.owncloud.android.R
-import com.owncloud.android.databinding.PlayerVideoFileFragmentBinding
-import dagger.android.support.AndroidSupportInjection
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-class VideoFileFragment :
- Fragment(),
- PlaybackModel.Listener {
-
- companion object {
- private const val ARGUMENT_FILE = "ARGUMENT_FILE"
- private const val SURFACE_ALPHA_VISIBLE = 1f
- private const val SURFACE_ALPHA_HIDDEN = 0f
-
- fun createInstance(file: PlaybackFile) = VideoFileFragment().apply {
- arguments = Bundle().apply { putPlaybackFile(ARGUMENT_FILE, file) }
- }
- }
-
- @Inject
- lateinit var playerModel: PlaybackModel
-
- @Inject
- lateinit var playerThumbnailLoader: PlayerThumbnailLoader
-
- private var _binding: PlayerVideoFileFragmentBinding? = null
- private val binding get() = checkNotNull(_binding) { "Binding accessed outside of the view lifecycle" }
-
- private var renderedVideoSize: VideoSize? = null
-
- private val file by lazy {
- requireNotNull(arguments.getPlaybackFile(ARGUMENT_FILE)) {
- "VideoFileFragment requires a $ARGUMENT_FILE argument"
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- AndroidSupportInjection.inject(this)
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
- PlayerVideoFileFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- loadFileThumbnail()
- binding.root.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- render(playerModel.state)
- }
- }
-
- override fun onStart() {
- super.onStart()
- render(playerModel.state)
- playerModel.addListener(this)
- }
-
- override fun onResume() {
- super.onResume()
- render(playerModel.state)
- }
-
- override fun onStop() {
- playerModel.removeListener(this)
- super.onStop()
- }
-
- override fun onDestroyView() {
- _binding?.let { playerModel.clearVideoSurfaceView(it.surfaceView) }
- _binding = null
- super.onDestroyView()
- }
-
- override fun onPlaybackUpdate(state: PlaybackState) {
- render(state)
- }
-
- private fun loadFileThumbnail() = viewLifecycleOwner.lifecycleScope.launch {
- val context = context ?: return@launch
- val thumbnailSize = context.resources.getDimensionPixelSize(R.dimen.player_album_cover_size)
- val thumbnail = playerThumbnailLoader.await(file, thumbnailSize, thumbnailSize) ?: return@launch
- _binding?.thumbnail?.setImageBitmap(thumbnail)
- }
-
- private fun render(state: PlaybackState?) {
- val binding = _binding ?: return
- val currentItemState = state?.currentItemState
-
- if (currentItemState?.file == file) {
- showVideo(currentItemState.videoSize)
- return
- }
-
- renderedVideoSize = null
- binding.surfaceView.isVisible = false
- if (currentItemState == null) {
- playerModel.setVideoSurfaceView(null)
- }
- }
-
- private fun showVideo(videoSize: VideoSize?) {
- if (ownsPlayback(binding.surfaceView)) {
- playerModel.setVideoSurfaceView(binding.surfaceView)
- }
-
- val size = videoSize ?: renderedVideoSize
- renderedVideoSize = size
-
- binding.surfaceView.isVisible = true
- binding.surfaceView.alpha = if (size == null) SURFACE_ALPHA_HIDDEN else SURFACE_ALPHA_VISIBLE
-
- if (size == null) return
-
- binding.surfaceView.applyVideoSize(size)
- }
-}
diff --git a/app/src/main/java/com/nextcloud/client/player/ui/video/VideoPlayerView.kt b/app/src/main/java/com/nextcloud/client/player/ui/video/VideoPlayerView.kt
deleted file mode 100644
index 5bf1f4c34ee4..000000000000
--- a/app/src/main/java/com/nextcloud/client/player/ui/video/VideoPlayerView.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2025 STRATO GmbH.
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-package com.nextcloud.client.player.ui.video
-
-import android.content.Context
-import android.view.WindowInsets
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.WindowInsetsCompat.Type
-import androidx.fragment.app.Fragment
-import com.nextcloud.client.player.model.file.PlaybackFile
-import com.nextcloud.client.player.ui.PlayerView
-import com.owncloud.android.R
-import kotlinx.coroutines.Job
-
-class VideoPlayerView(context: Context) : PlayerView(context) {
- override val layoutRes get() = R.layout.player_video_view
-
- override val createFragment: (PlaybackFile) -> Fragment get() = { VideoFileFragment.createInstance(it) }
-
- private var hideControlsTimerJob: Job? = null
-
- init {
- topBar.setBackgroundResource(R.drawable.player_video_top_scrim)
- }
-
- override fun onStop() {
- super.onStop()
- cancelHideControlsTimer()
- playbackModel.setVideoSurfaceView(null)
- }
-
- override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
- val windowInsetsCompat = WindowInsetsCompat.toWindowInsetsCompat(windowInsets)
- val insets = windowInsetsCompat.getInsets(Type.systemBars() or Type.displayCutout())
-
- applyTopBarInsets(insets)
- playerControlView.setPadding(insets.left, 0, insets.right, insets.bottom)
-
- windowWrapper.setupStatusBar(R.color.player_video_toolbar_background_color)
- windowWrapper.setupNavigationBar(R.color.player_video_control_view_background_color)
-
- return WindowInsetsCompat.CONSUMED.toWindowInsets()
- }
-
- private fun cancelHideControlsTimer() {
- hideControlsTimerJob?.cancel()
- hideControlsTimerJob = null
- }
-}
diff --git a/app/src/main/java/com/nextcloud/client/player/util/PlayerUtil.kt b/app/src/main/java/com/nextcloud/client/player/util/PlayerUtil.kt
index d7dbaa93864a..6f95188370eb 100644
--- a/app/src/main/java/com/nextcloud/client/player/util/PlayerUtil.kt
+++ b/app/src/main/java/com/nextcloud/client/player/util/PlayerUtil.kt
@@ -16,10 +16,10 @@ import android.database.ContentObserver
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
-import android.util.LruCache
import android.os.Handler
import android.os.Looper
import android.os.Process
+import android.util.LruCache
import android.view.SurfaceView
import android.view.View
import android.view.ViewGroup
diff --git a/app/src/main/java/com/nextcloud/client/systembars/SystemBarBackgroundCallbacks.kt b/app/src/main/java/com/nextcloud/client/systembars/SystemBarBackgroundCallbacks.kt
index a0ebd8ee76ab..680810b6f941 100644
--- a/app/src/main/java/com/nextcloud/client/systembars/SystemBarBackgroundCallbacks.kt
+++ b/app/src/main/java/com/nextcloud/client/systembars/SystemBarBackgroundCallbacks.kt
@@ -13,10 +13,12 @@ import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import com.nextcloud.client.onboarding.FirstRunActivity
+import com.nextcloud.client.player.ui.audio.AudioPlayerActivity
import com.nmc.android.ui.LauncherActivity
import com.owncloud.android.R
import com.owncloud.android.authentication.AuthenticatorActivity
import com.owncloud.android.ui.activity.DrawerActivity
+import com.owncloud.android.ui.preview.PreviewImageActivity
import com.owncloud.android.utils.theme.ViewThemeUtils
import dynamiccolor.MaterialDynamicColors
import javax.inject.Provider
@@ -29,7 +31,9 @@ class SystemBarBackgroundCallbacks(private val viewThemeUtilsProvider: Provider<
private val excludedActivities = listOf(
LauncherActivity::class,
AuthenticatorActivity::class,
- FirstRunActivity::class
+ FirstRunActivity::class,
+ AudioPlayerActivity::class,
+ PreviewImageActivity::class
)
private fun isExcluded(activity: Activity): Boolean = excludedActivities.any { it.isInstance(activity) }
diff --git a/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt
index 36195f78c2d3..1d7199dd6844 100644
--- a/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt
+++ b/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt
@@ -16,6 +16,9 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
import com.nextcloud.ui.behavior.OnScrollBehavior
import com.owncloud.android.lib.common.utils.Log_OC
@@ -103,3 +106,12 @@ fun T.slideHideBottomBehavior(visible: Boolean) {
}
}
}
+
+fun View.addPaddingForNavBar() {
+ addOnLayoutChangeListener { view, _, _, _, _, _, _, _, _ ->
+ val insets = ViewCompat.getRootWindowInsets(view)
+ ?.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.navigationBars())
+ ?: return@addOnLayoutChangeListener
+ view.updatePadding(left = insets.left, right = insets.right, bottom = insets.bottom)
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/utils/extensions/WindowExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/WindowExtensions.kt
index 4f9ba65373fd..d062e9b3d127 100644
--- a/app/src/main/java/com/nextcloud/utils/extensions/WindowExtensions.kt
+++ b/app/src/main/java/com/nextcloud/utils/extensions/WindowExtensions.kt
@@ -7,12 +7,7 @@
package com.nextcloud.utils.extensions
-import android.view.View
-import android.view.Window
import androidx.appcompat.app.ActionBar
-import androidx.core.view.WindowCompat
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.WindowInsetsControllerCompat
fun ActionBar.toggle(show: Boolean) {
if (show) {
@@ -21,27 +16,3 @@ fun ActionBar.toggle(show: Boolean) {
hide()
}
}
-
-fun Window.showSystemBar(show: Boolean, view: View) {
- WindowCompat.getInsetsController(this, view).run {
- val types = WindowInsetsCompat.Type.systemBars()
- if (show) {
- show(types)
- } else {
- systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
- hide(types)
- }
- }
-}
-
-fun Window.showNavigationBar(show: Boolean, view: View) {
- WindowCompat.getInsetsController(this, view).run {
- val types = WindowInsetsCompat.Type.navigationBars()
- if (show) {
- show(types)
- } else {
- systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
- hide(types)
- }
- }
-}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
index be00f4f53900..494b1a8ae6d4 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
@@ -37,7 +37,7 @@
import com.nextcloud.client.jobs.upload.FileUploadHelper;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.network.NetworkChangeListener;
-import com.nextcloud.client.player.ui.PlayerActivity;
+import com.nextcloud.client.player.ui.audio.AudioPlayerActivity;
import com.nextcloud.client.utils.IntentUtil;
import com.nextcloud.utils.EditorUtils;
import com.nextcloud.utils.SnackbarUtil;
@@ -255,7 +255,7 @@ public void networkAndServerConnectionListener(boolean isNetworkAndServerAvailab
refreshList();
}
} else {
- if (this instanceof PlayerActivity) {
+ if (this instanceof AudioPlayerActivity) {
hideInfoBox();
} else {
showInfoBox(R.string.offline_mode);
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
index 436eb2373016..858b4700cb69 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
@@ -73,7 +73,7 @@ import com.nextcloud.client.jobs.upload.FileUploadHelper
import com.nextcloud.client.jobs.upload.FileUploadWorker
import com.nextcloud.client.network.ClientFactory.CreationException
import com.nextcloud.client.player.model.file.toPlaybackCollection
-import com.nextcloud.client.player.ui.PlayerLauncher
+import com.nextcloud.client.player.ui.audio.AudioPlayerLauncher
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.client.utils.IntentUtil
import com.nextcloud.model.OCUploadLocalPathData
@@ -242,7 +242,7 @@ class FileDisplayActivity :
private var listFragmentJustCreated = false
@Inject
- lateinit var playerLauncher: PlayerLauncher
+ lateinit var audioPlayerLauncher: AudioPlayerLauncher
@Inject
lateinit var localBroadcastManager: LocalBroadcastManager
@@ -882,8 +882,7 @@ class FileDisplayActivity :
}
}
- fun canMediaPreviewed(file: OCFile?): Boolean =
- file != null && (MimeTypeUtil.isAudio(file) || MimeTypeUtil.isVideo(file))
+ fun canPreviewInAudioPlayer(file: OCFile?): Boolean = file != null && MimeTypeUtil.isAudio(file)
private fun tryStartWaitingPreview(success: Boolean): Boolean {
if (!success) return false
@@ -897,8 +896,8 @@ class FileDisplayActivity :
true
}
- canMediaPreviewed(file) -> {
- startMediaPreview(file, true, true)
+ canPreviewInAudioPlayer(file) -> {
+ startAudioPreview(file, true, true)
true
}
@@ -2096,9 +2095,9 @@ class FileDisplayActivity :
} else if (PreviewTextFileFragment.canBePreviewed(file)) {
setFabVisible?.onComplete(false)
startTextPreview(file, false)
- } else if (canMediaPreviewed(file)) {
+ } else if (canPreviewInAudioPlayer(file)) {
setFabVisible?.onComplete(false)
- startMediaPreview(file, true, false)
+ startAudioPreview(file, true, false)
} else {
fileOperationsHelper.openFile(file)
}
@@ -2741,28 +2740,28 @@ class FileDisplayActivity :
}
/**
- * Starts the preview of a media [OCFile], synchronizing it first when it is not available yet.
+ * Starts the preview of an audio [OCFile], synchronizing it first when it is not available yet.
*/
- fun startMediaPreview(file: OCFile, showPreview: Boolean, streamMedia: Boolean) {
+ fun startAudioPreview(file: OCFile, showPreview: Boolean, streamMedia: Boolean) {
val user = getUser()
if (!user.isPresent) {
return // not reachable under normal conditions
}
if ((showPreview && file.isDown && !file.isDownloading) || streamMedia) {
- startMediaActivity(file)
+ startAudioPlayer(file)
} else {
val previewIntent = Intent()
previewIntent.putExtra(EXTRA_FILE, file)
- previewIntent.putExtra(MEDIA_PREVIEW, true)
+ previewIntent.putExtra(AUDIO_PREVIEW, true)
val fileOperationsHelper =
FileOperationsHelper(this, userAccountManager, connectivityService, editorUtils)
fileOperationsHelper.startSyncForFileAndIntent(file, previewIntent)
}
}
- private fun startMediaActivity(file: OCFile) {
+ private fun startAudioPlayer(file: OCFile) {
val collection = listOfFilesFragment?.currentSearchType.toPlaybackCollection()
- playerLauncher.launch(this, file, collection)
+ audioPlayerLauncher.launch(this, file, collection)
}
fun configureToolbarForPreview(file: OCFile?) {
@@ -2959,8 +2958,8 @@ class FileDisplayActivity :
if (event.intent.getBooleanExtra(TEXT_PREVIEW, false)) {
startTextPreview(file, true)
- } else if (event.intent.getBooleanExtra(MEDIA_PREVIEW, false)) {
- startMediaPreview(file, true, true)
+ } else if (event.intent.getBooleanExtra(AUDIO_PREVIEW, false)) {
+ startAudioPreview(file, true, true)
} else if (bundle.containsKey(PreviewImageActivity.EXTRA_VIRTUAL_TYPE)) {
val virtualType = bundle.get(PreviewImageActivity.EXTRA_VIRTUAL_TYPE) as VirtualFolderType?
startImagePreview(
@@ -3345,7 +3344,7 @@ class FileDisplayActivity :
const val TAG_LIST_OF_FILES: String = "LIST_OF_FILES"
const val TEXT_PREVIEW: String = "TEXT_PREVIEW"
- const val MEDIA_PREVIEW: String = "MEDIA_PREVIEW"
+ const val AUDIO_PREVIEW: String = "AUDIO_PREVIEW"
const val KEY_IS_SEARCH_OPEN: String = "IS_SEARCH_OPEN"
const val KEY_SEARCH_QUERY: String = "SEARCH_QUERY"
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
index 202fb2157656..e2e517b34599 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
@@ -1238,9 +1238,9 @@ private void handlePendingDownloadFile(OCFile file) {
boolean webViewAvailable = WebViewUtil.available(context);
- if (!file.isEncrypted() && mContainerActivity instanceof FileDisplayActivity fda && fda.canMediaPreviewed(file)) {
+ if (!file.isEncrypted() && mContainerActivity instanceof FileDisplayActivity fda && fda.canPreviewInAudioPlayer(file)) {
setFabVisible(false);
- fda.startMediaPreview(file, true, true);
+ fda.startAudioPreview(file, true, true);
} else if (webViewAvailable && editorUtils.getEditor(accountManager.getUser(), file.getMimeType()) != null && !file.isEncrypted()) {
TextEditorWebView.Companion.startTextEditor(file, context);
} else if (supportsDirectEditing(file, webViewAvailable)) {
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumItemsFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumItemsFragment.kt
index f779a7d78e36..d620e785abcb 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumItemsFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumItemsFragment.kt
@@ -564,13 +564,13 @@ class AlbumItemsFragment :
MimeTypeUtil.isVideo(file) ->
activity.startImagePreview(file, true, VirtualFolderType.ALBUM)
- file.isDown && activity.canMediaPreviewed(file) ->
- activity.startMediaPreview(file, showPreview = true, streamMedia = false)
+ file.isDown && activity.canPreviewInAudioPlayer(file) ->
+ activity.startAudioPreview(file, showPreview = true, streamMedia = false)
file.isDown -> containerActivity?.fileOperationsHelper?.openFile(file)
- activity.canMediaPreviewed(file) && !file.isEncrypted ->
- activity.startMediaPreview(file, showPreview = true, streamMedia = true)
+ activity.canPreviewInAudioPlayer(file) && !file.isEncrypted ->
+ activity.startAudioPreview(file, showPreview = true, streamMedia = true)
else -> Log_OC.d(TAG, "Couldn't handle item click")
}
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt
index af1cd78fc7a5..76b54dbdc0a5 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt
@@ -12,6 +12,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
+import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import androidx.activity.OnBackPressedCallback
@@ -102,14 +103,16 @@ class PreviewImageActivity :
private val downloadFinishReceiver = DownloadFinishReceiver()
private val windowInsetsController: WindowInsetsControllerCompat by lazy {
- WindowCompat.getInsetsController(window, window.decorView)
+ WindowCompat.getInsetsController(window, window.decorView).apply {
+ systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ }
}
private var isDownloadWorkStarted = false
private var screenState = PreviewImageActivityState.Idle
- private val pictureInPicture by lazy { VideoPictureInPicture(this, playbackModel, autoEnter = false) }
- private var wasSystemUiVisibleBeforePictureInPicture = true
+ private val pictureInPicture by lazy { VideoPictureInPicture(this, playbackModel) }
+ private var wasActionBarVisibleBeforePictureInPicture = true
private var keepPlaybackOnFinish = false
@Inject
@@ -123,7 +126,7 @@ class PreviewImageActivity :
if (savedInstanceState != null &&
!savedInstanceState.getBoolean(
- KEY_SYSTEM_VISIBLE,
+ KEY_ACTION_BAR_VISIBLE,
true
) &&
supportActionBar != null
@@ -158,6 +161,15 @@ class PreviewImageActivity :
lifecycle.addObserver(sendShareDownloader)
sendShareDownloader.restoreState(savedInstanceState)
+ applyDarkSystemBars()
+ }
+
+ private fun applyDarkSystemBars() {
+ windowInsetsController.isAppearanceLightStatusBars = false
+ windowInsetsController.isAppearanceLightNavigationBars = false
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ window.isNavigationBarContrastEnforced = false
+ }
}
override fun onNewIntent(intent: Intent?) {
@@ -380,7 +392,7 @@ class PreviewImageActivity :
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(KEY_WAITING_FOR_BINDER, screenState == PreviewImageActivityState.WaitingForBinder)
- outState.putBoolean(KEY_SYSTEM_VISIBLE, isSystemUIVisible)
+ outState.putBoolean(KEY_ACTION_BAR_VISIBLE, isActionBarVisible)
sendShareDownloader.saveState(outState)
}
@@ -539,12 +551,12 @@ class PreviewImageActivity :
updatePagerDisplayCutOutPadding(isInPictureInPictureMode, displayCutOutSafeInsetTop())
if (isInPictureInPictureMode) {
- wasSystemUiVisibleBeforePictureInPicture = isSystemUIVisible
+ wasActionBarVisibleBeforePictureInPicture = isActionBarVisible
toggleActionBarVisibility(false)
return
}
- toggleActionBarVisibility(wasSystemUiVisibleBeforePictureInPicture)
+ toggleActionBarVisibility(wasActionBarVisibleBeforePictureInPicture)
if (lifecycle.currentState != Lifecycle.State.CREATED) return
@@ -629,19 +641,19 @@ class PreviewImageActivity :
}
}
- val isSystemUIVisible: Boolean
+ val isActionBarVisible: Boolean
get() = supportActionBar == null || supportActionBar?.isShowing == true
fun toggleFullScreen() {
- val rootInsets = ViewCompat.getRootWindowInsets(window.decorView) ?: return
-
// the content is laid out edge to edge so that showing and hiding the bars does not move it
WindowCompat.setDecorFitsSystemWindows(window, false)
- if (rootInsets.isVisible(WindowInsetsCompat.Type.systemBars())) {
- windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
+ val showBars = !isActionBarVisible
+ toggleActionBarVisibility(showBars)
+ if (showBars) {
+ windowInsetsController.show(WindowInsetsCompat.Type.navigationBars())
} else {
- windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
+ windowInsetsController.hide(WindowInsetsCompat.Type.navigationBars())
}
}
@@ -675,7 +687,7 @@ class PreviewImageActivity :
const val EXTRA_VIRTUAL_TYPE: String = "EXTRA_VIRTUAL_TYPE"
const val EXTRA_MEDIA_STATE: String = "EXTRA_MEDIA_STATE"
private const val KEY_WAITING_FOR_BINDER = "WAITING_FOR_BINDER"
- private const val KEY_SYSTEM_VISIBLE = "TRUE"
+ private const val KEY_ACTION_BAR_VISIBLE = "TRUE"
private const val NO_POSITION = -1
fun previewFileIntent(context: Context?, user: User?, file: OCFile?): Intent =
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt
index 33e51fa0ebf9..ae30ce5e3344 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt
@@ -794,7 +794,7 @@ class PreviewImageFragment :
if (binding.image.drawable is LayerDrawable) {
val layerDrawable = binding.image.drawable as LayerDrawable
- val layerOne = if (previewImageActivity?.isSystemUIVisible == true) {
+ val layerOne = if (previewImageActivity?.isActionBarVisible == true) {
ResourcesCompat.getDrawable(resources, R.color.bg_default, null)
} else {
ResourcesCompat.getDrawable(
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewPlaybackFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewPlaybackFragment.kt
index 37619429685c..bf04c13810c0 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewPlaybackFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewPlaybackFragment.kt
@@ -27,13 +27,13 @@ import com.google.android.material.snackbar.Snackbar
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.player.media3.PlaybackModel
+import com.nextcloud.client.player.media3.PlaybackQueueLoader
import com.nextcloud.client.player.model.PlayerThumbnailLoader
import com.nextcloud.client.player.model.file.PlaybackCollection
import com.nextcloud.client.player.model.file.PlaybackFile
import com.nextcloud.client.player.model.state.PlaybackState
import com.nextcloud.client.player.model.state.VideoSize
import com.nextcloud.client.player.ui.MediaNavigator
-import com.nextcloud.client.player.ui.PlayerLauncher
import com.nextcloud.client.player.util.PlayerUtil.applyVideoSize
import com.nextcloud.client.player.util.PlayerUtil.isPictureInPictureAllowed
import com.nextcloud.client.player.util.PlayerUtil.ownsPlayback
@@ -41,11 +41,10 @@ import com.nextcloud.client.player.util.PlayerUtil.toPlaybackFile
import com.nextcloud.ui.fileactions.FileAction
import com.nextcloud.ui.fileactions.FileActionsBottomSheet
import com.nextcloud.utils.SnackbarUtil
+import com.nextcloud.utils.extensions.addPaddingForNavBar
import com.nextcloud.utils.extensions.getParcelableArgument
import com.nextcloud.utils.extensions.getSerializableArgument
import com.nextcloud.utils.extensions.setVisibilityWithAnimation
-import com.nextcloud.utils.extensions.showNavigationBar
-import com.nextcloud.utils.extensions.showSystemBar
import com.owncloud.android.R
import com.owncloud.android.databinding.PreviewPlaybackFragmentBinding
import com.owncloud.android.datamodel.OCFile
@@ -61,11 +60,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
-/**
- * Plays an audio or video page of the preview pager in place, so that swiping between images and media keeps the
- * user on the same screen. Playback itself is owned by the shared [PlaybackModel], the same one that drives
- * [com.nextcloud.client.player.ui.PlayerActivity], notification and background playback.
- */
class PreviewPlaybackFragment :
Fragment(),
PlaybackModel.Listener {
@@ -75,31 +69,24 @@ class PreviewPlaybackFragment :
private const val ARGUMENT_FILE = "ARGUMENT_FILE"
private const val ARGUMENT_COLLECTION = "ARGUMENT_COLLECTION"
private const val ARGUMENT_AUTOPLAY = "ARGUMENT_AUTOPLAY"
- private const val ARGUMENT_PICTURE_IN_PICTURE_ON_BACK = "ARGUMENT_PICTURE_IN_PICTURE_ON_BACK"
private const val SURFACE_ALPHA_VISIBLE = 1f
private const val SURFACE_ALPHA_HIDDEN = 0f
- @Suppress("LongParameterList")
- fun newInstance(
- file: OCFile,
- collection: PlaybackCollection,
- autoplay: Boolean = false,
- pictureInPictureOnBack: Boolean = true
- ) = PreviewPlaybackFragment().apply {
- arguments = bundleOf(
- ARGUMENT_FILE to file,
- ARGUMENT_COLLECTION to collection,
- ARGUMENT_AUTOPLAY to autoplay,
- ARGUMENT_PICTURE_IN_PICTURE_ON_BACK to pictureInPictureOnBack
- )
- }
+ fun newInstance(file: OCFile, collection: PlaybackCollection, autoplay: Boolean = false) =
+ PreviewPlaybackFragment().apply {
+ arguments = bundleOf(
+ ARGUMENT_FILE to file,
+ ARGUMENT_COLLECTION to collection,
+ ARGUMENT_AUTOPLAY to autoplay
+ )
+ }
}
@Inject
lateinit var playbackModel: PlaybackModel
@Inject
- lateinit var playerLauncher: PlayerLauncher
+ lateinit var playbackQueueLoader: PlaybackQueueLoader
@Inject
lateinit var playerThumbnailLoader: PlayerThumbnailLoader
@@ -118,12 +105,13 @@ class PreviewPlaybackFragment :
private lateinit var playbackFile: PlaybackFile
private var playbackCollection = PlaybackCollection.FOLDER
private var autoplay: Boolean = false
- private var pictureInPictureOnBack: Boolean = true
private var wasCurrentItem = false
private var renderedVideoSize: VideoSize? = null
private var pictureInPictureCallback: OnBackPressedCallback? = null
- private var isFullScreen = false
+
+ private val previewActivity: PreviewImageActivity?
+ get() = activity as? PreviewImageActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -134,7 +122,6 @@ class PreviewPlaybackFragment :
playbackCollection = arguments.getSerializableArgument(ARGUMENT_COLLECTION, PlaybackCollection::class.java)
?: PlaybackCollection.FOLDER
autoplay = arguments?.getBoolean(ARGUMENT_AUTOPLAY) == true
- pictureInPictureOnBack = arguments?.getBoolean(ARGUMENT_PICTURE_IN_PICTURE_ON_BACK) != false
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@@ -142,7 +129,8 @@ class PreviewPlaybackFragment :
loadThumbnail()
registerPictureInPictureOnBack()
binding.playerControlView.navigator = activity as? MediaNavigator
- isFullScreen = previewActivity()?.isSystemUIVisible == false
+ binding.playerControlView.addPaddingForNavBar()
+ binding.playerControlView.isVisible = previewActivity?.isActionBarVisible != false
binding.surfaceView.setOnClickListener { toggleFullScreen() }
updatePlayerControlsVisibility()
binding.root.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
@@ -183,7 +171,7 @@ class PreviewPlaybackFragment :
}
private fun onOverflowClick(isManualClick: Boolean = false) {
- val storageManager = previewActivity()?.storageManager ?: return
+ val storageManager = previewActivity?.storageManager ?: return
val updatedFile = storageManager.getFileById(file.fileId)
// check for albums file for album file both local and remoteId will be same configured at operation level
@@ -203,7 +191,7 @@ class PreviewPlaybackFragment :
}
private fun fetchFileMetaDataIfAbsent(ocFile: OCFile) {
- val previewActivity = previewActivity() ?: return
+ val previewActivity = previewActivity ?: return
val context = context ?: return
previewActivity.showLoadingDialog(getString(R.string.wait_a_moment))
@@ -233,7 +221,7 @@ class PreviewPlaybackFragment :
@Suppress("CyclomaticComplexMethod")
private fun onFileActionChosen(itemId: Int) {
- val previewActivity = previewActivity() ?: return
+ val previewActivity = previewActivity ?: return
val fileOperationsHelper = previewActivity.fileOperationsHelper
when (itemId) {
@@ -279,7 +267,7 @@ class PreviewPlaybackFragment :
}
private fun registerPictureInPictureOnBack() {
- if (!pictureInPictureOnBack || !MimeTypeUtil.isVideo(file)) {
+ if (!MimeTypeUtil.isVideo(file)) {
return
}
@@ -291,7 +279,7 @@ class PreviewPlaybackFragment :
viewLifecycleOwner,
enabled = false
) {
- if (previewActivity()?.enterPictureInPicture() == true) {
+ if (previewActivity?.enterPictureInPicture() == true) {
return@addCallback
}
@@ -308,7 +296,6 @@ class PreviewPlaybackFragment :
override fun onResume() {
super.onResume()
- playbackModel.onPictureInPictureClose?.invoke()
pictureInPictureCallback?.isEnabled = true
preparePlayback()
binding.playerControlView.onStart()
@@ -359,7 +346,11 @@ class PreviewPlaybackFragment :
playbackModel.play()
}
} else {
- playerLauncher.prepare(this, file, playbackCollection, autoplay)
+ playbackQueueLoader.load(this, file, playbackCollection) {
+ if (autoplay) {
+ playbackModel.play()
+ }
+ }
}
}
@@ -392,7 +383,7 @@ class PreviewPlaybackFragment :
}
private fun showPageOfCurrentItem(state: PlaybackState?) {
- val previewActivity = previewActivity() ?: return
+ val previewActivity = previewActivity ?: return
val localId = state?.currentItemState?.file?.id?.toLongOrNull()
if (localId != null && previewActivity.showFilePage(localId)) return
@@ -401,19 +392,12 @@ class PreviewPlaybackFragment :
}
}
- private fun previewActivity(): PreviewImageActivity? = activity as? PreviewImageActivity
-
private fun isInPictureInPictureMode(): Boolean = activity?.isInPictureInPictureMode == true
private fun toggleFullScreen() {
- val previewActivity = previewActivity() ?: return
- isFullScreen = !isFullScreen
- previewActivity.toggleActionBarVisibility(!isFullScreen)
- binding.playerControlView.setVisibilityWithAnimation(!isFullScreen)
- previewActivity.window.run {
- showSystemBar(!isFullScreen, binding.root)
- showNavigationBar(!isFullScreen, binding.root)
- }
+ val previewActivity = previewActivity ?: return
+ binding.playerControlView.setVisibilityWithAnimation(!previewActivity.isActionBarVisible)
+ previewActivity.toggleFullScreen()
}
private fun updatePlayerControlsVisibility() {
@@ -422,8 +406,7 @@ class PreviewPlaybackFragment :
return
}
- // playback updates arrive every second, so they must not undo full screen or cut the fade short
- if (!isFullScreen) {
+ if (previewActivity?.isActionBarVisible == true) {
binding.playerControlView.isVisible = true
}
}
diff --git a/app/src/main/res/drawable/player_video_top_scrim.xml b/app/src/main/res/drawable/player_video_top_scrim.xml
deleted file mode 100644
index 28424508e791..000000000000
--- a/app/src/main/res/drawable/player_video_top_scrim.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/player_top_bar.xml b/app/src/main/res/layout/player_audio_top_bar.xml
similarity index 94%
rename from app/src/main/res/layout/player_top_bar.xml
rename to app/src/main/res/layout/player_audio_top_bar.xml
index b6de94155ea8..9c499858204c 100644
--- a/app/src/main/res/layout/player_top_bar.xml
+++ b/app/src/main/res/layout/player_audio_top_bar.xml
@@ -13,7 +13,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/player_toolbar_height"
- app:menu="@menu/player_top_bar"
+ app:menu="@menu/player_audio_top_bar"
app:navigationContentDescription="@string/common_back"
app:navigationIcon="@drawable/ic_arrow_back"
tools:title="Player title" />
diff --git a/app/src/main/res/layout/player_audio_view.xml b/app/src/main/res/layout/player_audio_view.xml
index 158ade3fdc6e..97eaba2bee3b 100644
--- a/app/src/main/res/layout/player_audio_view.xml
+++ b/app/src/main/res/layout/player_audio_view.xml
@@ -11,9 +11,9 @@
android:background="@color/player_background_color"
android:orientation="vertical">
-
+
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/player_video_view.xml b/app/src/main/res/layout/player_video_view.xml
deleted file mode 100644
index dfa6d20b83a5..000000000000
--- a/app/src/main/res/layout/player_video_view.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/menu/player_top_bar.xml b/app/src/main/res/menu/player_audio_top_bar.xml
similarity index 100%
rename from app/src/main/res/menu/player_top_bar.xml
rename to app/src/main/res/menu/player_audio_top_bar.xml
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 5a05b4786e5f..09c99a01702f 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -96,8 +96,6 @@
@color/white
@color/white
@color/white
- #21000000
- #21000000
#B3000000
#979797
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
index 952ca315f9c3..18ffcda69b58 100644
--- a/app/src/main/res/values/ids.xml
+++ b/app/src/main/res/values/ids.xml
@@ -43,7 +43,7 @@
-
-
-
+
+
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 0003449383bf..3b48bcb6524a 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -545,8 +545,6 @@
- @color/player_default_text_color
-
-