mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 07:50:31 +05:30
Merge branch 'master' into patch-5
This commit is contained in:
commit
9be8f5549b
@ -97,8 +97,6 @@ dependencies {
|
|||||||
// Do not update jackson annotations! It does not supports < API 26.
|
// Do not update jackson annotations! It does not supports < API 26.
|
||||||
implementation libs.jacksonAnnotations
|
implementation libs.jacksonAnnotations
|
||||||
|
|
||||||
implementation libs.mobileffmpeg
|
|
||||||
|
|
||||||
coreLibraryDesugaring libs.desugaring
|
coreLibraryDesugaring libs.desugaring
|
||||||
implementation libs.cronet.embedded
|
implementation libs.cronet.embedded
|
||||||
implementation libs.cronet.okhttp
|
implementation libs.cronet.okhttp
|
||||||
|
@ -193,8 +193,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
searchView.setOnCloseListener {
|
searchView.setOnCloseListener {
|
||||||
|
if (navController.currentDestination?.id == R.id.searchFragment) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
true
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
@ -13,18 +13,15 @@ import com.github.libretube.databinding.VideoRowBinding
|
|||||||
import com.github.libretube.dialogs.PlaylistOptionsDialog
|
import com.github.libretube.dialogs.PlaylistOptionsDialog
|
||||||
import com.github.libretube.dialogs.VideoOptionsDialog
|
import com.github.libretube.dialogs.VideoOptionsDialog
|
||||||
import com.github.libretube.obj.SearchItem
|
import com.github.libretube.obj.SearchItem
|
||||||
import com.github.libretube.obj.Subscribe
|
|
||||||
import com.github.libretube.preferences.PreferenceHelper
|
|
||||||
import com.github.libretube.util.ConnectionHelper
|
import com.github.libretube.util.ConnectionHelper
|
||||||
import com.github.libretube.util.NavigationHelper
|
import com.github.libretube.util.NavigationHelper
|
||||||
import com.github.libretube.util.RetrofitInstance
|
import com.github.libretube.util.SubscriptionHelper
|
||||||
import com.github.libretube.util.formatShort
|
import com.github.libretube.util.formatShort
|
||||||
import com.github.libretube.util.setWatchProgressLength
|
import com.github.libretube.util.setWatchProgressLength
|
||||||
import com.github.libretube.util.toID
|
import com.github.libretube.util.toID
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class SearchAdapter(
|
class SearchAdapter(
|
||||||
private val searchItems: MutableList<SearchItem>,
|
private val searchItems: MutableList<SearchItem>,
|
||||||
@ -128,45 +125,33 @@ class SearchAdapter(
|
|||||||
NavigationHelper.navigateChannel(root.context, item.url)
|
NavigationHelper.navigateChannel(root.context, item.url)
|
||||||
}
|
}
|
||||||
val channelId = item.url.toID()
|
val channelId = item.url.toID()
|
||||||
val token = PreferenceHelper.getToken()
|
|
||||||
|
|
||||||
// only show subscribe button if logged in
|
isSubscribed(channelId, binding)
|
||||||
if (token != "") isSubscribed(channelId, token, binding)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isSubscribed(channelId: String, token: String, binding: ChannelRowBinding) {
|
private fun isSubscribed(channelId: String, binding: ChannelRowBinding) {
|
||||||
var isSubscribed = false
|
|
||||||
|
|
||||||
// check whether the user subscribed to the channel
|
// check whether the user subscribed to the channel
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val response = try {
|
var isSubscribed = SubscriptionHelper.isSubscribed(channelId)
|
||||||
RetrofitInstance.authApi.isSubscribed(
|
|
||||||
channelId,
|
|
||||||
token
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
// if subscribed change text to unsubscribe
|
// if subscribed change text to unsubscribe
|
||||||
if (response.subscribed == true) {
|
if (isSubscribed == true) {
|
||||||
isSubscribed = true
|
|
||||||
binding.searchSubButton.text = binding.root.context.getString(R.string.unsubscribe)
|
binding.searchSubButton.text = binding.root.context.getString(R.string.unsubscribe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sub button visible and set the on click listeners to (un)subscribe
|
// make sub button visible and set the on click listeners to (un)subscribe
|
||||||
if (response.subscribed != null) {
|
if (isSubscribed == null) return@launch
|
||||||
binding.searchSubButton.visibility = View.VISIBLE
|
binding.searchSubButton.visibility = View.VISIBLE
|
||||||
|
|
||||||
binding.searchSubButton.setOnClickListener {
|
binding.searchSubButton.setOnClickListener {
|
||||||
if (!isSubscribed) {
|
if (isSubscribed == false) {
|
||||||
subscribe(token, channelId)
|
SubscriptionHelper.subscribe(channelId)
|
||||||
binding.searchSubButton.text =
|
binding.searchSubButton.text =
|
||||||
binding.root.context.getString(R.string.unsubscribe)
|
binding.root.context.getString(R.string.unsubscribe)
|
||||||
isSubscribed = true
|
isSubscribed = true
|
||||||
} else {
|
} else {
|
||||||
unsubscribe(token, channelId)
|
SubscriptionHelper.unsubscribe(channelId)
|
||||||
binding.searchSubButton.text =
|
binding.searchSubButton.text =
|
||||||
binding.root.context.getString(R.string.subscribe)
|
binding.root.context.getString(R.string.subscribe)
|
||||||
isSubscribed = false
|
isSubscribed = false
|
||||||
@ -174,33 +159,6 @@ class SearchAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun subscribe(token: String, channelId: String) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
try {
|
|
||||||
RetrofitInstance.authApi.subscribe(
|
|
||||||
token,
|
|
||||||
Subscribe(channelId)
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unsubscribe(token: String, channelId: String) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
try {
|
|
||||||
RetrofitInstance.authApi.unsubscribe(
|
|
||||||
token,
|
|
||||||
Subscribe(channelId)
|
|
||||||
)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindPlaylist(item: SearchItem, binding: PlaylistSearchRowBinding) {
|
private fun bindPlaylist(item: SearchItem, binding: PlaylistSearchRowBinding) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
package com.github.libretube.adapters
|
package com.github.libretube.adapters
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.databinding.ChannelSubscriptionRowBinding
|
import com.github.libretube.databinding.ChannelSubscriptionRowBinding
|
||||||
import com.github.libretube.obj.Subscribe
|
|
||||||
import com.github.libretube.obj.Subscription
|
import com.github.libretube.obj.Subscription
|
||||||
import com.github.libretube.preferences.PreferenceHelper
|
|
||||||
import com.github.libretube.util.ConnectionHelper
|
import com.github.libretube.util.ConnectionHelper
|
||||||
import com.github.libretube.util.NavigationHelper
|
import com.github.libretube.util.NavigationHelper
|
||||||
import com.github.libretube.util.RetrofitInstance
|
import com.github.libretube.util.SubscriptionHelper
|
||||||
import com.github.libretube.util.toID
|
import com.github.libretube.util.toID
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscription>) :
|
class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscription>) :
|
||||||
RecyclerView.Adapter<SubscriptionChannelViewHolder>() {
|
RecyclerView.Adapter<SubscriptionChannelViewHolder>() {
|
||||||
@ -46,51 +40,17 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscrip
|
|||||||
val channelId = subscription.url.toID()
|
val channelId = subscription.url.toID()
|
||||||
if (subscribed) {
|
if (subscribed) {
|
||||||
subscriptionSubscribe.text = root.context.getString(R.string.subscribe)
|
subscriptionSubscribe.text = root.context.getString(R.string.subscribe)
|
||||||
unsubscribe(channelId)
|
SubscriptionHelper.unsubscribe(channelId)
|
||||||
subscribed = false
|
subscribed = false
|
||||||
} else {
|
} else {
|
||||||
subscriptionSubscribe.text =
|
subscriptionSubscribe.text =
|
||||||
root.context.getString(R.string.unsubscribe)
|
root.context.getString(R.string.unsubscribe)
|
||||||
subscribe(channelId)
|
SubscriptionHelper.subscribe(channelId)
|
||||||
subscribed = true
|
subscribed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun subscribe(channelId: String) {
|
|
||||||
fun run() {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
try {
|
|
||||||
val token = PreferenceHelper.getToken()
|
|
||||||
RetrofitInstance.authApi.subscribe(
|
|
||||||
token,
|
|
||||||
Subscribe(channelId)
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unsubscribe(channelId: String) {
|
|
||||||
fun run() {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
try {
|
|
||||||
val token = PreferenceHelper.getToken()
|
|
||||||
RetrofitInstance.authApi.unsubscribe(
|
|
||||||
token,
|
|
||||||
Subscribe(channelId)
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SubscriptionChannelViewHolder(val binding: ChannelSubscriptionRowBinding) :
|
class SubscriptionChannelViewHolder(val binding: ChannelSubscriptionRowBinding) :
|
||||||
|
@ -4,6 +4,7 @@ import android.app.Dialog
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@ -41,6 +42,16 @@ class DownloadDialog : DialogFragment() {
|
|||||||
|
|
||||||
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
|
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
|
||||||
|
|
||||||
|
binding.audioRadio.setOnClickListener {
|
||||||
|
binding.videoSpinner.visibility = View.GONE
|
||||||
|
binding.audioSpinner.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.videoRadio.setOnClickListener {
|
||||||
|
binding.audioSpinner.visibility = View.GONE
|
||||||
|
binding.videoSpinner.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
builder.setView(binding.root)
|
builder.setView(binding.root)
|
||||||
builder.create()
|
builder.create()
|
||||||
} ?: throw IllegalStateException("Activity cannot be null")
|
} ?: throw IllegalStateException("Activity cannot be null")
|
||||||
@ -118,14 +129,15 @@ class DownloadDialog : DialogFragment() {
|
|||||||
if (binding.audioSpinner.size >= 1) binding.audioSpinner.setSelection(1)
|
if (binding.audioSpinner.size >= 1) binding.audioSpinner.setSelection(1)
|
||||||
|
|
||||||
binding.download.setOnClickListener {
|
binding.download.setOnClickListener {
|
||||||
val selectedAudioUrl = audioUrl[binding.audioSpinner.selectedItemPosition]
|
val selectedAudioUrl =
|
||||||
val selectedVideoUrl = vidUrl[binding.videoSpinner.selectedItemPosition]
|
if (binding.audioRadio.isChecked) audioUrl[binding.audioSpinner.selectedItemPosition] else ""
|
||||||
|
val selectedVideoUrl =
|
||||||
|
if (binding.videoRadio.isChecked) vidUrl[binding.videoSpinner.selectedItemPosition] else ""
|
||||||
|
|
||||||
val intent = Intent(context, DownloadService::class.java)
|
val intent = Intent(context, DownloadService::class.java)
|
||||||
intent.putExtra("videoId", videoId)
|
intent.putExtra("videoName", streams.title)
|
||||||
intent.putExtra("videoUrl", selectedVideoUrl)
|
intent.putExtra("videoUrl", selectedVideoUrl)
|
||||||
intent.putExtra("audioUrl", selectedAudioUrl)
|
intent.putExtra("audioUrl", selectedAudioUrl)
|
||||||
intent.putExtra("duration", duration)
|
|
||||||
context?.startService(intent)
|
context?.startService(intent)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
|
|
||||||
class ShareDialog(
|
class ShareDialog(
|
||||||
private val id: String,
|
private val id: String,
|
||||||
private val isPlaylist: Boolean
|
private val isPlaylist: Boolean,
|
||||||
|
private val position: Long = 0L
|
||||||
) : DialogFragment() {
|
) : DialogFragment() {
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
@ -39,7 +40,14 @@ class ShareDialog(
|
|||||||
else -> instanceUrl
|
else -> instanceUrl
|
||||||
}
|
}
|
||||||
val path = if (!isPlaylist) "/watch?v=$id" else "/playlist?list=$id"
|
val path = if (!isPlaylist) "/watch?v=$id" else "/playlist?list=$id"
|
||||||
val url = "$host$path"
|
var url = "$host$path"
|
||||||
|
if (PreferenceHelper.getBoolean(
|
||||||
|
PreferenceKeys.SHARE_WITH_TIME_CODE,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
url += "?t=$position"
|
||||||
|
}
|
||||||
|
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
intent.apply {
|
intent.apply {
|
||||||
|
@ -5,17 +5,15 @@ import android.util.Log
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.adapters.ChannelAdapter
|
import com.github.libretube.adapters.ChannelAdapter
|
||||||
import com.github.libretube.databinding.FragmentChannelBinding
|
import com.github.libretube.databinding.FragmentChannelBinding
|
||||||
import com.github.libretube.obj.Subscribe
|
|
||||||
import com.github.libretube.preferences.PreferenceHelper
|
|
||||||
import com.github.libretube.util.ConnectionHelper
|
import com.github.libretube.util.ConnectionHelper
|
||||||
import com.github.libretube.util.RetrofitInstance
|
import com.github.libretube.util.RetrofitInstance
|
||||||
|
import com.github.libretube.util.SubscriptionHelper
|
||||||
import com.github.libretube.util.formatShort
|
import com.github.libretube.util.formatShort
|
||||||
import com.github.libretube.util.toID
|
import com.github.libretube.util.toID
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
@ -31,7 +29,7 @@ class ChannelFragment : Fragment() {
|
|||||||
var nextPage: String? = null
|
var nextPage: String? = null
|
||||||
private var channelAdapter: ChannelAdapter? = null
|
private var channelAdapter: ChannelAdapter? = null
|
||||||
private var isLoading = true
|
private var isLoading = true
|
||||||
private var isSubscribed: Boolean = false
|
private var isSubscribed: Boolean? = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -61,13 +59,7 @@ class ChannelFragment : Fragment() {
|
|||||||
val refreshChannel = {
|
val refreshChannel = {
|
||||||
binding.channelRefresh.isRefreshing = true
|
binding.channelRefresh.isRefreshing = true
|
||||||
fetchChannel()
|
fetchChannel()
|
||||||
if (PreferenceHelper.getToken() != "") {
|
|
||||||
isSubscribed()
|
isSubscribed()
|
||||||
} else {
|
|
||||||
binding.channelSubscribe.setOnClickListener {
|
|
||||||
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
refreshChannel()
|
refreshChannel()
|
||||||
binding.channelRefresh.setOnRefreshListener {
|
binding.channelRefresh.setOnRefreshListener {
|
||||||
@ -90,77 +82,29 @@ class ChannelFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isSubscribed() {
|
private fun isSubscribed() {
|
||||||
fun run() {
|
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
val response = try {
|
isSubscribed = SubscriptionHelper.isSubscribed(channelId!!)
|
||||||
val token = PreferenceHelper.getToken()
|
if (isSubscribed == null) return@launchWhenCreated
|
||||||
RetrofitInstance.authApi.isSubscribed(
|
|
||||||
channelId!!,
|
|
||||||
token
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, e.toString())
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (response.subscribed == true) {
|
if (isSubscribed == true) {
|
||||||
isSubscribed = true
|
|
||||||
binding.channelSubscribe.text = getString(R.string.unsubscribe)
|
binding.channelSubscribe.text = getString(R.string.unsubscribe)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.channelSubscribe.setOnClickListener {
|
binding.channelSubscribe.setOnClickListener {
|
||||||
if (response.subscribed != null) {
|
binding.channelSubscribe.text = if (isSubscribed == true) {
|
||||||
binding.channelSubscribe.text = if (isSubscribed) {
|
SubscriptionHelper.unsubscribe(channelId!!)
|
||||||
unsubscribe()
|
isSubscribed = false
|
||||||
getString(R.string.subscribe)
|
getString(R.string.subscribe)
|
||||||
} else {
|
} else {
|
||||||
subscribe()
|
SubscriptionHelper.subscribe(channelId!!)
|
||||||
|
isSubscribed = true
|
||||||
getString(R.string.unsubscribe)
|
getString(R.string.unsubscribe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun subscribe() {
|
|
||||||
fun run() {
|
|
||||||
lifecycleScope.launchWhenCreated {
|
|
||||||
try {
|
|
||||||
val token = PreferenceHelper.getToken()
|
|
||||||
RetrofitInstance.authApi.subscribe(
|
|
||||||
token,
|
|
||||||
Subscribe(channelId)
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, e.toString())
|
|
||||||
}
|
|
||||||
isSubscribed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unsubscribe() {
|
|
||||||
fun run() {
|
|
||||||
lifecycleScope.launchWhenCreated {
|
|
||||||
try {
|
|
||||||
val token = PreferenceHelper.getToken()
|
|
||||||
RetrofitInstance.authApi.unsubscribe(
|
|
||||||
token,
|
|
||||||
Subscribe(channelId)
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, e.toString())
|
|
||||||
}
|
|
||||||
isSubscribed = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchChannel() {
|
private fun fetchChannel() {
|
||||||
fun run() {
|
fun run() {
|
||||||
|
@ -53,9 +53,7 @@ import com.github.libretube.obj.ChapterSegment
|
|||||||
import com.github.libretube.obj.Playlist
|
import com.github.libretube.obj.Playlist
|
||||||
import com.github.libretube.obj.Segment
|
import com.github.libretube.obj.Segment
|
||||||
import com.github.libretube.obj.Segments
|
import com.github.libretube.obj.Segments
|
||||||
import com.github.libretube.obj.StreamItem
|
|
||||||
import com.github.libretube.obj.Streams
|
import com.github.libretube.obj.Streams
|
||||||
import com.github.libretube.obj.Subscribe
|
|
||||||
import com.github.libretube.preferences.PreferenceHelper
|
import com.github.libretube.preferences.PreferenceHelper
|
||||||
import com.github.libretube.preferences.PreferenceKeys
|
import com.github.libretube.preferences.PreferenceKeys
|
||||||
import com.github.libretube.services.BackgroundMode
|
import com.github.libretube.services.BackgroundMode
|
||||||
@ -66,6 +64,7 @@ import com.github.libretube.util.DescriptionAdapter
|
|||||||
import com.github.libretube.util.OnDoubleTapEventListener
|
import com.github.libretube.util.OnDoubleTapEventListener
|
||||||
import com.github.libretube.util.PlayerHelper
|
import com.github.libretube.util.PlayerHelper
|
||||||
import com.github.libretube.util.RetrofitInstance
|
import com.github.libretube.util.RetrofitInstance
|
||||||
|
import com.github.libretube.util.SubscriptionHelper
|
||||||
import com.github.libretube.util.formatShort
|
import com.github.libretube.util.formatShort
|
||||||
import com.github.libretube.util.hideKeyboard
|
import com.github.libretube.util.hideKeyboard
|
||||||
import com.github.libretube.util.toID
|
import com.github.libretube.util.toID
|
||||||
@ -117,8 +116,9 @@ class PlayerFragment : Fragment() {
|
|||||||
private var videoId: String? = null
|
private var videoId: String? = null
|
||||||
private var playlistId: String? = null
|
private var playlistId: String? = null
|
||||||
private var channelId: String? = null
|
private var channelId: String? = null
|
||||||
private var isSubscribed: Boolean = false
|
private var isSubscribed: Boolean? = false
|
||||||
private var isLive = false
|
private var isLive = false
|
||||||
|
private lateinit var streams: Streams
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* for the transition
|
* for the transition
|
||||||
@ -175,7 +175,6 @@ class PlayerFragment : Fragment() {
|
|||||||
/**
|
/**
|
||||||
* for autoplay
|
* for autoplay
|
||||||
*/
|
*/
|
||||||
private var relatedStreams: List<StreamItem>? = arrayListOf()
|
|
||||||
private var nextStreamId: String? = null
|
private var nextStreamId: String? = null
|
||||||
private var playlistStreamIds: MutableList<String> = arrayListOf()
|
private var playlistStreamIds: MutableList<String> = arrayListOf()
|
||||||
private var playlistNextPage: String? = null
|
private var playlistNextPage: String? = null
|
||||||
@ -187,13 +186,6 @@ class PlayerFragment : Fragment() {
|
|||||||
private lateinit var mediaSessionConnector: MediaSessionConnector
|
private lateinit var mediaSessionConnector: MediaSessionConnector
|
||||||
private lateinit var playerNotification: PlayerNotificationManager
|
private lateinit var playerNotification: PlayerNotificationManager
|
||||||
|
|
||||||
/**
|
|
||||||
* for the media description of the notification
|
|
||||||
*/
|
|
||||||
private lateinit var title: String
|
|
||||||
private lateinit var uploader: String
|
|
||||||
private lateinit var thumbnailUrl: String
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
arguments?.let {
|
arguments?.let {
|
||||||
@ -540,7 +532,7 @@ class PlayerFragment : Fragment() {
|
|||||||
|
|
||||||
// share button
|
// share button
|
||||||
binding.relPlayerShare.setOnClickListener {
|
binding.relPlayerShare.setOnClickListener {
|
||||||
val shareDialog = ShareDialog(videoId!!, false)
|
val shareDialog = ShareDialog(videoId!!, false, exoPlayer.currentPosition)
|
||||||
shareDialog.show(childFragmentManager, "ShareDialog")
|
shareDialog.show(childFragmentManager, "ShareDialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -739,7 +731,7 @@ class PlayerFragment : Fragment() {
|
|||||||
private fun playVideo() {
|
private fun playVideo() {
|
||||||
fun run() {
|
fun run() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
val response = try {
|
streams = try {
|
||||||
RetrofitInstance.api.getStreams(videoId!!)
|
RetrofitInstance.api.getStreams(videoId!!)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
println(e)
|
println(e)
|
||||||
@ -751,21 +743,13 @@ class PlayerFragment : Fragment() {
|
|||||||
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
|
||||||
return@launchWhenCreated
|
return@launchWhenCreated
|
||||||
}
|
}
|
||||||
// for the notification description adapter
|
|
||||||
title = response.title!!
|
|
||||||
uploader = response.uploader!!
|
|
||||||
thumbnailUrl = response.thumbnailUrl!!
|
|
||||||
channelId = response.uploaderUrl.toID()
|
|
||||||
|
|
||||||
// save related streams for autoplay
|
|
||||||
relatedStreams = response.relatedStreams
|
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
// set media sources for the player
|
// set media sources for the player
|
||||||
setResolutionAndSubtitles(response)
|
setResolutionAndSubtitles(streams)
|
||||||
prepareExoPlayerView()
|
prepareExoPlayerView()
|
||||||
initializePlayerView(response)
|
initializePlayerView(streams)
|
||||||
seekToWatchPosition()
|
if (!isLive) seekToWatchPosition()
|
||||||
exoPlayer.prepare()
|
exoPlayer.prepare()
|
||||||
exoPlayer.play()
|
exoPlayer.play()
|
||||||
exoPlayerView.useController = true
|
exoPlayerView.useController = true
|
||||||
@ -776,7 +760,7 @@ class PlayerFragment : Fragment() {
|
|||||||
// prepare for autoplay
|
// prepare for autoplay
|
||||||
initAutoPlay()
|
initAutoPlay()
|
||||||
if (watchHistoryEnabled) {
|
if (watchHistoryEnabled) {
|
||||||
PreferenceHelper.addToWatchHistory(videoId!!, response)
|
PreferenceHelper.addToWatchHistory(videoId!!, streams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -828,7 +812,10 @@ class PlayerFragment : Fragment() {
|
|||||||
val watchPositions = PreferenceHelper.getWatchPositions()
|
val watchPositions = PreferenceHelper.getWatchPositions()
|
||||||
var position: Long? = null
|
var position: Long? = null
|
||||||
watchPositions.forEach {
|
watchPositions.forEach {
|
||||||
if (it.videoId == videoId) position = it.position
|
if (it.videoId == videoId &&
|
||||||
|
// don't seek to the position if it's the end, autoplay would skip it immediately
|
||||||
|
streams.duration!! - it.position / 1000 > 2
|
||||||
|
) position = it.position
|
||||||
}
|
}
|
||||||
// support for time stamped links
|
// support for time stamped links
|
||||||
val timeStamp: Long? = arguments?.getLong("timeStamp")
|
val timeStamp: Long? = arguments?.getLong("timeStamp")
|
||||||
@ -884,9 +871,9 @@ class PlayerFragment : Fragment() {
|
|||||||
// else: the video must be the last video of the playlist so nothing happens
|
// else: the video must be the last video of the playlist so nothing happens
|
||||||
|
|
||||||
// if it's not a playlist then use the next related video
|
// if it's not a playlist then use the next related video
|
||||||
} else if (relatedStreams != null && relatedStreams!!.isNotEmpty()) {
|
} else if (streams.relatedStreams != null && streams.relatedStreams!!.isNotEmpty()) {
|
||||||
// save next video from related streams for autoplay
|
// save next video from related streams for autoplay
|
||||||
nextStreamId = relatedStreams!![0].url.toID()
|
nextStreamId = streams.relatedStreams!![0].url.toID()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -896,6 +883,9 @@ class PlayerFragment : Fragment() {
|
|||||||
// check whether there is a new video in the queue
|
// check whether there is a new video in the queue
|
||||||
// by making sure that the next and the current video aren't the same
|
// by making sure that the next and the current video aren't the same
|
||||||
saveWatchPosition()
|
saveWatchPosition()
|
||||||
|
// forces the comments to reload for the new video
|
||||||
|
commentsLoaded = false
|
||||||
|
binding.commentsRecView.adapter = null
|
||||||
if (videoId != nextStreamId) {
|
if (videoId != nextStreamId) {
|
||||||
// save the id of the next stream as videoId and load the next video
|
// save the id of the next stream as videoId and load the next video
|
||||||
videoId = nextStreamId
|
videoId = nextStreamId
|
||||||
@ -1064,9 +1054,9 @@ class PlayerFragment : Fragment() {
|
|||||||
|
|
||||||
intent.action = Intent.ACTION_VIEW
|
intent.action = Intent.ACTION_VIEW
|
||||||
intent.setDataAndType(uri, "video/*")
|
intent.setDataAndType(uri, "video/*")
|
||||||
intent.putExtra(Intent.EXTRA_TITLE, title)
|
intent.putExtra(Intent.EXTRA_TITLE, streams.title)
|
||||||
intent.putExtra("title", title)
|
intent.putExtra("title", streams.title)
|
||||||
intent.putExtra("artist", uploader)
|
intent.putExtra("artist", streams.uploader)
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1278,7 +1268,7 @@ class PlayerFragment : Fragment() {
|
|||||||
// get the name of the currently played chapter
|
// get the name of the currently played chapter
|
||||||
private fun getCurrentChapterIndex(): Int {
|
private fun getCurrentChapterIndex(): Int {
|
||||||
val currentPosition = exoPlayer.currentPosition
|
val currentPosition = exoPlayer.currentPosition
|
||||||
var chapterIndex: Int? = null
|
var chapterIndex = 0
|
||||||
|
|
||||||
chapters.forEachIndexed { index, chapter ->
|
chapters.forEachIndexed { index, chapter ->
|
||||||
// check whether the chapter start is greater than the current player position
|
// check whether the chapter start is greater than the current player position
|
||||||
@ -1287,7 +1277,7 @@ class PlayerFragment : Fragment() {
|
|||||||
chapterIndex = index
|
chapterIndex = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return chapterIndex!!
|
return chapterIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setMediaSource(
|
private fun setMediaSource(
|
||||||
@ -1525,7 +1515,12 @@ class PlayerFragment : Fragment() {
|
|||||||
playerNotification = PlayerNotificationManager
|
playerNotification = PlayerNotificationManager
|
||||||
.Builder(c, PLAYER_NOTIFICATION_ID, BACKGROUND_CHANNEL_ID)
|
.Builder(c, PLAYER_NOTIFICATION_ID, BACKGROUND_CHANNEL_ID)
|
||||||
.setMediaDescriptionAdapter(
|
.setMediaDescriptionAdapter(
|
||||||
DescriptionAdapter(title, uploader, thumbnailUrl, requireContext())
|
DescriptionAdapter(
|
||||||
|
streams.title!!,
|
||||||
|
streams.uploader!!,
|
||||||
|
streams.thumbnailUrl!!,
|
||||||
|
requireContext()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@ -1564,86 +1559,26 @@ class PlayerFragment : Fragment() {
|
|||||||
private fun isSubscribed() {
|
private fun isSubscribed() {
|
||||||
fun run() {
|
fun run() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
val response = try {
|
isSubscribed = SubscriptionHelper.isSubscribed(channelId!!)
|
||||||
RetrofitInstance.authApi.isSubscribed(
|
|
||||||
channelId!!,
|
if (isSubscribed == null) return@launchWhenCreated
|
||||||
token
|
|
||||||
)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
println(e)
|
|
||||||
Log.e(TAG, "IOException, you might not have internet connection")
|
|
||||||
return@launchWhenCreated
|
|
||||||
} catch (e: HttpException) {
|
|
||||||
Log.e(TAG, "HttpException, unexpected response")
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (response.subscribed == true) {
|
if (isSubscribed == true) {
|
||||||
isSubscribed = true
|
|
||||||
binding.playerSubscribe.text = getString(R.string.unsubscribe)
|
binding.playerSubscribe.text = getString(R.string.unsubscribe)
|
||||||
}
|
}
|
||||||
if (response.subscribed != null) {
|
|
||||||
binding.playerSubscribe.setOnClickListener {
|
binding.playerSubscribe.setOnClickListener {
|
||||||
if (isSubscribed) {
|
if (isSubscribed == true) {
|
||||||
unsubscribe(channelId!!)
|
SubscriptionHelper.unsubscribe(channelId!!)
|
||||||
binding.playerSubscribe.text = getString(R.string.subscribe)
|
binding.playerSubscribe.text = getString(R.string.subscribe)
|
||||||
} else {
|
} else {
|
||||||
subscribe(channelId!!)
|
SubscriptionHelper.subscribe(channelId!!)
|
||||||
binding.playerSubscribe.text = getString(R.string.unsubscribe)
|
binding.playerSubscribe.text = getString(R.string.unsubscribe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun subscribe(channelId: String) {
|
|
||||||
fun run() {
|
|
||||||
lifecycleScope.launchWhenCreated {
|
|
||||||
try {
|
|
||||||
RetrofitInstance.authApi.subscribe(
|
|
||||||
token,
|
|
||||||
Subscribe(channelId)
|
|
||||||
)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
println(e)
|
|
||||||
Log.e(TAG, "IOException, you might not have internet connection")
|
|
||||||
return@launchWhenCreated
|
|
||||||
} catch (e: HttpException) {
|
|
||||||
Log.e(TAG, "HttpException, unexpected response$e")
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
isSubscribed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unsubscribe(channel_id: String) {
|
|
||||||
fun run() {
|
|
||||||
lifecycleScope.launchWhenCreated {
|
|
||||||
try {
|
|
||||||
RetrofitInstance.authApi.unsubscribe(
|
|
||||||
token,
|
|
||||||
Subscribe(channel_id)
|
|
||||||
)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
println(e)
|
|
||||||
Log.e(TAG, "IOException, you might not have internet connection")
|
|
||||||
return@launchWhenCreated
|
|
||||||
} catch (e: HttpException) {
|
|
||||||
Log.e(TAG, "HttpException, unexpected response")
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
isSubscribed = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run()
|
run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import com.github.libretube.obj.StreamItem
|
|||||||
import com.github.libretube.preferences.PreferenceHelper
|
import com.github.libretube.preferences.PreferenceHelper
|
||||||
import com.github.libretube.preferences.PreferenceKeys
|
import com.github.libretube.preferences.PreferenceKeys
|
||||||
import com.github.libretube.util.RetrofitInstance
|
import com.github.libretube.util.RetrofitInstance
|
||||||
|
import com.github.libretube.util.SubscriptionHelper
|
||||||
import com.github.libretube.util.toID
|
import com.github.libretube.util.toID
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
@ -53,8 +54,6 @@ class SubscriptionsFragment : Fragment() {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
token = PreferenceHelper.getToken()
|
token = PreferenceHelper.getToken()
|
||||||
|
|
||||||
if (token != "") {
|
|
||||||
binding.loginOrRegister.visibility = View.GONE
|
|
||||||
binding.subRefresh.isEnabled = true
|
binding.subRefresh.isEnabled = true
|
||||||
|
|
||||||
binding.subProgress.visibility = View.VISIBLE
|
binding.subProgress.visibility = View.VISIBLE
|
||||||
@ -106,10 +105,6 @@ class SubscriptionsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
binding.subRefresh.isEnabled = false
|
|
||||||
binding.subFeedContainer.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSortDialog() {
|
private fun showSortDialog() {
|
||||||
@ -130,7 +125,10 @@ class SubscriptionsFragment : Fragment() {
|
|||||||
fun run() {
|
fun run() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
feed = try {
|
feed = try {
|
||||||
RetrofitInstance.authApi.getFeed(token)
|
if (token != "") RetrofitInstance.authApi.getFeed(token)
|
||||||
|
else RetrofitInstance.authApi.getUnauthenticatedFeed(
|
||||||
|
SubscriptionHelper.getFormattedLocalSubscriptions()
|
||||||
|
)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, e.toString())
|
Log.e(TAG, e.toString())
|
||||||
Log.e(TAG, "IOException, you might not have internet connection")
|
Log.e(TAG, "IOException, you might not have internet connection")
|
||||||
@ -148,15 +146,7 @@ class SubscriptionsFragment : Fragment() {
|
|||||||
showFeed()
|
showFeed()
|
||||||
} else {
|
} else {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
with(binding.boogh) {
|
binding.emptyFeed.visibility = View.VISIBLE
|
||||||
visibility = View.VISIBLE
|
|
||||||
setImageResource(R.drawable.ic_list)
|
|
||||||
}
|
|
||||||
with(binding.textLike) {
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
text = getString(R.string.emptyList)
|
|
||||||
}
|
|
||||||
binding.loginOrRegister.visibility = View.VISIBLE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.subProgress.visibility = View.GONE
|
binding.subProgress.visibility = View.GONE
|
||||||
@ -185,7 +175,10 @@ class SubscriptionsFragment : Fragment() {
|
|||||||
fun run() {
|
fun run() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
val response = try {
|
val response = try {
|
||||||
RetrofitInstance.authApi.subscriptions(token)
|
if (token != "") RetrofitInstance.authApi.subscriptions(token)
|
||||||
|
else RetrofitInstance.authApi.unauthenticatedSubscriptions(
|
||||||
|
SubscriptionHelper.getFormattedLocalSubscriptions()
|
||||||
|
)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, e.toString())
|
Log.e(TAG, e.toString())
|
||||||
Log.e(TAG, "IOException, you might not have internet connection")
|
Log.e(TAG, "IOException, you might not have internet connection")
|
||||||
|
@ -235,6 +235,21 @@ object PreferenceHelper {
|
|||||||
return getString(PreferenceKeys.ERROR_LOG, "")
|
return getString(PreferenceKeys.ERROR_LOG, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getLocalSubscriptions(): List<String> {
|
||||||
|
val json = settings.getString(PreferenceKeys.LOCAL_SUBSCRIPTIONS, "")
|
||||||
|
return try {
|
||||||
|
val type = object : TypeReference<List<String>>() {}
|
||||||
|
mapper.readValue(json, type)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
listOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLocalSubscriptions(channels: List<String>) {
|
||||||
|
val json = mapper.writeValueAsString(channels)
|
||||||
|
editor.putString(PreferenceKeys.LOCAL_SUBSCRIPTIONS, json).commit()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getDefaultSharedPreferences(context: Context): SharedPreferences {
|
private fun getDefaultSharedPreferences(context: Context): SharedPreferences {
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,6 @@ object PreferenceKeys {
|
|||||||
/**
|
/**
|
||||||
* Download
|
* Download
|
||||||
*/
|
*/
|
||||||
const val DOWNLOAD_VIDEO_FORMAT = "video_format"
|
|
||||||
const val DOWNLOAD_LOCATION = "download_location"
|
const val DOWNLOAD_LOCATION = "download_location"
|
||||||
const val DOWNLOAD_FOLDER = "download_folder"
|
const val DOWNLOAD_FOLDER = "download_folder"
|
||||||
|
|
||||||
@ -81,9 +80,15 @@ object PreferenceKeys {
|
|||||||
const val CLEAR_SEARCH_HISTORY = "clear_search_history"
|
const val CLEAR_SEARCH_HISTORY = "clear_search_history"
|
||||||
const val CLEAR_WATCH_HISTORY = "clear_watch_history"
|
const val CLEAR_WATCH_HISTORY = "clear_watch_history"
|
||||||
const val CLEAR_WATCH_POSITIONS = "clear_watch_positions"
|
const val CLEAR_WATCH_POSITIONS = "clear_watch_positions"
|
||||||
|
const val SHARE_WITH_TIME_CODE = "share_with_time_code"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error logs
|
* Error logs
|
||||||
*/
|
*/
|
||||||
const val ERROR_LOG = "error_log"
|
const val ERROR_LOG = "error_log"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data
|
||||||
|
*/
|
||||||
|
const val LOCAL_SUBSCRIPTIONS = "local_subscriptions"
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ import android.os.IBinder
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.arthenica.ffmpegkit.FFmpegKit
|
|
||||||
import com.github.libretube.DOWNLOAD_CHANNEL_ID
|
import com.github.libretube.DOWNLOAD_CHANNEL_ID
|
||||||
import com.github.libretube.DOWNLOAD_FAILURE_NOTIFICATION_ID
|
import com.github.libretube.DOWNLOAD_FAILURE_NOTIFICATION_ID
|
||||||
import com.github.libretube.DOWNLOAD_PENDING_NOTIFICATION_ID
|
import com.github.libretube.DOWNLOAD_PENDING_NOTIFICATION_ID
|
||||||
@ -35,11 +34,9 @@ class DownloadService : Service() {
|
|||||||
private lateinit var notification: NotificationCompat.Builder
|
private lateinit var notification: NotificationCompat.Builder
|
||||||
|
|
||||||
private var downloadId: Long = -1
|
private var downloadId: Long = -1
|
||||||
private lateinit var videoId: String
|
private lateinit var videoName: String
|
||||||
private lateinit var videoUrl: String
|
private lateinit var videoUrl: String
|
||||||
private lateinit var audioUrl: String
|
private lateinit var audioUrl: String
|
||||||
private lateinit var extension: String
|
|
||||||
private var duration: Int = 0
|
|
||||||
private var downloadType: Int = 3
|
private var downloadType: Int = 3
|
||||||
|
|
||||||
private lateinit var audioDir: File
|
private lateinit var audioDir: File
|
||||||
@ -52,13 +49,11 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
videoId = intent?.getStringExtra("videoId")!!
|
videoName = intent?.getStringExtra("videoName")!!
|
||||||
videoUrl = intent.getStringExtra("videoUrl")!!
|
videoUrl = intent.getStringExtra("videoUrl")!!
|
||||||
audioUrl = intent.getStringExtra("audioUrl")!!
|
audioUrl = intent.getStringExtra("audioUrl")!!
|
||||||
duration = intent.getIntExtra("duration", 1)
|
|
||||||
extension = PreferenceHelper.getString(PreferenceKeys.DOWNLOAD_VIDEO_FORMAT, ".mp4")!!
|
downloadType = if (audioUrl != "") DownloadType.AUDIO
|
||||||
downloadType = if (audioUrl != "" && videoUrl != "") DownloadType.MUX
|
|
||||||
else if (audioUrl != "") DownloadType.AUDIO
|
|
||||||
else if (videoUrl != "") DownloadType.VIDEO
|
else if (videoUrl != "") DownloadType.VIDEO
|
||||||
else DownloadType.NONE
|
else DownloadType.NONE
|
||||||
if (downloadType != DownloadType.NONE) {
|
if (downloadType != DownloadType.NONE) {
|
||||||
@ -115,18 +110,8 @@ class DownloadService : Service() {
|
|||||||
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||||
)
|
)
|
||||||
when (downloadType) {
|
when (downloadType) {
|
||||||
DownloadType.MUX -> {
|
|
||||||
audioDir = File(tempDir, "$videoId-audio")
|
|
||||||
videoDir = File(tempDir, "$videoId-video")
|
|
||||||
downloadId = downloadManagerRequest(
|
|
||||||
getString(R.string.video),
|
|
||||||
getString(R.string.downloading),
|
|
||||||
videoUrl,
|
|
||||||
videoDir
|
|
||||||
)
|
|
||||||
}
|
|
||||||
DownloadType.VIDEO -> {
|
DownloadType.VIDEO -> {
|
||||||
videoDir = File(libretubeDir, "$videoId-video")
|
videoDir = File(libretubeDir, videoName)
|
||||||
downloadId = downloadManagerRequest(
|
downloadId = downloadManagerRequest(
|
||||||
getString(R.string.video),
|
getString(R.string.video),
|
||||||
getString(R.string.downloading),
|
getString(R.string.downloading),
|
||||||
@ -135,7 +120,7 @@ class DownloadService : Service() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
DownloadType.AUDIO -> {
|
DownloadType.AUDIO -> {
|
||||||
audioDir = File(libretubeDir, "$videoId-audio")
|
audioDir = File(libretubeDir, videoName)
|
||||||
downloadId = downloadManagerRequest(
|
downloadId = downloadManagerRequest(
|
||||||
getString(R.string.audio),
|
getString(R.string.audio),
|
||||||
getString(R.string.downloading),
|
getString(R.string.downloading),
|
||||||
@ -146,6 +131,7 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
Log.e(TAG, "download error $e")
|
Log.e(TAG, "download error $e")
|
||||||
|
downloadFailedNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,11 +152,6 @@ class DownloadService : Service() {
|
|||||||
downloadSucceededNotification()
|
downloadSucceededNotification()
|
||||||
onDestroy()
|
onDestroy()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
muxDownloadedMedia()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,7 +214,7 @@ class DownloadService : Service() {
|
|||||||
val builder = NotificationCompat.Builder(this@DownloadService, DOWNLOAD_CHANNEL_ID)
|
val builder = NotificationCompat.Builder(this@DownloadService, DOWNLOAD_CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_download)
|
.setSmallIcon(R.drawable.ic_download)
|
||||||
.setContentTitle(resources.getString(R.string.success))
|
.setContentTitle(resources.getString(R.string.success))
|
||||||
.setContentText(getString(R.string.fail))
|
.setContentText(getString(R.string.downloadsucceeded))
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
with(NotificationManagerCompat.from(this@DownloadService)) {
|
with(NotificationManagerCompat.from(this@DownloadService)) {
|
||||||
// notificationId is a unique int for each notification that you must define
|
// notificationId is a unique int for each notification that you must define
|
||||||
@ -241,39 +222,6 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun muxDownloadedMedia() {
|
|
||||||
val command = "-y -i $videoDir -i $audioDir -c copy $libretubeDir/${videoId}$extension"
|
|
||||||
notification.setContentTitle("Muxing")
|
|
||||||
FFmpegKit.executeAsync(
|
|
||||||
command,
|
|
||||||
{ session ->
|
|
||||||
val state = session.state
|
|
||||||
val returnCode = session.returnCode
|
|
||||||
// CALLED WHEN SESSION IS EXECUTED
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
String.format(
|
|
||||||
"FFmpeg process exited with state %s and rc %s.%s",
|
|
||||||
state,
|
|
||||||
returnCode,
|
|
||||||
session.failStackTrace
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tempDir.deleteRecursively()
|
|
||||||
if (returnCode.toString() != "0") downloadFailedNotification()
|
|
||||||
else downloadSucceededNotification()
|
|
||||||
onDestroy()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// CALLED WHEN SESSION PRINTS LOGS
|
|
||||||
Log.e(TAG, it.message.toString())
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
// CALLED WHEN SESSION GENERATES STATISTICS
|
|
||||||
Log.e(TAG + "stat", it.time.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
try {
|
try {
|
||||||
unregisterReceiver(onDownloadComplete)
|
unregisterReceiver(onDownloadComplete)
|
||||||
|
@ -99,6 +99,9 @@ interface PipedApi {
|
|||||||
@GET("feed")
|
@GET("feed")
|
||||||
suspend fun getFeed(@Query("authToken") token: String?): List<StreamItem>
|
suspend fun getFeed(@Query("authToken") token: String?): List<StreamItem>
|
||||||
|
|
||||||
|
@GET("feed/unauthenticated")
|
||||||
|
suspend fun getUnauthenticatedFeed(@Query("channels") channels: String): List<StreamItem>
|
||||||
|
|
||||||
@GET("subscribed")
|
@GET("subscribed")
|
||||||
suspend fun isSubscribed(
|
suspend fun isSubscribed(
|
||||||
@Query("channelId") channelId: String,
|
@Query("channelId") channelId: String,
|
||||||
@ -108,6 +111,9 @@ interface PipedApi {
|
|||||||
@GET("subscriptions")
|
@GET("subscriptions")
|
||||||
suspend fun subscriptions(@Header("Authorization") token: String): List<Subscription>
|
suspend fun subscriptions(@Header("Authorization") token: String): List<Subscription>
|
||||||
|
|
||||||
|
@GET("subscriptions/unauthenticated")
|
||||||
|
suspend fun unauthenticatedSubscriptions(@Query("channels") channels: String): List<Subscription>
|
||||||
|
|
||||||
@POST("subscribe")
|
@POST("subscribe")
|
||||||
suspend fun subscribe(
|
suspend fun subscribe(
|
||||||
@Header("Authorization") token: String,
|
@Header("Authorization") token: String,
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
package com.github.libretube.util
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.github.libretube.obj.Subscribe
|
||||||
|
import com.github.libretube.preferences.PreferenceHelper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
object SubscriptionHelper {
|
||||||
|
val TAG = "SubscriptionHelper"
|
||||||
|
|
||||||
|
fun subscribe(channelId: String) {
|
||||||
|
if (PreferenceHelper.getToken() != "") {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
try {
|
||||||
|
RetrofitInstance.authApi.subscribe(
|
||||||
|
PreferenceHelper.getToken(),
|
||||||
|
Subscribe(channelId)
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val channels = PreferenceHelper.getLocalSubscriptions().toMutableList()
|
||||||
|
channels.add(channelId)
|
||||||
|
PreferenceHelper.setLocalSubscriptions(channels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unsubscribe(channelId: String) {
|
||||||
|
if (PreferenceHelper.getToken() != "") {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
try {
|
||||||
|
RetrofitInstance.authApi.unsubscribe(
|
||||||
|
PreferenceHelper.getToken(),
|
||||||
|
Subscribe(channelId)
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val channels = PreferenceHelper.getLocalSubscriptions().toMutableList()
|
||||||
|
channels.remove(channelId)
|
||||||
|
PreferenceHelper.setLocalSubscriptions(channels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun isSubscribed(channelId: String): Boolean? {
|
||||||
|
if (PreferenceHelper.getToken() != "") {
|
||||||
|
val isSubscribed = try {
|
||||||
|
RetrofitInstance.authApi.isSubscribed(
|
||||||
|
channelId,
|
||||||
|
PreferenceHelper.getToken()
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return isSubscribed.subscribed
|
||||||
|
} else {
|
||||||
|
return PreferenceHelper.getLocalSubscriptions().contains(channelId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFormattedLocalSubscriptions(): String {
|
||||||
|
return PreferenceHelper.getLocalSubscriptions().joinToString(",")
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -14,6 +13,28 @@
|
|||||||
android:text="@string/app_name"
|
android:text="@string/app_name"
|
||||||
android:textSize="20sp" />
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:checkedButton="@id/video_radio"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/video_radio"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/video" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/audio_radio"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:text="@string/audio" />
|
||||||
|
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/video_spinner"
|
android:id="@+id/video_spinner"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -24,12 +45,13 @@
|
|||||||
android:id="@+id/audio_spinner"
|
android:id="@+id/audio_spinner"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp" />
|
android:layout_margin="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/download"
|
android:id="@+id/download"
|
||||||
style="@style/CustomDialogButton"
|
style="@style/CustomDialogButton"
|
||||||
android:layout_marginRight="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:text="@string/download" />
|
android:text="@string/download" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -15,10 +15,11 @@
|
|||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/loginOrRegister"
|
android:id="@+id/emptyFeed"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_centerInParent="true">
|
android:layout_centerInParent="true"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/boogh"
|
android:id="@+id/boogh"
|
||||||
@ -26,7 +27,7 @@
|
|||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:src="@drawable/ic_login" />
|
android:src="@drawable/ic_list" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textLike"
|
android:id="@+id/textLike"
|
||||||
@ -36,7 +37,7 @@
|
|||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/please_login"
|
android:text="@string/emptyList"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -289,4 +289,6 @@
|
|||||||
<string name="no_search_result">No results.</string>
|
<string name="no_search_result">No results.</string>
|
||||||
<string name="error_occurred">Error</string>
|
<string name="error_occurred">Error</string>
|
||||||
<string name="copied">Copied</string>
|
<string name="copied">Copied</string>
|
||||||
|
<string name="downloadsucceeded">Downloaded</string>
|
||||||
|
<string name="share_with_time">Share with start time</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -4,15 +4,6 @@
|
|||||||
|
|
||||||
<PreferenceCategory app:title="@string/downloads">
|
<PreferenceCategory app:title="@string/downloads">
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
app:defaultValue=".mp4"
|
|
||||||
app:entries="@array/videoFormats"
|
|
||||||
app:entryValues="@array/videoFormatsValues"
|
|
||||||
app:icon="@drawable/ic_videocam"
|
|
||||||
app:key="video_format"
|
|
||||||
app:summary="@string/video_format_summary"
|
|
||||||
app:title="@string/video_format" />
|
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="downloads"
|
android:defaultValue="downloads"
|
||||||
android:entries="@array/downloadLocation"
|
android:entries="@array/downloadLocation"
|
||||||
@ -31,6 +22,16 @@
|
|||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/share">
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:defaultValue="true"
|
||||||
|
app:icon="@drawable/ic_time"
|
||||||
|
app:key="share_with_time_code"
|
||||||
|
app:title="@string/share_with_time" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/advanced">
|
<PreferenceCategory app:title="@string/advanced">
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
|
Loading…
x
Reference in New Issue
Block a user