diff --git a/app/src/main/java/com/github/libretube/constants/IntentData.kt b/app/src/main/java/com/github/libretube/constants/IntentData.kt index bf8a5d7a6..432f03ddc 100644 --- a/app/src/main/java/com/github/libretube/constants/IntentData.kt +++ b/app/src/main/java/com/github/libretube/constants/IntentData.kt @@ -6,6 +6,7 @@ object IntentData { const val id = "id" const val videoId = "videoId" const val videoIds = "videoIds" + const val videoTitle = "videoTitle" const val channelId = "channelId" const val channelName = "channelName" const val channelAvatar = "channelAvatar" @@ -52,4 +53,6 @@ object IntentData { const val downloadTab = "downloadTab" const val shuffle = "shuffle" const val noInternet = "noInternet" + const val isPlayingOffline = "isPlayingOffline" + const val downloadInfo = "downloadInfo" } diff --git a/app/src/main/java/com/github/libretube/db/dao/DownloadDao.kt b/app/src/main/java/com/github/libretube/db/dao/DownloadDao.kt index 5882cc672..f60448a43 100644 --- a/app/src/main/java/com/github/libretube/db/dao/DownloadDao.kt +++ b/app/src/main/java/com/github/libretube/db/dao/DownloadDao.kt @@ -21,7 +21,7 @@ interface DownloadDao { @Transaction @Query("SELECT * FROM download WHERE videoId = :videoId") - suspend fun findById(videoId: String): DownloadWithItems + suspend fun findById(videoId: String): DownloadWithItems? @Query("SELECT videoId FROM downloadItem WHERE type = :fileType ORDER BY RANDOM() LIMIT 1") suspend fun getRandomVideoIdByFileType(fileType: FileType): String? diff --git a/app/src/main/java/com/github/libretube/helpers/DownloadHelper.kt b/app/src/main/java/com/github/libretube/helpers/DownloadHelper.kt index e043e4885..e34b8cdce 100644 --- a/app/src/main/java/com/github/libretube/helpers/DownloadHelper.kt +++ b/app/src/main/java/com/github/libretube/helpers/DownloadHelper.kt @@ -12,6 +12,8 @@ import com.github.libretube.api.PlaylistsHelper import com.github.libretube.constants.IntentData import com.github.libretube.constants.PreferenceKeys import com.github.libretube.db.obj.DownloadItem +import com.github.libretube.db.obj.DownloadWithItems +import com.github.libretube.enums.FileType import com.github.libretube.enums.PlaylistType import com.github.libretube.extensions.toID import com.github.libretube.extensions.toastFromMainDispatcher @@ -139,4 +141,20 @@ object DownloadHelper { } } } + + fun extractDownloadInfoText(context: Context, download: DownloadWithItems): List { + val downloadInfo = mutableListOf() + download.downloadItems.firstOrNull { it.type == FileType.VIDEO }?.let { videoItem -> + downloadInfo.add(context.getString(R.string.video) + ": ${videoItem.format} ${videoItem.quality}") + } + download.downloadItems.firstOrNull { it.type == FileType.AUDIO }?.let { audioItem -> + var infoString = ": ${audioItem.quality} ${audioItem.format})" + if (audioItem.language != null) infoString += " ${audioItem.language}" + downloadInfo.add(context.getString(R.string.audio) + infoString) + } + download.downloadItems.firstOrNull { it.type == FileType.SUBTITLE }?.let { + downloadInfo.add(context.getString(R.string.captions) + ": ${it.language}") + } + return downloadInfo + } } diff --git a/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt b/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt index 66f3493db..91a752d42 100644 --- a/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt +++ b/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt @@ -69,7 +69,7 @@ class OfflinePlayerService : AbstractPlayerService() { override suspend fun startPlaybackAndUpdateNotification() { val downloadWithItems = withContext(Dispatchers.IO) { Database.downloadDao().findById(videoId) - } + }!! this.downloadWithItems = downloadWithItems onNewVideoStarted?.let { it(downloadWithItems.download.toStreamItem()) } diff --git a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt index 5ff651c8d..59929c70c 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt @@ -229,7 +229,7 @@ class OfflinePlayerActivity : BaseActivity() { lifecycleScope.launch { val (downloadInfo, downloadItems, downloadChapters) = withContext(Dispatchers.IO) { Database.downloadDao().findById(videoId) - } + }!! PlayingQueue.updateCurrent(downloadInfo.toStreamItem()) val chapters = downloadChapters.map(DownloadChapter::toChapterSegment) diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/PlayOfflineDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/PlayOfflineDialog.kt new file mode 100644 index 000000000..7f67ca310 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/dialogs/PlayOfflineDialog.kt @@ -0,0 +1,55 @@ +package com.github.libretube.ui.dialogs + +import android.app.Dialog +import android.content.Intent +import android.os.Bundle +import android.widget.ArrayAdapter +import androidx.core.os.bundleOf +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.setFragmentResult +import com.github.libretube.R +import com.github.libretube.constants.IntentData +import com.github.libretube.databinding.DialogPlayOfflineBinding +import com.github.libretube.ui.activities.OfflinePlayerActivity +import com.github.libretube.util.TextUtils +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class PlayOfflineDialog : DialogFragment() { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val binding = DialogPlayOfflineBinding.inflate(layoutInflater) + val videoId = requireArguments().getString(IntentData.videoId) + binding.videoTitle.text = requireArguments().getString(IntentData.videoTitle) + + val downloadInfo = requireArguments().getStringArray(IntentData.downloadInfo) + binding.downloadInfo.adapter = + ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, downloadInfo.orEmpty().map { + TextUtils.SEPARATOR + it + }) + + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.dialog_play_offline_title) + .setView(binding.root) + .setPositiveButton(R.string.yes) { _, _ -> + val intent = Intent(requireContext(), OfflinePlayerActivity::class.java) + .putExtra(IntentData.videoId, videoId) + requireContext().startActivity(intent) + + setFragmentResult( + PLAY_OFFLINE_DIALOG_REQUEST_KEY, + bundleOf(IntentData.isPlayingOffline to true) + ) + } + .setNegativeButton(getString(R.string.cancel)) { _, _ -> + setFragmentResult( + PLAY_OFFLINE_DIALOG_REQUEST_KEY, + bundleOf(IntentData.isPlayingOffline to false) + ) + } + .show() + } + + companion object { + const val PLAY_OFFLINE_DIALOG_REQUEST_KEY = "play_offline_dialog_request_key" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index 757cc1708..f2fc1c64b 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -65,6 +65,7 @@ import com.github.libretube.constants.IntentData import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.FragmentPlayerBinding import com.github.libretube.db.DatabaseHelper +import com.github.libretube.db.DatabaseHolder import com.github.libretube.enums.PlayerEvent import com.github.libretube.enums.ShareObjectType import com.github.libretube.extensions.formatShort @@ -98,6 +99,7 @@ import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.adapters.VideosAdapter import com.github.libretube.ui.base.BaseActivity import com.github.libretube.ui.dialogs.AddToPlaylistDialog +import com.github.libretube.ui.dialogs.PlayOfflineDialog import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.extensions.animateDown import com.github.libretube.ui.extensions.setupSubscriptionButton @@ -119,6 +121,7 @@ import com.github.libretube.util.TextUtils.toTimeInSeconds import com.github.libretube.util.YoutubeHlsPlaylistParser import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import java.util.concurrent.Executors import kotlin.math.abs @@ -243,7 +246,10 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { private val playerListener = object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { - if (PlayerHelper.pipEnabled || PictureInPictureCompat.isInPictureInPictureMode(mainActivity)) { + if (PlayerHelper.pipEnabled || PictureInPictureCompat.isInPictureInPictureMode( + mainActivity + ) + ) { PictureInPictureCompat.setPictureInPictureParams(requireActivity(), pipParams) } @@ -437,7 +443,37 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { binding.player.setCurrentChapterName() } - playVideo() + val localDownloadVersion = runBlocking(Dispatchers.IO) { + DatabaseHolder.Database.downloadDao().findById(videoId) + } + + if (localDownloadVersion != null) { + childFragmentManager.setFragmentResultListener( + PlayOfflineDialog.PLAY_OFFLINE_DIALOG_REQUEST_KEY, viewLifecycleOwner + ) { _, bundle -> + if (bundle.getBoolean(IntentData.isPlayingOffline)) { + // offline video playback started and thus the player fragment is no longer needed + killPlayerFragment() + } else { + playVideo() + } + } + + val downloadInfo = DownloadHelper.extractDownloadInfoText( + requireContext(), + localDownloadVersion + ).toTypedArray() + + PlayOfflineDialog().apply { + arguments = bundleOf( + IntentData.videoId to videoId, + IntentData.videoTitle to localDownloadVersion.download.title, + IntentData.downloadInfo to downloadInfo + ) + }.show(childFragmentManager, null) + } else { + playVideo() + } showBottomBar() } diff --git a/app/src/main/res/layout/dialog_play_offline.xml b/app/src/main/res/layout/dialog_play_offline.xml new file mode 100644 index 000000000..6d35d19a4 --- /dev/null +++ b/app/src/main/res/layout/dialog_play_offline.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 61b96f376..fed71ff71 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -517,7 +517,8 @@ Directly fetch video playback information from YouTube without using Piped. Local Return Youtube Dislikes Directly fetch dislike information from https://returnyoutubedislikeapi.com - %1$s views + A local version of this video is available. + Would you like to play the video from the download folder? Download Service @@ -563,5 +564,4 @@ Also clear watch positions Import temporary playlist? Do you want to create a new playlist named \'%1$s\'? The playlist will contain %2$d videos. -