mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-13 22:00:30 +05:30
feat: prompt to play offline if video already downloaded
This commit is contained in:
parent
da8316ac3b
commit
8bf7f56ec8
@ -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"
|
||||
}
|
||||
|
@ -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?
|
||||
|
@ -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<String> {
|
||||
val downloadInfo = mutableListOf<String>()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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()) }
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
45
app/src/main/res/layout/dialog_play_offline.xml
Normal file
45
app/src/main/res/layout/dialog_play_offline.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Some random video title" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/download_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/dialog_play_offline_body" />
|
||||
|
||||
</LinearLayout>
|
@ -517,7 +517,8 @@
|
||||
<string name="local_stream_extraction_summary">Directly fetch video playback information from YouTube without using Piped.</string>
|
||||
<string name="local_ryd">Local Return Youtube Dislikes</string>
|
||||
<string name="local_ryd_summary">Directly fetch dislike information from https://returnyoutubedislikeapi.com</string>
|
||||
<string name="view_count">%1$s views</string>
|
||||
<string name="dialog_play_offline_title">A local version of this video is available.</string>
|
||||
<string name="dialog_play_offline_body">Would you like to play the video from the download folder?</string>
|
||||
|
||||
<!-- Notification channel strings -->
|
||||
<string name="download_channel_name">Download Service</string>
|
||||
@ -563,5 +564,4 @@
|
||||
<string name="also_clear_watch_positions">Also clear watch positions</string>
|
||||
<string name="import_temp_playlist">Import temporary playlist?</string>
|
||||
<string name="import_temp_playlist_summary">Do you want to create a new playlist named \'%1$s\'? The playlist will contain %2$d videos.</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user