1.The videos are not visible on the home screen, but they are visible on the same server in libretube.

2.Open download card late on YouTube.

3.When you enter the end time or a higher value there and confirm, the application is closed and the error..

4.The home page player and cut player do not open. When the cut player opens, the video often stops

5.The cutting player does not do its work exactly.
This commit is contained in:
deniscerri 2023-07-30 16:20:09 +02:00
parent 8a42b3f515
commit 444f0cc582
No known key found for this signature in database
GPG key ID: 95C43D517D830350
18 changed files with 185 additions and 74 deletions

View file

@ -10,7 +10,7 @@ def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 6 def versionMinor = 6
def versionPatch = 3 def versionPatch = 3
def versionBuild = 15 // bump for dogfood builds, public betas, etc. def versionBuild = 17 // bump for dogfood builds, public betas, etc.
def versionExt = "" def versionExt = ""
if (versionBuild > 0){ if (versionBuild > 0){

View file

@ -52,7 +52,10 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.random.Random
import kotlin.random.nextInt
class DownloadViewModel(application: Application) : AndroidViewModel(application) { class DownloadViewModel(application: Application) : AndroidViewModel(application) {
@ -149,7 +152,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
} }
suspend fun updateDownload(item: DownloadItem){ suspend fun updateDownload(item: DownloadItem){
repository.update(item) if (sharedPreferences.getBoolean("incognito", false)){
if (item.status == DownloadRepository.Status.Cancelled.toString() || item.status == DownloadRepository.Status.Error.toString()){
repository.delete(item)
}
}else{
repository.update(item)
}
} }
fun getItemByID(id: Long) : DownloadItem { fun getItemByID(id: Long) : DownloadItem {
@ -485,6 +494,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString() if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString()
if (activeAndQueuedDownloads.firstOrNull{d -> if (activeAndQueuedDownloads.firstOrNull{d ->
d.id = 0 d.id = 0
d.logID = null
d.customFileNameTemplate = it.customFileNameTemplate
d.status = DownloadRepository.Status.Queued.toString() d.status = DownloadRepository.Status.Queued.toString()
d.toString() == it.toString() d.toString() == it.toString()
} != null) { } != null) {
@ -506,6 +517,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show() Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
} }
} }
val workManager = WorkManager.getInstance(context)
queuedItems.forEach { queuedItems.forEach {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
var delay = if (it.downloadStartTime != 0L){ var delay = if (it.downloadStartTime != 0L){
@ -518,19 +532,20 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>() val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(Data.Builder().putLong("id", it.id).build()) .setInputData(Data.Builder().putLong("id", it.id).build())
.addTag(it.id.toString())
.addTag("download") .addTag("download")
.setConstraints(workConstraints.build()) .setConstraints(workConstraints.build())
.setInitialDelay(delay, TimeUnit.SECONDS) .setInitialDelay(delay, TimeUnit.SECONDS)
.build() .build()
WorkManager.getInstance(context).beginUniqueWork( workManager.enqueueUniqueWork(
it.id.toString(), Random.nextInt(1000000000).toString(),
ExistingWorkPolicy.KEEP, ExistingWorkPolicy.REPLACE,
workRequest workRequest
).enqueue() )
} }
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
if (!allowMeteredNetworks && isCurrentNetworkMetered){ if (!allowMeteredNetworks && isCurrentNetworkMetered){
Looper.prepare().run { Looper.prepare().run {

View file

@ -17,15 +17,17 @@ class CancelDownloadNotificationReceiver : BroadcastReceiver() {
val message = intent.getStringExtra("cancel") val message = intent.getStringExtra("cancel")
val id = intent.getIntExtra("workID", 0) val id = intent.getIntExtra("workID", 0)
if (message != null) { if (message != null) {
val notificationUtil = NotificationUtil(c) runCatching {
notificationUtil.cancelDownloadNotification(id) val notificationUtil = NotificationUtil(c)
YoutubeDL.getInstance().destroyProcessById(id.toString()) notificationUtil.cancelDownloadNotification(id)
YoutubeDL.getInstance().destroyProcessById(id.toString())
val dbManager = DBManager.getInstance(c) val dbManager = DBManager.getInstance(c)
CoroutineScope(Dispatchers.IO).launch{ CoroutineScope(Dispatchers.IO).launch{
val item = dbManager.downloadDao.getDownloadById(id.toLong()) val item = dbManager.downloadDao.getDownloadById(id.toLong())
item.status = DownloadRepository.Status.Cancelled.toString() item.status = DownloadRepository.Status.Cancelled.toString()
dbManager.downloadDao.update(item) dbManager.downloadDao.update(item)
}
} }
} }

View file

@ -10,11 +10,13 @@ class PauseDownloadNotificationReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) { override fun onReceive(c: Context, intent: Intent) {
val id = intent.getIntExtra("workID", 0) val id = intent.getIntExtra("workID", 0)
if (id != 0) { if (id != 0) {
val title = intent.getStringExtra("title") runCatching {
val notificationUtil = NotificationUtil(c) val title = intent.getStringExtra("title")
notificationUtil.cancelDownloadNotification(id) val notificationUtil = NotificationUtil(c)
YoutubeDL.getInstance().destroyProcessById(id.toString()) notificationUtil.cancelDownloadNotification(id)
notificationUtil.createResumeDownload(id, title, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID) YoutubeDL.getInstance().destroyProcessById(id.toString())
notificationUtil.createResumeDownload(id, title, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
}
} }
} }
} }

View file

@ -6,6 +6,7 @@ import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
@ -26,10 +27,14 @@ import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import java.util.regex.Pattern
class AddExtraCommandsDialog(private val item: DownloadItem, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() { class AddExtraCommandsDialog(private val item: DownloadItem, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() {
@ -81,11 +86,32 @@ class AddExtraCommandsDialog(private val item: DownloadItem, private val callbac
val text = view.findViewById<EditText>(R.id.command) val text = view.findViewById<EditText>(R.id.command)
val templates = view.findViewById<Button>(R.id.commands) val templates = view.findViewById<Button>(R.id.commands)
val shortcuts = view.findViewById<Button>(R.id.shortcuts) val shortcuts = view.findViewById<Button>(R.id.shortcuts)
val currentCommand = infoUtil.buildYoutubeDLRequest(item).buildCommand().joinToString(" ") val currentCommand = infoUtil.parseYTDLRequestString(infoUtil.buildYoutubeDLRequest(item))
view.findViewById<TextView>(R.id.currentText)?.text = currentCommand val currentText = view.findViewById<TextView>(R.id.currentText)
currentText?.text = currentCommand
//init syntax highlighter
val highlight = Highlight()
val highlightWatcher = HighlightTextWatcher()
val schemes = listOf(
ColorScheme(Pattern.compile("([\"'])(?:\\\\1|.)*?\\1"), Color.parseColor("#FC8500")),
ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#00FF00")),
ColorScheme(Pattern.compile("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})"), Color.parseColor("#b5942f")),
ColorScheme(Pattern.compile("\\d+(\\.\\d)?%"), Color.parseColor("#43a564"))
)
highlight.addScheme(
*schemes.map { it }.toTypedArray()
)
highlightWatcher.addScheme(
*schemes.map { it }.toTypedArray()
)
highlight.setSpan(currentText)
currentText.addTextChangedListener(highlightWatcher)
text?.setText(item.extraCommands) text?.setText(item.extraCommands)
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
text!!.postDelayed({ text!!.postDelayed({
text.setSelection(text.length()) text.setSelection(text.length())

View file

@ -8,28 +8,21 @@ import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Range
import android.view.KeyEvent import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View import android.view.View
import android.widget.* import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.children import androidx.core.view.children
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem.fromUri import androidx.media3.common.MediaItem.fromUri
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.cronet.CronetDataSource
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.source.MediaSource import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.exoplayer.source.MergingMediaSource import androidx.media3.exoplayer.source.MergingMediaSource
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ChapterItem import com.deniscerri.ytdlnis.database.models.ChapterItem
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
@ -54,14 +47,12 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.chromium.net.CronetEngine
import java.lang.reflect.Type import java.lang.reflect.Type
import java.util.* import java.util.*
import java.util.concurrent.Executors
import kotlin.properties.Delegates import kotlin.properties.Delegates
class CutVideoBottomSheetDialog(private val item: DownloadItem, private var urls : String?, private var chapters: List<ChapterItem>?, private val listener: VideoCutListener) : BottomSheetDialogFragment() { class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls : String?, private var chapters: List<ChapterItem>?, private val listener: VideoCutListener) : BottomSheetDialogFragment() {
private lateinit var behavior: BottomSheetBehavior<View> private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var infoUtil: InfoUtil private lateinit var infoUtil: InfoUtil
private lateinit var player: ExoPlayer private lateinit var player: ExoPlayer
@ -279,7 +270,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private var urls
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestampString) toTextInput.editText!!.setTextAndRecalculateWidth(endTimestampString)
okBtn.isEnabled = !(values[0] == 0F && values[1] == 100F) okBtn.isEnabled = values[0] != 0F && values[1] != 100F
try { try {
player.seekTo((startTimestamp * 1000).toLong()) player.seekTo((startTimestamp * 1000).toLong())
@ -294,26 +285,29 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private var urls
if ((event!!.action == KeyEvent.ACTION_DOWN) && if ((event!!.action == KeyEvent.ACTION_DOWN) &&
(keyCode == KeyEvent.KEYCODE_ENTER)) { (keyCode == KeyEvent.KEYCODE_ENTER)) {
val startTimestamp = (rangeSlider.valueFrom.toInt() * timeSeconds) / 100 var startTimestamp = (rangeSlider.valueFrom.toInt() * timeSeconds) / 100
val endTimestamp = (rangeSlider.valueTo.toInt() * timeSeconds) / 100 val endTimestamp = (rangeSlider.valueTo.toInt() * timeSeconds) / 100
fromTextInput.editText!!.clearFocus() fromTextInput.editText!!.clearFocus()
val seconds = convertStringToTimestamp(fromTextInput.editText!!.text.toString()) var seconds = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
val endSeconds = convertStringToTimestamp(toTextInput.editText!!.text.toString()) val endSeconds = convertStringToTimestamp(toTextInput.editText!!.text.toString())
val startValue = (seconds.toFloat() / endTimestamp) * 100 var startValue = (seconds.toFloat() / endTimestamp) * 100
val endValue = (endSeconds.toFloat() / endTimestamp) * 100 val endValue = (endSeconds.toFloat() / endTimestamp) * 100
if (seconds == 0) { if (seconds == 0) {
fromTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(startTimestamp, Locale.US)) fromTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(startTimestamp, Locale.US))
}else if (startValue > 100){ }else if (startValue > 100){
startTimestamp = 0
seconds = 0
fromTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(startTimestamp, Locale.US)) fromTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(startTimestamp, Locale.US))
startValue = 0F
} }
rangeSlider.setValues(startValue, endValue) rangeSlider.setValues(startValue, endValue)
okBtn.isEnabled = !(startValue == 0F && endValue == 100F) okBtn.isEnabled = startValue != 0F && endValue != 100F
try { try {
player.seekTo((((startValue * timeSeconds) / 100) * 1000).toLong()) player.seekTo(seconds.toLong() * 1000)
player.play() player.play()
}catch (ignored: Exception) {} }catch (ignored: Exception) {}
@ -331,14 +325,19 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private var urls
(keyCode == KeyEvent.KEYCODE_ENTER)) { (keyCode == KeyEvent.KEYCODE_ENTER)) {
val values = rangeSlider.values val values = rangeSlider.values
val endTimestamp = (rangeSlider.valueTo.toInt() * timeSeconds) / 100 var endTimestamp = (rangeSlider.valueTo.toInt() * timeSeconds) / 100
toTextInput.editText!!.clearFocus() toTextInput.editText!!.clearFocus()
val startSeconds = convertStringToTimestamp(fromTextInput.editText!!.text.toString()) val startSeconds = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
val seconds = convertStringToTimestamp(toTextInput.editText!!.text.toString()) val seconds = convertStringToTimestamp(toTextInput.editText!!.text.toString())
val startValue = (startSeconds.toFloat() / endTimestamp) * 100 val startValue = (startSeconds.toFloat() / endTimestamp) * 100
val endValue = (seconds.toFloat() / endTimestamp) * 100 var endValue = (seconds.toFloat() / endTimestamp) * 100
if (endValue > 100F){
endTimestamp = timeSeconds
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
endValue = 100F
}
if (seconds == 0) { if (seconds == 0) {
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US)) toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
@ -347,9 +346,9 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private var urls
} }
rangeSlider.setValues(startValue, endValue) rangeSlider.setValues(startValue, endValue)
okBtn.isEnabled = !(startValue == 0F && endValue == 100F) okBtn.isEnabled = startValue != 0F && endValue != 100F
try { try {
player.seekTo((((startValue * timeSeconds) / 100) * 1000).toLong()) player.seekTo(startSeconds.toLong() * 1000)
player.play() player.play()
}catch (ignored: Exception) {} }catch (ignored: Exception) {}
@ -543,10 +542,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private var urls
private fun videoProgress(player: ExoPlayer?) = flow { private fun videoProgress(player: ExoPlayer?) = flow {
while (true) { while (true) {
runCatching { emit((player!!.currentPosition / 1000).toInt())
emit((player!!.currentPosition / 1000).toInt()) delay(1000)
delay(1000)
}
} }
}.flowOn(Dispatchers.Main) }.flowOn(Dispatchers.Main)
@ -587,4 +584,4 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private var urls
interface VideoCutListener{ interface VideoCutListener{
fun onChangeCut(list: List<String>) fun onChangeCut(list: List<String>)
} }

View file

@ -182,7 +182,11 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
} }
items.forEachIndexed { index, i -> items.forEachIndexed { index, i ->
i.allFormats.clear() i.allFormats.clear()
if (formatCollection.isNotEmpty() && formatCollection[index].isNotEmpty()) i.allFormats.addAll(formatCollection[index]) if (formatCollection.size == items.size && formatCollection[index].isNotEmpty()) {
runCatching {
i.allFormats.addAll(formatCollection[index])
}
}
i.format = selectedFormats[index].format i.format = selectedFormats[index].format
if (i.type == DownloadViewModel.Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) } if (i.type == DownloadViewModel.Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) }
} }

View file

@ -75,6 +75,7 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
private lateinit var activeDownloads: ActiveDownloadMinifiedAdapter private lateinit var activeDownloads: ActiveDownloadMinifiedAdapter
private lateinit var queuedDownloads: GenericDownloadAdapter private lateinit var queuedDownloads: GenericDownloadAdapter
private var activeCount: Int = 0
private lateinit var queuedItems : List<DownloadItem> private lateinit var queuedItems : List<DownloadItem>
private lateinit var activeItems : List<DownloadItem> private lateinit var activeItems : List<DownloadItem>
@ -197,6 +198,10 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
queuedDownloads.submitList(it) queuedDownloads.submitList(it)
} }
downloadViewModel.activeDownloadsCount.observe(viewLifecycleOwner){
activeCount = it
}
bottomSheetLink.text = item.url bottomSheetLink.text = item.url
bottomSheetLink.setOnClickListener{ bottomSheetLink.setOnClickListener{
@ -448,7 +453,7 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show() Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
downloadViewModel.deleteDownload(item) downloadViewModel.deleteDownload(item)
item.downloadStartTime = it.timeInMillis item.downloadStartTime = it.timeInMillis
WorkManager.getInstance(requireContext()).cancelUniqueWork(item.id.toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
runBlocking { runBlocking {
downloadViewModel.queueDownloads(listOf(item)) downloadViewModel.queueDownloads(listOf(item))
} }
@ -512,7 +517,7 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
bottomSheet.dismiss() bottomSheet.dismiss()
downloadViewModel.deleteDownload(item) downloadViewModel.deleteDownload(item)
item.downloadStartTime = 0 item.downloadStartTime = 0
WorkManager.getInstance(requireContext()).cancelUniqueWork(item.id.toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
runBlocking { runBlocking {
downloadViewModel.queueDownloads(listOf(item)) downloadViewModel.queueDownloads(listOf(item))
} }
@ -534,6 +539,21 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
} }
override fun onCancelClick(itemID: Long) { override fun onCancelClick(itemID: Long) {
lifecycleScope.launch {
if (activeCount == 1){
val queue = withContext(Dispatchers.IO){
val list = downloadViewModel.getQueued().toMutableList()
list.map { it.status = DownloadRepository.Status.Queued.toString() }
list
}
runBlocking {
downloadViewModel.queueDownloads(queue)
}
}
}
cancelActiveDownload(itemID) cancelActiveDownload(itemID)
} }
override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int) { override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int) {
@ -557,8 +577,16 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
} }
activeDownloads.notifyItemChanged(position) activeDownloads.notifyItemChanged(position)
val queue = if (activeCount > 1) listOf(item)
else withContext(Dispatchers.IO){
val list = downloadViewModel.getQueued().toMutableList()
list.map { it.status = DownloadRepository.Status.Queued.toString() }
list.add(0, item)
list
}
runBlocking { runBlocking {
downloadViewModel.queueDownloads(listOf(item)) downloadViewModel.queueDownloads(queue)
} }
} }
} }
@ -574,14 +602,14 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
private fun cancelItem(id: Int){ private fun cancelItem(id: Int){
YoutubeDL.getInstance().destroyProcessById(id.toString()) YoutubeDL.getInstance().destroyProcessById(id.toString())
WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
notificationUtil.cancelDownloadNotification(id) notificationUtil.cancelDownloadNotification(id)
} }
private fun cancelActiveDownload(itemID: Long){ private fun cancelActiveDownload(itemID: Long){
val id = itemID.toInt() val id = itemID.toInt()
YoutubeDL.getInstance().destroyProcessById(id.toString()) YoutubeDL.getInstance().destroyProcessById(id.toString())
WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
notificationUtil.cancelDownloadNotification(id) notificationUtil.cancelDownloadNotification(id)
activeItems.find { it.id == itemID }?.let { activeItems.find { it.id == itemID }?.let {

View file

@ -41,6 +41,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
private lateinit var notificationUtil: NotificationUtil private lateinit var notificationUtil: NotificationUtil
private lateinit var list: List<DownloadItem> private lateinit var list: List<DownloadItem>
private lateinit var pauseResume: MaterialButton private lateinit var pauseResume: MaterialButton
private lateinit var workManager: WorkManager
override fun onCreateView( override fun onCreateView(
@ -53,6 +54,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
notificationUtil = NotificationUtil(requireContext()) notificationUtil = NotificationUtil(requireContext())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
list = listOf() list = listOf()
workManager = WorkManager.getInstance(requireContext())
return fragmentView return fragmentView
} }
@ -87,7 +89,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
} }
queued.forEach { queued.forEach {
WorkManager.getInstance(requireContext()).cancelUniqueWork(it.id.toString()) workManager.cancelAllWorkByTag(it.id.toString())
} }
list.forEach { list.forEach {
@ -136,7 +138,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
it.forEach{item -> it.forEach{item ->
WorkManager.getInstance(requireContext()) WorkManager.getInstance(requireContext())
.getWorkInfosForUniqueWorkLiveData(item.id.toString()) .getWorkInfosByTagLiveData(item.id.toString())
.observe(viewLifecycleOwner){ list -> .observe(viewLifecycleOwner){ list ->
list.forEach {work -> list.forEach {work ->
if (work == null) return@observe if (work == null) return@observe
@ -162,6 +164,21 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
} }
override fun onCancelClick(itemID: Long) { override fun onCancelClick(itemID: Long) {
lifecycleScope.launch {
if (list.size == 1){
val queue = withContext(Dispatchers.IO){
val list = downloadViewModel.getQueued().toMutableList()
list.map { it.status = DownloadRepository.Status.Queued.toString() }
list
}
runBlocking {
downloadViewModel.queueDownloads(queue)
}
}
}
cancelDownload(itemID) cancelDownload(itemID)
} }
@ -216,7 +233,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
private fun cancelItem(id: Int){ private fun cancelItem(id: Int){
YoutubeDL.getInstance().destroyProcessById(id.toString()) YoutubeDL.getInstance().destroyProcessById(id.toString())
WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
notificationUtil.cancelDownloadNotification(id) notificationUtil.cancelDownloadNotification(id)
} }

View file

@ -167,7 +167,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show() Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
downloadViewModel.deleteDownload(item) downloadViewModel.deleteDownload(item)
item.downloadStartTime = it.timeInMillis item.downloadStartTime = it.timeInMillis
WorkManager.getInstance(requireContext()).cancelUniqueWork(item.id.toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
runBlocking { runBlocking {
downloadViewModel.queueDownloads(listOf(item)) downloadViewModel.queueDownloads(listOf(item))
} }
@ -231,7 +231,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
bottomSheet.dismiss() bottomSheet.dismiss()
downloadViewModel.deleteDownload(item) downloadViewModel.deleteDownload(item)
item.downloadStartTime = 0 item.downloadStartTime = 0
WorkManager.getInstance(requireContext()).cancelUniqueWork(item.id.toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
runBlocking { runBlocking {
downloadViewModel.queueDownloads(listOf(item)) downloadViewModel.queueDownloads(listOf(item))
} }
@ -321,7 +321,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
for (obj in selectedObjects!!){ for (obj in selectedObjects!!){
val id = obj.id.toInt() val id = obj.id.toInt()
YoutubeDL.getInstance().destroyProcessById(id.toString()) YoutubeDL.getInstance().destroyProcessById(id.toString())
WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
notificationUtil.cancelDownloadNotification(id) notificationUtil.cancelDownloadNotification(id)
downloadViewModel.deleteDownload(obj) downloadViewModel.deleteDownload(obj)
} }
@ -333,7 +333,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
} }
R.id.download -> { R.id.download -> {
for (obj in selectedObjects!!){ for (obj in selectedObjects!!){
WorkManager.getInstance(requireContext()).cancelUniqueWork(obj.id.toInt().toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(obj.id.toInt().toString())
} }
selectedObjects!!.forEach { it.downloadStartTime = 0L } selectedObjects!!.forEach { it.downloadStartTime = 0L }
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {

View file

@ -337,7 +337,7 @@ class TerminalActivity : BaseActivity() {
private fun cancelDownload() { private fun cancelDownload() {
YoutubeDL.getInstance().destroyProcessById(downloadID.toString()) YoutubeDL.getInstance().destroyProcessById(downloadID.toString())
WorkManager.getInstance(this).cancelUniqueWork(downloadID.toString()) WorkManager.getInstance(this).cancelAllWorkByTag(downloadID.toString())
notificationUtil.cancelDownloadNotification(downloadID) notificationUtil.cancelDownloadNotification(downloadID)
} }

View file

@ -1139,9 +1139,9 @@ class InfoUtil(private val context: Context) {
formatArgument = if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty() && ! downloadItem.videoPreferences.removeAudio){ formatArgument = if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty() && ! downloadItem.videoPreferences.removeAudio){
val audioIds = downloadItem.videoPreferences.audioFormatIDs.joinToString("+") val audioIds = downloadItem.videoPreferences.audioFormatIDs.joinToString("+")
"$videoFormatID+$audioIds/best/$videoFormatID" "$videoFormatID+$audioIds/$videoFormatID/best"
}else{ }else{
"$videoFormatID+bestaudio/best/$videoFormatID" "$videoFormatID+bestaudio/$videoFormatID/best"
} }
} }
Log.e(DownloadWorker.TAG, formatArgument) Log.e(DownloadWorker.TAG, formatArgument)
@ -1200,6 +1200,18 @@ class InfoUtil(private val context: Context) {
return request return request
} }
fun parseYTDLRequestString(request : YoutubeDLRequest) : String {
val arr = request.buildCommand().toMutableList()
for (i in arr.indices) {
if (!arr[i].startsWith("-")) {
arr[i] = "\"${arr[i]}\""
}
}
return java.lang.String.join(" ", arr).replace("\"\"", "\" \"")
}
private val pipedURL = sharedPreferences.getString("piped_instance", defaultPipedURL)?.ifEmpty { defaultPipedURL }?.removeSuffix("/") private val pipedURL = sharedPreferences.getString("piped_instance", defaultPipedURL)?.ifEmpty { defaultPipedURL }?.removeSuffix("/")
class PlaylistTuple internal constructor( class PlaylistTuple internal constructor(

View file

@ -342,6 +342,12 @@ class NotificationUtil(var context: Context) {
} }
} }
fun showYTDLUpdateNotification() : Notification{
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
notificationBuilder.setContentTitle("Updating YT-DLP...")
return notificationBuilder.build()
}
companion object { companion object {
const val DOWNLOAD_SERVICE_CHANNEL_ID = "1" const val DOWNLOAD_SERVICE_CHANNEL_ID = "1"
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2" const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"

View file

@ -39,7 +39,7 @@ class DownloadWorker(
) : Worker(context, workerParams) { ) : Worker(context, workerParams) {
override fun doWork(): Result { override fun doWork(): Result {
itemId = inputData.getLong("id", 0) itemId = inputData.getLong("id", 0)
if (itemId == 0L || isStopped) return Result.failure() if (itemId == 0L || isStopped) return Result.success()
val notificationUtil = NotificationUtil(context) val notificationUtil = NotificationUtil(context)
val infoUtil = InfoUtil(context) val infoUtil = InfoUtil(context)
@ -56,10 +56,10 @@ class DownloadWorker(
downloadItem = repository.getItemByID(itemId) downloadItem = repository.getItemByID(itemId)
}catch (e: Exception){ }catch (e: Exception){
e.printStackTrace() e.printStackTrace()
return Result.failure() return Result.success()
} }
if (downloadItem.status != DownloadRepository.Status.Queued.toString() && downloadItem.status != DownloadRepository.Status.Paused.toString()) return Result.failure() if (downloadItem.status != DownloadRepository.Status.Queued.toString() && downloadItem.status != DownloadRepository.Status.Paused.toString()) return Result.success()
val pendingIntent = NavDeepLinkBuilder(context) val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph) .setGraph(R.navigation.nav_graph)
@ -101,7 +101,7 @@ class DownloadWorker(
"URL: ${downloadItem.url}\n" + "URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" + "Type: ${downloadItem.type}\n" +
"Format: ${downloadItem.format}\n\n" + "Format: ${downloadItem.format}\n\n" +
"Command: ${java.lang.String.join(" ", request.buildCommand())}\n\n", "Command: ${infoUtil.parseYTDLRequestString(request)} ${downloadItem.extraCommands}\n\n",
downloadItem.format, downloadItem.format,
downloadItem.type, downloadItem.type,
System.currentTimeMillis(), System.currentTimeMillis(),
@ -194,7 +194,7 @@ class DownloadWorker(
}.onFailure { }.onFailure {
if (it is YoutubeDL.CanceledException) { if (it is YoutubeDL.CanceledException) {
return Result.failure( return Result.success(
Data.Builder().putString("output", "Download has been cancelled!").build() Data.Builder().putString("output", "Download has been cancelled!").build()
) )
}else{ }else{
@ -225,7 +225,7 @@ class DownloadWorker(
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
) )
return Result.failure( return Result.success(
Data.Builder().putString("output", it.toString()).build() Data.Builder().putString("output", it.toString()).build()
) )
} }

View file

@ -39,6 +39,9 @@ class UpdateYTDLWorker(
workerParams: WorkerParameters workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) { ) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
val notification = NotificationUtil(context).showYTDLUpdateNotification()
val foregroundInfo = ForegroundInfo(System.currentTimeMillis().toInt(), notification)
setForeground(foregroundInfo)
UpdateUtil(context).updateYoutubeDL() UpdateUtil(context).updateYoutubeDL()
return Result.success() return Result.success()
} }

View file

@ -49,7 +49,6 @@
app:icon="@drawable/exomedia_ic_play_arrow_white" app:icon="@drawable/exomedia_ic_play_arrow_white"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:indeterminate="true" android:indeterminate="true"
android:translationZ="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout" app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -67,7 +66,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:indeterminate="true" android:indeterminate="true"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
android:translationZ="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout" app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -72,6 +72,7 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:textIsSelectable="true"
android:id="@+id/currentText" android:id="@+id/currentText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -15,7 +15,7 @@ buildscript {
// supports java 1.6 // supports java 1.6
commonsCompressVer = '1.12' commonsCompressVer = '1.12'
youtubedlAndroidVer = "23b26d55f8" youtubedlAndroidVer = "23b26d55f8"
workVer = "2.8.1" workVer = "2.7.0"
composeVer = '1.4.2' composeVer = '1.4.2'
kotlinVer = "1.7.21" kotlinVer = "1.7.21"
coroutineVer = "1.6.4" coroutineVer = "1.6.4"