many more changes lol
This commit is contained in:
parent
0c1c06bedb
commit
cf9e584e33
29 changed files with 524 additions and 622 deletions
|
|
@ -0,0 +1,8 @@
|
|||
package com.deniscerri.ytdl.database.models
|
||||
|
||||
data class YoutubeGeneratePoTokenItem(
|
||||
var enabled: Boolean,
|
||||
var clients: MutableList<String>,
|
||||
var poTokens: MutableList<YoutubePoTokenItem>,
|
||||
var visitorData: String
|
||||
)
|
||||
|
|
@ -5,8 +5,7 @@ data class YoutubePlayerClientItem(
|
|||
var poTokens: MutableList<YoutubePoTokenItem>,
|
||||
var enabled: Boolean = true,
|
||||
var useOnlyPoToken: Boolean = false,
|
||||
var urlRegex: MutableList<String> = mutableListOf(),
|
||||
var autoGenerated: Boolean = false
|
||||
var urlRegex: MutableList<String> = mutableListOf()
|
||||
)
|
||||
|
||||
data class YoutubePoTokenItem(
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
|||
card.findViewById<TextView>(R.id.title).text = item!!.title
|
||||
card.findViewById<TextView>(R.id.author).text = item.author
|
||||
card.findViewById<TextView>(R.id.duration).text = item.duration
|
||||
card.findViewById<TextView>(R.id.index).text = ((item.playlistIndex ?: (position + 1))).toString()
|
||||
|
||||
// CHECKBOX ----------------------------------
|
||||
val check = card.findViewById<CheckBox>(R.id.checkBox)
|
||||
|
|
|
|||
|
|
@ -91,8 +91,6 @@ class YoutubePlayerClientAdapter(onItemClickListener: OnItemClickListener, activ
|
|||
title.alpha = if (item.enabled) 1f else 0.3f
|
||||
content.alpha = if (item.enabled) 1f else 0.3f
|
||||
|
||||
card.findViewById<MaterialSwitch>(R.id.autoGeneratedNewPipe).isVisible = item.autoGenerated
|
||||
|
||||
val switch = card.findViewById<MaterialSwitch>(R.id.materialSwitch)
|
||||
switch.isChecked = item.enabled
|
||||
switch.setOnClickListener {
|
||||
|
|
|
|||
|
|
@ -150,12 +150,12 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
startTextInput = view.findViewById(R.id.from_textinput_edittext)
|
||||
startTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
||||
startTextInput.imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
startTextInput.inputType = EditorInfo.TYPE_NUMBER_FLAG_DECIMAL
|
||||
startTextInput.inputType = EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
startTextInput.maxLines = 1
|
||||
endTextInput = view.findViewById(R.id.to_textinput_edittext)
|
||||
endTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
||||
endTextInput.imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
endTextInput.inputType = EditorInfo.TYPE_NUMBER_FLAG_DECIMAL
|
||||
endTextInput.inputType = EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
endTextInput.maxLines = 1
|
||||
|
||||
cancelBtn = view.findViewById(R.id.cancelButton)
|
||||
|
|
|
|||
|
|
@ -1,27 +1,19 @@
|
|||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources.Theme
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.core.view.forEach
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
|
|
@ -34,13 +26,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.MainActivity
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.databinding.NavOptionsItemBinding
|
||||
import com.deniscerri.ytdl.ui.adapter.NavBarOptionsAdapter
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.ThemeUtil
|
||||
import com.deniscerri.ytdl.util.ThemeUtil.getThemeColor
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.UpdateUtil
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
@ -54,6 +44,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
private var updateUtil: UpdateUtil? = null
|
||||
private var activeDownloadCount = 0
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.general_preferences, rootKey)
|
||||
NavbarUtil.init(requireContext())
|
||||
|
|
@ -82,7 +73,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
|
||||
findPreference<Preference>("label_visibility")?.apply {
|
||||
isVisible = !resources.getBoolean(R.bool.uses_side_nav)
|
||||
setOnPreferenceChangeListener { preference, newValue ->
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
ThemeUtil.recreateMain()
|
||||
true
|
||||
}
|
||||
|
|
@ -98,9 +89,9 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
val options = NavbarUtil.getNavBarItems(requireContext())
|
||||
|
||||
val optionsRecycler = binding.findViewById<RecyclerView>(R.id.options_recycler)
|
||||
var adapter : NavBarOptionsAdapter? = null;
|
||||
val adapter : NavBarOptionsAdapter?
|
||||
|
||||
val onitemClick = object: NavBarOptionsAdapter.OnItemClickListener {
|
||||
val onItemClick = object: NavBarOptionsAdapter.OnItemClickListener {
|
||||
override fun onNavBarOptionDeselected(item: NavOptionsItemBinding) {
|
||||
optionsRecycler.findViewHolderForLayoutPosition(0)?.apply {
|
||||
(this as NavBarOptionsAdapter.NavBarOptionsViewHolder).apply {
|
||||
|
|
@ -113,7 +104,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
adapter = NavBarOptionsAdapter(
|
||||
options.toMutableList(),
|
||||
NavbarUtil.getStartFragmentId(requireContext()),
|
||||
onitemClick
|
||||
onItemClick
|
||||
)
|
||||
|
||||
val itemTouchCallback = object : ItemTouchHelper.Callback() {
|
||||
|
|
@ -257,20 +248,12 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("piped_instance")?.setOnPreferenceClickListener {
|
||||
UiUtil.showPipedInstancesDialog(requireActivity(), preferences.getString("piped_instance", "")!!){
|
||||
editor.putString("piped_instance", it)
|
||||
editor.apply()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<MultiSelectListPreference>("hide_thumbnails")?.apply {
|
||||
values.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValues ->
|
||||
(newValues as Set<String>).filter { it.isNotBlank() }.apply {
|
||||
(newValues as Set<*>).map { it as String }.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
true
|
||||
|
|
@ -282,7 +265,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValues ->
|
||||
(newValues as Set<String>).filter { it.isNotBlank() }.apply {
|
||||
(newValues as Set<*>).map { it as String }.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
true
|
||||
|
|
@ -360,7 +343,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
if (newValues.size == entries.size) {
|
||||
summary = "${s}\n[${getString(R.string.all)}]"
|
||||
}else if (newValues.isNotEmpty()) {
|
||||
val indexes = newValues.mapIndexed { index, _ -> index }
|
||||
val indexes = List(newValues.size) { index -> index }
|
||||
summary = "${s}\n[${entries.filterIndexed { index, _ -> indexes.contains(index) }.joinToString(", ")}]"
|
||||
}else{
|
||||
summary = s
|
||||
|
|
@ -390,18 +373,9 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
super.onResume()
|
||||
}
|
||||
|
||||
private fun restartApp() {
|
||||
val intent = Intent(requireContext(), MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
requireActivity().finishAffinity()
|
||||
activity?.finishAffinity()
|
||||
}
|
||||
|
||||
private var displayOverAppsResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
) { _ ->
|
||||
findNavController().popBackStack(R.id.appearanceSettingsFragment, false)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,51 +39,9 @@ class AdvancedSettingsFragment : BaseSettingsFragment() {
|
|||
false
|
||||
}
|
||||
|
||||
val newPipeUtil = NewPipeUtil(requireContext())
|
||||
|
||||
findPreference<SwitchPreferenceCompat>("use_newpipe_potoken")?.apply {
|
||||
fun getValues() : Triple<String, String, String> {
|
||||
val gvs = prefs.getString("newpipe_gvs_potoken", "")!!
|
||||
val player = prefs.getString("newpipe_player_potoken", "")!!
|
||||
val visitorData = prefs.getString("newpipe_visitordata", "")!!
|
||||
|
||||
return Triple(gvs, player, visitorData)
|
||||
}
|
||||
|
||||
fun updateSummary() : String {
|
||||
val values = getValues()
|
||||
val gvs = values.first
|
||||
val player = values.second
|
||||
val visitorData = values.third
|
||||
|
||||
return if (visitorData.isNotBlank()) {
|
||||
"PO Token (GVS): ${gvs.take(10)}...\nPO Token (Player): ${player.take(10)}...\nVisitor Data: ${visitorData.take(10)}..."
|
||||
}else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
summary = updateSummary()
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
if (this.isChecked) {
|
||||
this.isChecked = false
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.use_newpipe_potoken), getString(R.string.use_newpipe_potoken_warning)) {
|
||||
editor.putBoolean("use_cookies", false)
|
||||
editor.putBoolean("use_newpipe_token", true)
|
||||
editor.apply()
|
||||
this.isChecked = true
|
||||
lifecycleScope.launch {
|
||||
summary = getString(R.string.loading)
|
||||
withContext(Dispatchers.IO) {
|
||||
newPipeUtil.testRun()
|
||||
}
|
||||
summary = updateSummary()
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
findPreference<Preference>("generate_po_tokens")?.setOnPreferenceClickListener {
|
||||
findNavController().navigate(R.id.generateYoutubePoTokensFragment)
|
||||
false
|
||||
}
|
||||
|
||||
val formatImportanceAudio: Preference? = findPreference("format_importance_audio")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
package com.deniscerri.ytdl.ui.more.settings.advanced
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.YoutubeGeneratePoTokenItem
|
||||
import com.deniscerri.ytdl.database.models.YoutubePoTokenItem
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
|
||||
import com.deniscerri.ytdl.util.Extensions.getIDFromYoutubeURL
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.PoTokenGenerator
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.materialswitch.MaterialSwitch
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class GenerateYoutubePoTokensFragment : Fragment() {
|
||||
private lateinit var settingsActivity: SettingsActivity
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private lateinit var configuration : MutableList<YoutubeGeneratePoTokenItem>
|
||||
private lateinit var poTokenGenerator: PoTokenGenerator
|
||||
|
||||
private val sampleURL = "https://www.youtube.com/watch?v=aqz-KE-bpKQ" // Big Buck Bunny
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
settingsActivity = activity as SettingsActivity
|
||||
settingsActivity.changeTopAppbarTitle(getString(R.string.generate_potokens))
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
poTokenGenerator = PoTokenGenerator()
|
||||
return inflater.inflate(R.layout.fragment_generate_youtube_po_token, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
configuration = Gson().fromJson(
|
||||
preferences.getString("youtube_generated_po_tokens", "[]"),
|
||||
Array<YoutubeGeneratePoTokenItem>::class.java
|
||||
).toMutableList()
|
||||
|
||||
initWeb()
|
||||
}
|
||||
|
||||
private fun initWeb() {
|
||||
val conf = configuration.find { it.clients.any { it2 -> it2.contains("web") } }
|
||||
?: YoutubeGeneratePoTokenItem(false, mutableListOf("mweb"), mutableListOf(), "")
|
||||
|
||||
|
||||
val switch = requireView().findViewById<MaterialSwitch>(R.id.web_client_switch)
|
||||
val gvs = requireView().findViewById<TextView>(R.id.content_gvs)
|
||||
val player = requireView().findViewById<TextView>(R.id.content_player)
|
||||
val visitorData = requireView().findViewById<TextView>(R.id.content_visitordata)
|
||||
|
||||
val playerClientDiv = requireView().findViewById<View>(R.id.playerclient_div)
|
||||
val playerClientText = requireView().findViewById<TextView>(R.id.content_playerclient)
|
||||
|
||||
val regenerate = requireView().findViewById<MaterialButton>(R.id.regenerate_webview_potokens)
|
||||
|
||||
switch.isChecked = conf.enabled
|
||||
|
||||
fun setValues(conf: YoutubeGeneratePoTokenItem) {
|
||||
gvs.text = conf.poTokens.find { it.context == "gvs" }?.token ?: ""
|
||||
player.text = conf.poTokens.find { it.context == "player" }?.token ?: ""
|
||||
visitorData.text = conf.visitorData
|
||||
playerClientText.text = conf.clients.joinToString(", ")
|
||||
}
|
||||
|
||||
setValues(conf)
|
||||
|
||||
val clicker = View.OnClickListener {
|
||||
UiUtil.copyToClipboard((it as TextView).text.toString(), requireActivity())
|
||||
}
|
||||
|
||||
gvs.setOnClickListener(clicker)
|
||||
player.setOnClickListener(clicker)
|
||||
visitorData.setOnClickListener(clicker)
|
||||
|
||||
playerClientDiv.setOnClickListener {
|
||||
val webClients = requireContext().getStringArray(R.array.web_player_clients)
|
||||
|
||||
val selectedItems = webClients.map {
|
||||
conf.clients.contains(it)
|
||||
}.toBooleanArray()
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(getString(R.string.player_client))
|
||||
.setMultiChoiceItems(webClients, selectedItems) { _, which, isChecked ->
|
||||
selectedItems[which] = isChecked
|
||||
}
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
val newValues = webClients
|
||||
.filterIndexed { index, _ -> selectedItems[index] }
|
||||
.toMutableSet()
|
||||
|
||||
configuration.remove(conf)
|
||||
conf.clients.clear()
|
||||
conf.clients.addAll(newValues)
|
||||
configuration.add(conf)
|
||||
preferences.edit().putString("youtube_generated_po_tokens", Gson().toJson(configuration).toString()).apply()
|
||||
setValues(conf)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
regenerate.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
val res = withContext(Dispatchers.IO) {
|
||||
poTokenGenerator.getWebClientPoToken(sampleURL.getIDFromYoutubeURL())
|
||||
}
|
||||
|
||||
if (res == null) {
|
||||
Snackbar.make(requireView(), getString(R.string.network_error), Snackbar.LENGTH_LONG).show()
|
||||
}else{
|
||||
configuration.remove(conf)
|
||||
conf.poTokens.clear()
|
||||
conf.poTokens.add(YoutubePoTokenItem("gvs", res.streamingDataPoToken ?: ""))
|
||||
conf.poTokens.add(YoutubePoTokenItem("player", res.playerRequestPoToken))
|
||||
conf.visitorData = res.visitorData
|
||||
|
||||
configuration.add(conf)
|
||||
setValues(conf)
|
||||
preferences.edit().putString("youtube_generated_po_tokens", Gson().toJson(configuration).toString()).apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch.setOnClickListener {
|
||||
configuration.remove(conf)
|
||||
conf.enabled = switch.isEnabled
|
||||
configuration.add(conf)
|
||||
preferences.edit().putString("youtube_generated_po_tokens", Gson().toJson(configuration).toString()).apply()
|
||||
regenerate.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import androidx.core.view.children
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
|
@ -94,7 +95,7 @@ class YoutubePlayerClientFragment : Fragment(), YoutubePlayerClientAdapter.OnIte
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
checkNoResults()
|
||||
|
||||
dragHandle.setOnClickListener {
|
||||
|
|
|
|||
|
|
@ -575,4 +575,21 @@ object Extensions {
|
|||
else "%(section_title&{} - |)s$this"
|
||||
}
|
||||
}
|
||||
|
||||
fun String.getIDFromYoutubeURL() : String {
|
||||
var el: Array<String?> =
|
||||
this.split("/".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
var query = el[el.size - 1]
|
||||
if (query!!.contains("watch?v=")) {
|
||||
query = query.substring(8)
|
||||
}
|
||||
el = query.split("&".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
query = el[0]
|
||||
el = query!!.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
query = el[0]
|
||||
return query!!
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,6 @@ import com.deniscerri.ytdl.ui.downloadcard.VideoCutListener
|
|||
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
|
||||
import com.deniscerri.ytdl.util.Extensions.getMediaDuration
|
||||
import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
||||
import com.deniscerri.ytdl.util.extractors.PipedApiUtil
|
||||
import com.deniscerri.ytdl.util.extractors.YTDLPUtil
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.badge.BadgeUtils
|
||||
|
|
@ -2030,81 +2029,6 @@ object UiUtil {
|
|||
|
||||
}
|
||||
|
||||
fun showPipedInstancesDialog(context: Activity, currentInstance: String, instanceSelected: (f: String) -> Unit){
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setTitle(context.getString(R.string.piped_instance))
|
||||
val view = context.layoutInflater.inflate(R.layout.filename_template_dialog, null)
|
||||
val editText = view.findViewById<EditText>(R.id.filename_edittext)
|
||||
view.findViewById<TextInputLayout>(R.id.filename).apply {
|
||||
hint = context.getString(R.string.piped_instance)
|
||||
endIconMode = END_ICON_NONE
|
||||
}
|
||||
editText.setText(currentInstance)
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(view)
|
||||
builder.setPositiveButton(
|
||||
context.getString(R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
instanceSelected(editText.text.toString())
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
context.getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
builder.setNeutralButton("?") { _: DialogInterface?, _: Int ->
|
||||
val browserIntent =
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/TeamPiped/Piped/wiki/Instances"))
|
||||
context.startActivity(browserIntent)
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.suggested).visibility = View.GONE
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editText!!.postDelayed({
|
||||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
|
||||
//handle suggestion chips
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val chipGroup = view.findViewById<ChipGroup>(R.id.filename_suggested_chipgroup)
|
||||
val chips = mutableListOf<Chip>()
|
||||
val instances = PipedApiUtil(context).getPipedInstances().ifEmpty { return@launch }
|
||||
instances.forEach { s ->
|
||||
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
|
||||
tmp.text = s
|
||||
|
||||
tmp.setOnClickListener {
|
||||
val c = it as Chip
|
||||
c.toggle()
|
||||
editText.setText(c.text.toString())
|
||||
editText.setSelection(c.text.length)
|
||||
}
|
||||
|
||||
chips.add(tmp)
|
||||
}
|
||||
withContext(Dispatchers.Main){
|
||||
view.findViewById<View>(R.id.suggested).visibility = View.VISIBLE
|
||||
chips.forEach {
|
||||
it.isChecked = editText.text == it.text
|
||||
chipGroup!!.addView(it)
|
||||
}
|
||||
|
||||
editText.doOnTextChanged { text, start, before, count ->
|
||||
chips.forEach {
|
||||
it.isChecked = editText.text == it.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
private fun showGeneratedCommand(context: Activity, preferences: SharedPreferences, command: String) {
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setTitle(context.getString(R.string.command))
|
||||
|
|
|
|||
|
|
@ -1,336 +0,0 @@
|
|||
package com.deniscerri.ytdl.util.extractors
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.text.Html
|
||||
import android.util.Log
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdl.database.models.ChapterItem
|
||||
import com.deniscerri.ytdl.database.models.Format
|
||||
import com.deniscerri.ytdl.database.models.ResultItem
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
||||
import com.google.gson.Gson
|
||||
import com.yausername.youtubedl_android.YoutubeDLException
|
||||
import kotlinx.coroutines.delay
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.util.Locale
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
class PipedApiUtil(context: Context) {
|
||||
private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
private val countryCode = sharedPreferences.getString("locale", "")!!.ifEmpty { "US" }
|
||||
private val defaultPipedURL = "https://pipedapi.kavin.rocks/"
|
||||
private val pipedURL = sharedPreferences.getString("piped_instance", "")!!.ifEmpty { defaultPipedURL }.removeSuffix("/")
|
||||
|
||||
fun getPipedInstances() : List<String> {
|
||||
kotlin.runCatching {
|
||||
val res = NetworkUtil.genericArrayRequest("https://piped-instances.kavin.rocks/")
|
||||
val list = mutableListOf<String>()
|
||||
for (i in 0 until res.length()) {
|
||||
val element = res.getJSONObject(i)
|
||||
list.add(element.getString("api_url"))
|
||||
}
|
||||
return list
|
||||
}
|
||||
return listOf()
|
||||
}
|
||||
|
||||
fun getVideoData(url : String) : Result<List<ResultItem>> {
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
val res = NetworkUtil.genericRequest("$pipedURL/streams/$id")
|
||||
if (res.length() == 0) {
|
||||
return Result.failure(Throwable())
|
||||
}
|
||||
|
||||
val vid = createVideoFromPipedJSON(res, url) ?: return Result.failure(Throwable())
|
||||
return Result.success(listOf(vid))
|
||||
}
|
||||
|
||||
fun getFormats(url: String) : Result<List<Format> > {
|
||||
try {
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
val res = NetworkUtil.genericRequest("$pipedURL/streams/$id")
|
||||
if (res.length() == 0) {
|
||||
return Result.failure(Throwable())
|
||||
}else {
|
||||
val item = createVideoFromPipedJSON(res, "https://youtube.com/watch?v=$id", true)
|
||||
return Result.success(item!!.formats)
|
||||
}
|
||||
|
||||
}catch(e: Exception) {
|
||||
println(e)
|
||||
if (e is CancellationException) throw e
|
||||
return Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFormatsForAll(urls: List<String>, progress: (progress: ResultViewModel.MultipleFormatProgress) -> Unit) : Result<MutableList<MutableList<Format>>> {
|
||||
return kotlin.runCatching {
|
||||
val formatCollection = mutableListOf<MutableList<Format>>()
|
||||
urls.forEach { url ->
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
val res = NetworkUtil.genericRequest("$pipedURL/streams/$id")
|
||||
createVideoFromPipedJSON(res, url).apply {
|
||||
formatCollection.add(this!!.formats)
|
||||
progress(
|
||||
ResultViewModel.MultipleFormatProgress(url, this.formats)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(formatCollection)
|
||||
}.onFailure {
|
||||
return Result.failure(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun search(query: String): Result<ArrayList<ResultItem>> {
|
||||
val items = arrayListOf<ResultItem>()
|
||||
val data = NetworkUtil.genericRequest("$pipedURL/search?q=$query&filter=videos®ion=${countryCode}")
|
||||
val dataArray = data.getJSONArray("items")
|
||||
if (dataArray.length() == 0) return Result.failure(Throwable())
|
||||
for (i in 0 until dataArray.length()) {
|
||||
val element = dataArray.getJSONObject(i)
|
||||
if (element.getInt("duration") == -1) continue
|
||||
element.put("uploader", element.getString("uploaderName"))
|
||||
val v = createVideoFromPipedJSON(element, "https://youtube.com" + element.getString("url"))
|
||||
if (v == null || v.thumb.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
items.add(v)
|
||||
}
|
||||
return Result.success(items)
|
||||
}
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun searchMusic(query: String): Result<ArrayList<ResultItem>> {
|
||||
val items = arrayListOf<ResultItem>()
|
||||
val data = NetworkUtil.genericRequest("$pipedURL/search?q=$query=&filter=music_songs®ion=${countryCode}")
|
||||
val dataArray = data.getJSONArray("items")
|
||||
if (dataArray.length() == 0) return Result.failure(Throwable())
|
||||
for (i in 0 until dataArray.length()) {
|
||||
val element = dataArray.getJSONObject(i)
|
||||
if (element.getInt("duration") == -1) continue
|
||||
element.put("uploader", element.getString("uploaderName"))
|
||||
val v = createVideoFromPipedJSON(element, "https://youtube.com" + element.getString("url"))
|
||||
if (v == null || v.thumb.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
items.add(v)
|
||||
}
|
||||
return Result.success(items)
|
||||
}
|
||||
|
||||
fun getStreamingUrlAndChapters(url: String) : Result<Pair<List<String>, List<ChapterItem>?>> {
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
val res = NetworkUtil.genericRequest("$pipedURL/streams/$id")
|
||||
if (res.length() == 0) {
|
||||
throw Exception()
|
||||
}else{
|
||||
val item = createVideoFromPipedJSON(res, url)
|
||||
if (item!!.urls.isBlank()) return Result.failure(Throwable())
|
||||
|
||||
val urls = item.urls.split(",")
|
||||
val chapters = item.chapters
|
||||
return Result.success(Pair(urls, chapters))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getPlaylistData(id: String, progress: (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> {
|
||||
val totalItems = mutableListOf<ResultItem>()
|
||||
val nextPageToken = ""
|
||||
var playlistName = ""
|
||||
|
||||
while (true) {
|
||||
val items = mutableListOf<ResultItem>()
|
||||
|
||||
var url = ""
|
||||
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
||||
else """$pipedURL/nextpage/playlists/$id?nextpage=${
|
||||
nextPageToken.replace(
|
||||
"&prettyPrint",
|
||||
"%26prettyPrint"
|
||||
)
|
||||
}"""
|
||||
|
||||
println(url)
|
||||
|
||||
val res = NetworkUtil.genericRequest(url)
|
||||
if (!res.has("relatedStreams")) throw Exception()
|
||||
|
||||
val dataArray = res.getJSONArray("relatedStreams")
|
||||
val nextpage = res.getString("nextpage")
|
||||
val isMixPlaylist = nextPageToken.isBlank() && res.getInt("videos") < 0
|
||||
if (isMixPlaylist) throw YoutubeDLException("This playlist type is unviewable.")
|
||||
|
||||
for (i in 0 until dataArray.length()) {
|
||||
kotlin.runCatching {
|
||||
val obj = dataArray.getJSONObject(i)
|
||||
createVideoFromPipedJSON(
|
||||
obj,
|
||||
"https://youtube.com" + obj.getString("url")
|
||||
)?.apply {
|
||||
playlistTitle = playlistName.ifEmpty { runCatching { res.getString("name") }.getOrElse { "" } }
|
||||
playlistURL = "https://www.youtube.com/playlist?list=$id"
|
||||
items.add(this)
|
||||
if (playlistTitle.isNotBlank() && playlistName.isNotBlank()) {
|
||||
playlistName = playlistTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progress(items)
|
||||
delay(1000)
|
||||
totalItems.addAll(items)
|
||||
if (nextpage == "null") break
|
||||
}
|
||||
|
||||
return Result.success(totalItems)
|
||||
}
|
||||
|
||||
|
||||
fun getTrending(): ArrayList<ResultItem> {
|
||||
val items = arrayListOf<ResultItem>()
|
||||
val url = "$pipedURL/trending?region=${countryCode}"
|
||||
val res = NetworkUtil.genericArrayRequest(url)
|
||||
for (i in 0 until res.length()) {
|
||||
val element = res.getJSONObject(i)
|
||||
if (element.getInt("duration") < 0) continue
|
||||
element.put("uploader", element.getString("uploaderName"))
|
||||
val v = createVideoFromPipedJSON(element, "https://youtube.com" + element.getString("url"))
|
||||
if (v == null || v.thumb.isEmpty()) continue
|
||||
items.add(v)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
fun getChannelData(
|
||||
url: String,
|
||||
progress: (pagedResults: MutableList<ResultItem>) -> Unit
|
||||
): Result<List<ResultItem>> {
|
||||
return Result.failure(Throwable("Not yet implemented / supported?"))
|
||||
}
|
||||
|
||||
private fun createVideoFromPipedJSON(obj: JSONObject, url: String, ignoreFormatPreference : Boolean = false): ResultItem? {
|
||||
var video: ResultItem? = null
|
||||
try {
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
val title = Html.fromHtml(obj.getString("title").toString()).toString()
|
||||
val author = try {
|
||||
Html.fromHtml(obj.getString("uploader").toString()).toString()
|
||||
}catch (e: Exception){
|
||||
Html.fromHtml(obj.getString("uploaderName").toString()).toString()
|
||||
}.removeSuffix(" - Topic")
|
||||
|
||||
val duration = obj.getInt("duration").toStringDuration(Locale.US)
|
||||
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
|
||||
val formats : ArrayList<Format> = ArrayList()
|
||||
|
||||
if(sharedPreferences.getString("formats_source", "yt-dlp") == "piped" || ignoreFormatPreference){
|
||||
if (obj.has("audioStreams")){
|
||||
val formatsInJSON = obj.getJSONArray("audioStreams")
|
||||
for (f in 0 until formatsInJSON.length()){
|
||||
val format = formatsInJSON.getJSONObject(f)
|
||||
if (format.getInt("bitrate") == 0) continue
|
||||
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
||||
try{
|
||||
formatObj.acodec = format.getString("codec")
|
||||
formatObj.asr = format.getString("quality")
|
||||
if (! format.getString("audioTrackName").equals("null", ignoreCase = true)){
|
||||
formatObj.format_note = format.getString("audioTrackName") + " Audio, " + formatObj.format_note
|
||||
}else{
|
||||
formatObj.format_note = formatObj.format_note + " Audio"
|
||||
}
|
||||
if (!formatObj.tbr.isNullOrBlank()){
|
||||
formatObj.tbr = (formatObj.tbr!!.toInt() / 1000).toString() + "k"
|
||||
}
|
||||
|
||||
}catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
formats.add(formatObj)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (obj.has("videoStreams")){
|
||||
val formatsInJSON = obj.getJSONArray("videoStreams")
|
||||
for (f in 0 until formatsInJSON.length()){
|
||||
val format = formatsInJSON.getJSONObject(f)
|
||||
if (format.getInt("bitrate") == 0) continue
|
||||
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
||||
try{
|
||||
formatObj.vcodec = format.getString("codec")
|
||||
if (!formatObj.tbr.isNullOrBlank()){
|
||||
formatObj.tbr = (formatObj.tbr!!.toInt() / 1000).toString() + "k"
|
||||
}
|
||||
}catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
formats.add(formatObj)
|
||||
}
|
||||
|
||||
}
|
||||
formats.groupBy { it.format_id }.forEach {
|
||||
if (it.value.count() > 1) {
|
||||
it.value.filter { f-> !f.format_note.contains("original", true) }.forEachIndexed { index, format -> format.format_id = format.format_id.split("-")[0] + "-${index}" }
|
||||
val defaultLang = it.value.find { f -> f.format_note.contains("original", true) }
|
||||
defaultLang?.format_id = (defaultLang?.format_id?.split("-")?.get(0) ?: "") + "-${it.value.size-1}"
|
||||
}
|
||||
}
|
||||
formats.sortByDescending { it.filesize }
|
||||
}
|
||||
|
||||
val chapters = ArrayList<ChapterItem>()
|
||||
if (obj.has("chapters") && obj.getJSONArray("chapters").length() > 0){
|
||||
val chaptersJArray = obj.getJSONArray("chapters")
|
||||
for (c in 0 until chaptersJArray.length()){
|
||||
val chapter = chaptersJArray.getJSONObject(c)
|
||||
val end = if (c == chaptersJArray.length() - 1) obj.getInt("duration") else chaptersJArray.getJSONObject(c+1).getInt("start")
|
||||
val item = ChapterItem(chapter.getInt("start").toLong(), end.toLong(), chapter.getString("title"))
|
||||
chapters.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
video = ResultItem(0,
|
||||
url,
|
||||
title,
|
||||
author,
|
||||
duration,
|
||||
thumb,
|
||||
"youtube",
|
||||
"",
|
||||
formats,
|
||||
if (obj.has("hls") && obj.getString("hls") != "null") obj.getString("hls") else "",
|
||||
chapters
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("PipedAPIUtil", e.toString())
|
||||
}
|
||||
return video
|
||||
}
|
||||
|
||||
private fun getIDFromYoutubeURL(inputQuery: String) : String {
|
||||
var el: Array<String?> =
|
||||
inputQuery.split("/".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
var query = el[el.size - 1]
|
||||
if (query!!.contains("watch?v=")) {
|
||||
query = query.substring(8)
|
||||
}
|
||||
el = query.split("&".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
query = el[0]
|
||||
el = query!!.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
query = el[0]
|
||||
return query!!
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.deniscerri.ytdl.database.models.ChapterItem
|
|||
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||
import com.deniscerri.ytdl.database.models.Format
|
||||
import com.deniscerri.ytdl.database.models.ResultItem
|
||||
import com.deniscerri.ytdl.database.models.YoutubeGeneratePoTokenItem
|
||||
import com.deniscerri.ytdl.database.models.YoutubePlayerClientItem
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
|
|
@ -635,6 +636,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
|
|||
if (aria2) {
|
||||
request.addOption("--downloader", "libaria2c.so")
|
||||
//request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
|
||||
request.addOption("--external-downloader-args", "aria2c:\"--check-certificate=false\"")
|
||||
}
|
||||
|
||||
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
||||
|
|
@ -1296,18 +1298,23 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
|
|||
}
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("use_newpipe_potoken", false)) {
|
||||
val visitorData = sharedPreferences.getString("newpipe_visitordata", "")!!
|
||||
if (visitorData.isNotBlank()) {
|
||||
playerClients.add("web")
|
||||
sharedPreferences.getString("newpipe_gvs_potoken", "")?.apply {
|
||||
if (this.isNotBlank()) poTokens.add("web.gvs+$this")
|
||||
val generatedPoTokensRaw = sharedPreferences.getString("youtube_generated_po_tokens", "[]")
|
||||
val generatedPoTokens = Gson().fromJson(generatedPoTokensRaw,Array<YoutubeGeneratePoTokenItem>::class.java).toMutableList()
|
||||
if (generatedPoTokens.isNotEmpty()) {
|
||||
for (value in generatedPoTokens) {
|
||||
if (value.enabled) {
|
||||
for (cl in value.clients) {
|
||||
playerClients.add(cl)
|
||||
for (pt in value.poTokens) {
|
||||
if (pt.token.isNotBlank()) {
|
||||
poTokens.add("${cl}.${pt.context}+${pt.token}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extractorArgs.add("player-skip=webpage,configs")
|
||||
extractorArgs.add("visitor_data=${value.visitorData}")
|
||||
}
|
||||
sharedPreferences.getString("newpipe_player_potoken", "")?.apply {
|
||||
if (this.isNotBlank()) poTokens.add("web.player+$this")
|
||||
}
|
||||
extractorArgs.add("player-skip=webpage,configs")
|
||||
extractorArgs.add("visitor_data=$visitorData")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import com.deniscerri.ytdl.database.models.ChapterItem
|
|||
import com.deniscerri.ytdl.database.models.Format
|
||||
import com.deniscerri.ytdl.database.models.ResultItem
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.util.Extensions.getIDFromYoutubeURL
|
||||
import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
||||
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.NewPipePoTokenGenerator
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.PoTokenGenerator
|
||||
import com.google.gson.Gson
|
||||
import okhttp3.OkHttpClient
|
||||
import org.json.JSONException
|
||||
|
|
@ -35,16 +36,10 @@ class NewPipeUtil(context: Context) {
|
|||
private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
private val countryCode = sharedPreferences.getString("locale", "")!!.ifEmpty { "US" }
|
||||
private val language = sharedPreferences.getString("app_language", "")!!.ifEmpty { "en" }
|
||||
private val testURL = "https://www.youtube.com/watch?v=aqz-KE-bpKQ" //bbb
|
||||
|
||||
init {
|
||||
NewPipe.init(NewPipeDownloaderImpl(OkHttpClient.Builder()), Localization(language, countryCode))
|
||||
YoutubeStreamExtractor.setPoTokenProvider(NewPipePoTokenGenerator())
|
||||
}
|
||||
|
||||
fun testRun() {
|
||||
getVideoData(testURL)
|
||||
return
|
||||
YoutubeStreamExtractor.setPoTokenProvider(PoTokenGenerator())
|
||||
}
|
||||
|
||||
fun getVideoData(url : String) : Result<List<ResultItem>> {
|
||||
|
|
@ -288,7 +283,7 @@ class NewPipeUtil(context: Context) {
|
|||
private fun createVideoFromStreamInfoItem(stream: StreamInfoItem, url: String) : ResultItem? {
|
||||
var video: ResultItem? = null
|
||||
try {
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
val id = url.getIDFromYoutubeURL()
|
||||
val title = stream.name
|
||||
val author = stream.uploaderName.removeSuffix(" - Topic")
|
||||
val duration = stream.duration.toInt().toStringDuration(Locale.US)
|
||||
|
|
@ -316,7 +311,7 @@ class NewPipeUtil(context: Context) {
|
|||
private fun createVideoFromStream(stream: StreamInfo, url: String, ignoreFormatPreference : Boolean = false): ResultItem? {
|
||||
var video: ResultItem? = null
|
||||
try {
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
val id = url.getIDFromYoutubeURL()
|
||||
val title = stream.name
|
||||
val author = stream.uploaderName.removeSuffix(" - Topic")
|
||||
val duration = stream.duration.toInt().toStringDuration(Locale.US)
|
||||
|
|
@ -420,27 +415,4 @@ class NewPipeUtil(context: Context) {
|
|||
}
|
||||
return video
|
||||
}
|
||||
|
||||
private fun getIDFromYoutubeURL(inputQuery: String) : String {
|
||||
var el: Array<String?> =
|
||||
inputQuery.split("/".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
var query = el[el.size - 1]
|
||||
if (query!!.contains("watch?v=")) {
|
||||
query = query.substring(8)
|
||||
}
|
||||
el = query.split("&".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
query = el[0]
|
||||
el = query!!.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
query = el[0]
|
||||
return query!!
|
||||
}
|
||||
|
||||
private fun generatePoToken(url: String) : PoTokenResult? {
|
||||
val generator = NewPipePoTokenGenerator()
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
return generator.getWebClientPoToken(id)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.deniscerri.ytdl.util.extractors.newpipe.potoken
|
||||
package com.deniscerri.ytdl.util.extractors.potoken
|
||||
|
||||
class PoTokenException(message: String) : Exception(message)
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.deniscerri.ytdl.util.extractors.newpipe.potoken
|
||||
package com.deniscerri.ytdl.util.extractors.potoken
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
|
@ -7,22 +7,16 @@ import android.webkit.CookieManager
|
|||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdl.App
|
||||
import com.deniscerri.ytdl.BuildConfig
|
||||
import com.deniscerri.ytdl.database.models.YoutubePlayerClientItem
|
||||
import com.deniscerri.ytdl.database.models.YoutubePoTokenItem
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.webview.PoTokenWebView
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.schabi.newpipe.extractor.NewPipe
|
||||
import org.schabi.newpipe.extractor.services.youtube.InnertubeClientRequestInfo
|
||||
import org.schabi.newpipe.extractor.services.youtube.PoTokenProvider
|
||||
import org.schabi.newpipe.extractor.services.youtube.PoTokenResult
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
|
||||
|
||||
class NewPipePoTokenGenerator : PoTokenProvider {
|
||||
val TAG = NewPipePoTokenGenerator::class.simpleName
|
||||
class PoTokenGenerator : PoTokenProvider {
|
||||
val TAG = PoTokenGenerator::class.simpleName
|
||||
private val supportsWebView by lazy { runCatching { CookieManager.getInstance() }.isSuccess }
|
||||
|
||||
private object WebPoTokenGenLock
|
||||
|
|
@ -39,15 +33,6 @@ class NewPipePoTokenGenerator : PoTokenProvider {
|
|||
val result = kotlin.runCatching {
|
||||
getWebClientPoToken(videoId, false)
|
||||
}
|
||||
|
||||
result.getOrNull()?.apply {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(App.instance)
|
||||
val editor = preferences.edit()
|
||||
editor.putString("newpipe_gvs_potoken", this.streamingDataPoToken ?: "")
|
||||
editor.putString("newpipe_player_potoken", this.playerRequestPoToken)
|
||||
editor.putString("newpipe_visitordata", this.visitorData)
|
||||
editor.apply()
|
||||
}
|
||||
return result.getOrNull()
|
||||
}
|
||||
|
||||
|
|
@ -84,8 +69,7 @@ class NewPipePoTokenGenerator : PoTokenProvider {
|
|||
webPoTokenGenerator?.let { Handler(Looper.getMainLooper()).post { it.close() } }
|
||||
|
||||
// create a new webPoTokenGenerator
|
||||
webPoTokenGenerator = PoTokenWebView
|
||||
.getNewPoTokenGenerator(App.instance)
|
||||
webPoTokenGenerator = PoTokenWebView.getNewPoTokenGenerator(App.instance)
|
||||
|
||||
// The streaming poToken needs to be generated exactly once before generating
|
||||
// any other (player) tokens.
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdl.util.extractors.newpipe.potoken
|
||||
package com.deniscerri.ytdl.util.extractors.potoken.webview
|
||||
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.PoTokenException
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.deniscerri.ytdl.util.extractors.newpipe.potoken
|
||||
package com.deniscerri.ytdl.util.extractors.potoken.webview
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
|
|
@ -18,10 +18,13 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.deniscerri.ytdl.BuildConfig
|
||||
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.parseChallengeData
|
||||
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.parseIntegrityTokenData
|
||||
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.stringToU8
|
||||
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.u8ToBase64
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.BadWebViewException
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.PoTokenException
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.buildExceptionForJsError
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.webview.JavascriptUtil.parseChallengeData
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.webview.JavascriptUtil.parseIntegrityTokenData
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.webview.JavascriptUtil.stringToU8
|
||||
import com.deniscerri.ytdl.util.extractors.potoken.webview.JavascriptUtil.u8ToBase64
|
||||
import okhttp3.Headers.Companion.toHeaders
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
|
@ -39,8 +42,7 @@ class PoTokenWebView private constructor(
|
|||
) {
|
||||
private val webView = WebView(context)
|
||||
private val scope = MainScope()
|
||||
private val poTokenContinuations =
|
||||
Collections.synchronizedMap(ArrayMap<String, Continuation<String>>())
|
||||
private val poTokenContinuations = Collections.synchronizedMap(ArrayMap<String, Continuation<String>>())
|
||||
private val exceptionHandler = CoroutineExceptionHandler { _, t ->
|
||||
onInitializationErrorCloseAndCancel(t)
|
||||
}
|
||||
|
|
@ -60,13 +62,6 @@ class PoTokenWebView private constructor(
|
|||
// so that we can run async functions and get back the result
|
||||
webView.addJavascriptInterface(this, JS_INTERFACE)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
preferences.getString("api_key", "")?.apply {
|
||||
if (this.isNotBlank()) {
|
||||
GOOGLE_API_KEY = this
|
||||
}
|
||||
}
|
||||
|
||||
webView.webChromeClient = object : WebChromeClient() {
|
||||
override fun onConsoleMessage(m: ConsoleMessage): Boolean {
|
||||
if (m.message().contains("Uncaught")) {
|
||||
|
|
@ -330,7 +325,7 @@ class PoTokenWebView private constructor(
|
|||
|
||||
companion object {
|
||||
private const val TAG = "PoTokenWebView"
|
||||
//libretube api key, if user has his own api key his will be used
|
||||
//libretube api key
|
||||
private var GOOGLE_API_KEY = "AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw"
|
||||
private const val REQUEST_KEY = "O43z0dpjhgX20SCx4KAo"
|
||||
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||
5
app/src/main/res/drawable/baseline_stars_24.xml
Normal file
5
app/src/main/res/drawable/baseline_stars_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?android:colorAccent" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM16.23,18L12,15.45 7.77,18l1.12,-4.81 -3.73,-3.23 4.92,-0.42L12,5l1.92,4.53 4.92,0.42 -3.73,3.23L16.23,18z"/>
|
||||
|
||||
</vector>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView android:layout_width="match_parent"
|
||||
<androidx.core.widget.NestedScrollView xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:scrollbars="none"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -200,7 +201,8 @@
|
|||
android:paddingHorizontal="10dp"
|
||||
android:text="-"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
|
|
|
|||
228
app/src/main/res/layout/fragment_generate_youtube_po_token.xml
Normal file
228
app/src/main/res/layout/fragment_generate_youtube_po_token.xml
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.deniscerri.ytdl.ui.more.terminal.TerminalActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/web_client_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:background="?attr/colorSurfaceContainer"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:layout_height="70dp"
|
||||
android:checked="false"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:text="Web Client Po Token"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:checkedIcon="@null"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
app:strokeWidth="0dp"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="15dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_gvs"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:text="PO Token (GVS)"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content_gvs"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title_gvs" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:checkedIcon="@null"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
app:strokeWidth="0dp"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="15dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_player"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:text="PO Token (Player)"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content_player"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title_player" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:checkedIcon="@null"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
app:strokeWidth="0dp"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="15dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_visitordata"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:text="Visitor Data"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content_visitordata"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title_visitordata" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:checkedIcon="@null"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
app:strokeWidth="0dp"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/playerclient_div"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="15dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_playerclients"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/player_client"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content_playerclient"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title_playerclients" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/regenerate_webview_potokens"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:text="@string/regenerate"
|
||||
android:outlineProvider="none"
|
||||
android:layout_gravity="end"
|
||||
android:layout_margin="20dp"
|
||||
app:icon="@drawable/baseline_stars_24"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -61,27 +61,6 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/autoGeneratedNewPipe"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
|
||||
android:layout_width="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="NewPipe Autogenerated"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorAccent"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintTop_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/urlRegex"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,26 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/index"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:layout_margin="5dp"
|
||||
android:background="#80000000"
|
||||
android:gravity="center"
|
||||
android:textStyle="bold"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="10dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintTop_toTopOf="@+id/downloads_image_view"
|
||||
app:layout_constraintStart_toStartOf="@+id/downloads_image_view" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/duration"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@
|
|||
<action
|
||||
android:id="@+id/action_advancedSettingsFragment_to_youtubePlayerClientFragment"
|
||||
app:destination="@id/youtubePlayerClientFragment" />
|
||||
<action
|
||||
android:id="@+id/action_advancedSettingsFragment_to_generateYoutubePoTokensFragment"
|
||||
app:destination="@id/generateYoutubePoTokensFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/youtubePlayerClientFragment"
|
||||
|
|
@ -65,4 +68,8 @@
|
|||
android:id="@+id/changeLogFragment"
|
||||
android:name="com.deniscerri.ytdl.ui.more.settings.updating.ChangeLogFragment"
|
||||
android:label="ChangeLogFragment" />
|
||||
<fragment
|
||||
android:id="@+id/generateYoutubePoTokensFragment"
|
||||
android:name="com.deniscerri.ytdl.ui.more.settings.advanced.GenerateYoutubePoTokensFragment"
|
||||
android:label="GenerateYoutubePoTokensFragment" />
|
||||
</navigation>
|
||||
|
|
@ -1541,4 +1541,13 @@
|
|||
<item>all</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="web_player_clients">
|
||||
<item>web</item>
|
||||
<item>mweb</item>
|
||||
<item>web_safari</item>
|
||||
<item>web_embedded</item>
|
||||
<item>web_creator</item>
|
||||
<item>web_music</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
|
@ -472,6 +472,6 @@
|
|||
<string name="automatic_backup_summary">Automatically make a backup of everything when a new version of the application is found</string>
|
||||
<string name="write_subs_when_embed_subs">Write subtitles when embedding</string>
|
||||
<string name="write_subs_when_embed_subs_summary">This will apply Save Subtitles and Save Automatic Subtitles to download the sub files and then delete them after embedding</string>
|
||||
<string name="use_newpipe_potoken">Use Autogenerated PO Tokens from NewPipe Extractor</string>
|
||||
<string name="use_newpipe_potoken_warning">By enabling this, the app will disable cookies and use the generated po tokens and visitor data as YouTube Extractor Arguments</string>
|
||||
<string name="generate_potokens">Generate PO Tokens</string>
|
||||
<string name="regenerate">Re-generate</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@
|
|||
<item name="cornerSize">7dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ShapeAppearanceOverlay.RightCorner" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSizeBottomRight">10dp</item>
|
||||
<item name="backgroundTint">#80000000</item>
|
||||
</style>
|
||||
|
||||
<style name="ShapeAppearanceOverlay.Chip.Rounded" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSizeTopLeft">12dp</item>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@
|
|||
android:summary="@string/player_client_summary"
|
||||
android:title="@string/player_client" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
android:icon="@drawable/ic_language"
|
||||
android:defaultValue="false"
|
||||
android:key="use_newpipe_potoken"
|
||||
android:title="@string/use_newpipe_potoken" />
|
||||
<Preference
|
||||
android:defaultValue=""
|
||||
android:icon="@drawable/baseline_stars_24"
|
||||
android:key="generate_po_tokens"
|
||||
android:summary="Web Client PO Token"
|
||||
android:title="@string/generate_potokens" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
|
|
|
|||
|
|
@ -102,15 +102,6 @@
|
|||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/format_source" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_dns"
|
||||
app:key="piped_instance"
|
||||
app:defaultValue=""
|
||||
app:isPreferenceVisible="false"
|
||||
android:summary="@string/piped_instance_summary"
|
||||
app:title="@string/piped_instance" />
|
||||
|
||||
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
|
|
|
|||
Loading…
Reference in a new issue