mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-12 21:30:30 +05:30
feat: local streams extraction
This commit is contained in:
parent
9c75ce03a1
commit
83f0823535
@ -127,6 +127,9 @@ dependencies {
|
|||||||
implementation(libs.kotlinx.datetime)
|
implementation(libs.kotlinx.datetime)
|
||||||
implementation(libs.kotlinx.serialization.retrofit)
|
implementation(libs.kotlinx.serialization.retrofit)
|
||||||
|
|
||||||
|
/* NewPipe Extractor */
|
||||||
|
implementation(libs.newpipeextractor)
|
||||||
|
|
||||||
/* Cronet and Coil */
|
/* Cronet and Coil */
|
||||||
coreLibraryDesugaring(libs.desugaring)
|
coreLibraryDesugaring(libs.desugaring)
|
||||||
implementation(libs.cronet.embedded)
|
implementation(libs.cronet.embedded)
|
||||||
|
6
app/proguard-rules.pro
vendored
6
app/proguard-rules.pro
vendored
@ -114,3 +114,9 @@
|
|||||||
|
|
||||||
# Settings fragments are loaded through reflection
|
# Settings fragments are loaded through reflection
|
||||||
-keep class com.github.libretube.ui.preferences.** { *; }
|
-keep class com.github.libretube.ui.preferences.** { *; }
|
||||||
|
|
||||||
|
## Rules for NewPipeExtractor
|
||||||
|
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
|
||||||
|
-keep class org.mozilla.javascript.** { *; }
|
||||||
|
-keep class org.mozilla.classfile.ClassFileWriter
|
||||||
|
-dontwarn org.mozilla.javascript.tools.**
|
||||||
|
@ -191,7 +191,7 @@ object PlaylistsHelper {
|
|||||||
MAX_CONCURRENT_IMPORT_CALLS
|
MAX_CONCURRENT_IMPORT_CALLS
|
||||||
).map { videos ->
|
).map { videos ->
|
||||||
videos.parallelMap {
|
videos.parallelMap {
|
||||||
runCatching { RetrofitInstance.api.getStreams(it) }
|
runCatching { StreamsExtractor.extractStreams(it) }
|
||||||
.getOrNull()
|
.getOrNull()
|
||||||
?.toStreamItem(it)
|
?.toStreamItem(it)
|
||||||
}.filterNotNull()
|
}.filterNotNull()
|
||||||
|
108
app/src/main/java/com/github/libretube/api/StreamsExtractor.kt
Normal file
108
app/src/main/java/com/github/libretube/api/StreamsExtractor.kt
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package com.github.libretube.api
|
||||||
|
|
||||||
|
import com.github.libretube.api.obj.ChapterSegment
|
||||||
|
import com.github.libretube.api.obj.MetaInfo
|
||||||
|
import com.github.libretube.api.obj.PreviewFrames
|
||||||
|
import com.github.libretube.api.obj.StreamItem
|
||||||
|
import com.github.libretube.api.obj.Streams
|
||||||
|
import com.github.libretube.api.obj.Subtitle
|
||||||
|
import com.github.libretube.helpers.PlayerHelper
|
||||||
|
import com.github.libretube.util.NewPipeDownloaderImpl
|
||||||
|
import kotlinx.datetime.toKotlinInstant
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfo
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
|
|
||||||
|
object StreamsExtractor {
|
||||||
|
// val npe by lazy {
|
||||||
|
// NewPipe.getService(ServiceList.YouTube.serviceId)
|
||||||
|
// }
|
||||||
|
|
||||||
|
init {
|
||||||
|
NewPipe.init(NewPipeDownloaderImpl())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun extractStreams(videoId: String): Streams {
|
||||||
|
if (!PlayerHelper.disablePipedProxy || !PlayerHelper.localStreamExtraction) {
|
||||||
|
return RetrofitInstance.api.getStreams(videoId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val resp = StreamInfo.getInfo("https://www.youtube.com/watch?v=$videoId")
|
||||||
|
return Streams(
|
||||||
|
title = resp.name,
|
||||||
|
description = resp.description.toString(),
|
||||||
|
uploader = resp.uploaderName,
|
||||||
|
uploaderAvatar = resp.uploaderAvatars.maxBy { it.height }.url,
|
||||||
|
uploaderUrl = resp.uploaderUrl,
|
||||||
|
uploaderVerified = resp.isUploaderVerified,
|
||||||
|
uploaderSubscriberCount = resp.uploaderSubscriberCount,
|
||||||
|
category = resp.category,
|
||||||
|
views = resp.viewCount,
|
||||||
|
likes = resp.likeCount,
|
||||||
|
license = resp.licence,
|
||||||
|
hls = resp.hlsUrl,
|
||||||
|
dash = resp.dashMpdUrl,
|
||||||
|
tags = resp.tags,
|
||||||
|
metaInfo = resp.metaInfo.map {
|
||||||
|
MetaInfo(
|
||||||
|
it.title,
|
||||||
|
it.content.content,
|
||||||
|
it.urls.map { url -> url.toString() },
|
||||||
|
it.urlTexts
|
||||||
|
)
|
||||||
|
},
|
||||||
|
visibility = resp.privacy.name.lowercase(),
|
||||||
|
duration = resp.duration,
|
||||||
|
uploadTimestamp = resp.uploadDate.offsetDateTime().toInstant().toKotlinInstant(),
|
||||||
|
uploaded = resp.uploadDate.offsetDateTime().toEpochSecond(),
|
||||||
|
thumbnailUrl = resp.thumbnails.maxBy { it.height }.url,
|
||||||
|
relatedStreams = resp.relatedItems.map { it as StreamInfoItem }.map {
|
||||||
|
StreamItem(
|
||||||
|
it.url,
|
||||||
|
it.infoType.name,
|
||||||
|
it.name,
|
||||||
|
it.thumbnails.maxBy { image -> image.height }.url,
|
||||||
|
it.uploaderName,
|
||||||
|
it.uploaderUrl,
|
||||||
|
it.uploaderAvatars.maxBy { image -> image.height }.url,
|
||||||
|
it.textualUploadDate,
|
||||||
|
it.duration,
|
||||||
|
it.viewCount,
|
||||||
|
it.isUploaderVerified,
|
||||||
|
it.uploadDate?.offsetDateTime()?.toEpochSecond() ?: 0L,
|
||||||
|
it.shortDescription,
|
||||||
|
it.isShortFormContent,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
chapters = resp.streamSegments.map {
|
||||||
|
ChapterSegment(
|
||||||
|
title = it.title,
|
||||||
|
image = it.previewUrl.orEmpty(),
|
||||||
|
start = it.startTimeSeconds.toLong()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
audioStreams = emptyList(), // TODO: audio streams and video streams via DASH, currently broken anyways
|
||||||
|
videoStreams = emptyList(),
|
||||||
|
previewFrames = resp.previewFrames.map {
|
||||||
|
PreviewFrames(
|
||||||
|
it.urls,
|
||||||
|
it.frameWidth,
|
||||||
|
it.frameHeight,
|
||||||
|
it.totalCount,
|
||||||
|
it.durationPerFrame.toLong(),
|
||||||
|
it.framesPerPageX,
|
||||||
|
it.framesPerPageY
|
||||||
|
)
|
||||||
|
},
|
||||||
|
subtitles = resp.subtitles.map {
|
||||||
|
Subtitle(
|
||||||
|
it.content,
|
||||||
|
it.format?.mimeType,
|
||||||
|
it.displayLanguageName,
|
||||||
|
it.languageTag,
|
||||||
|
it.isAutoGenerated
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -128,6 +128,7 @@ object PreferenceKeys {
|
|||||||
const val MAX_CONCURRENT_DOWNLOADS = "max_parallel_downloads"
|
const val MAX_CONCURRENT_DOWNLOADS = "max_parallel_downloads"
|
||||||
const val EXTERNAL_DOWNLOAD_PROVIDER = "external_download_provider"
|
const val EXTERNAL_DOWNLOAD_PROVIDER = "external_download_provider"
|
||||||
const val DISABLE_VIDEO_IMAGE_PROXY = "disable_video_image_proxy"
|
const val DISABLE_VIDEO_IMAGE_PROXY = "disable_video_image_proxy"
|
||||||
|
const val LOCAL_STREAM_EXTRACTION = "local_stream_extraction"
|
||||||
|
|
||||||
// History
|
// History
|
||||||
const val WATCH_HISTORY_SIZE = "watch_history_size"
|
const val WATCH_HISTORY_SIZE = "watch_history_size"
|
||||||
|
@ -350,6 +350,12 @@ object PlayerHelper {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val localStreamExtraction: Boolean
|
||||||
|
get() = PreferenceHelper.getBoolean(
|
||||||
|
PreferenceKeys.LOCAL_STREAM_EXTRACTION,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
val useHlsOverDash: Boolean
|
val useHlsOverDash: Boolean
|
||||||
get() = PreferenceHelper.getBoolean(
|
get() = PreferenceHelper.getBoolean(
|
||||||
PreferenceKeys.USE_HLS_OVER_DASH,
|
PreferenceKeys.USE_HLS_OVER_DASH,
|
||||||
|
@ -36,7 +36,7 @@ object ProxyHelper {
|
|||||||
* Detect whether the proxy should be used or not for a given stream URL based on user preferences
|
* Detect whether the proxy should be used or not for a given stream URL based on user preferences
|
||||||
*/
|
*/
|
||||||
fun unwrapStreamUrl(url: String): String {
|
fun unwrapStreamUrl(url: String): String {
|
||||||
return if (PlayerHelper.disablePipedProxy) {
|
return if (PlayerHelper.disablePipedProxy && !PlayerHelper.localStreamExtraction) {
|
||||||
unwrapUrl(url)
|
unwrapUrl(url)
|
||||||
} else {
|
} else {
|
||||||
url
|
url
|
||||||
|
@ -20,6 +20,7 @@ import com.github.libretube.LibreTubeApp.Companion.DOWNLOAD_CHANNEL_NAME
|
|||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.CronetHelper
|
import com.github.libretube.api.CronetHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
|
import com.github.libretube.api.StreamsExtractor
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.db.DatabaseHolder.Database
|
import com.github.libretube.db.DatabaseHolder.Database
|
||||||
import com.github.libretube.db.obj.Download
|
import com.github.libretube.db.obj.Download
|
||||||
@ -109,7 +110,7 @@ class DownloadService : LifecycleService() {
|
|||||||
lifecycleScope.launch(coroutineContext) {
|
lifecycleScope.launch(coroutineContext) {
|
||||||
try {
|
try {
|
||||||
val streams = withContext(Dispatchers.IO) {
|
val streams = withContext(Dispatchers.IO) {
|
||||||
RetrofitInstance.api.getStreams(videoId)
|
StreamsExtractor.extractStreams(videoId)
|
||||||
}
|
}
|
||||||
|
|
||||||
val thumbnailTargetPath = getDownloadPath(DownloadHelper.THUMBNAIL_DIR, fileName)
|
val thumbnailTargetPath = getDownloadPath(DownloadHelper.THUMBNAIL_DIR, fileName)
|
||||||
@ -386,7 +387,7 @@ class DownloadService : LifecycleService() {
|
|||||||
* Regenerate stream url using available info format and quality.
|
* Regenerate stream url using available info format and quality.
|
||||||
*/
|
*/
|
||||||
private suspend fun regenerateLink(item: DownloadItem) {
|
private suspend fun regenerateLink(item: DownloadItem) {
|
||||||
val streams = RetrofitInstance.api.getStreams(item.videoId)
|
val streams = StreamsExtractor.extractStreams(item.videoId)
|
||||||
val stream = when (item.type) {
|
val stream = when (item.type) {
|
||||||
FileType.AUDIO -> streams.audioStreams
|
FileType.AUDIO -> streams.audioStreams
|
||||||
FileType.VIDEO -> streams.videoStreams
|
FileType.VIDEO -> streams.videoStreams
|
||||||
|
@ -28,6 +28,7 @@ import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME
|
|||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.JsonHelper
|
import com.github.libretube.api.JsonHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
|
import com.github.libretube.api.StreamsExtractor
|
||||||
import com.github.libretube.api.obj.Segment
|
import com.github.libretube.api.obj.Segment
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
@ -253,7 +254,7 @@ class OnlinePlayerService : LifecycleService() {
|
|||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
streams = runCatching {
|
streams = runCatching {
|
||||||
RetrofitInstance.api.getStreams(videoId)
|
StreamsExtractor.extractStreams(videoId)
|
||||||
}.getOrNull() ?: return@launch
|
}.getOrNull() ?: return@launch
|
||||||
|
|
||||||
// clear the queue if it shouldn't be kept explicitly
|
// clear the queue if it shouldn't be kept explicitly
|
||||||
|
@ -12,6 +12,7 @@ import com.github.libretube.LibreTubeApp.Companion.PLAYLIST_DOWNLOAD_ENQUEUE_CHA
|
|||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.PlaylistsHelper
|
import com.github.libretube.api.PlaylistsHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
|
import com.github.libretube.api.StreamsExtractor
|
||||||
import com.github.libretube.api.obj.PipedStream
|
import com.github.libretube.api.obj.PipedStream
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
@ -136,7 +137,7 @@ class PlaylistDownloadEnqueueService : LifecycleService() {
|
|||||||
|
|
||||||
for (stream in streams) {
|
for (stream in streams) {
|
||||||
val videoInfo = runCatching {
|
val videoInfo = runCatching {
|
||||||
RetrofitInstance.api.getStreams(stream.url!!.toID())
|
StreamsExtractor.extractStreams(stream.url!!.toID())
|
||||||
}.getOrNull() ?: continue
|
}.getOrNull() ?: continue
|
||||||
|
|
||||||
val videoStream = getStream(videoInfo.videoStreams, maxVideoQuality)
|
val videoStream = getStream(videoInfo.videoStreams, maxVideoQuality)
|
||||||
|
@ -16,6 +16,7 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.PlaylistsHelper
|
import com.github.libretube.api.PlaylistsHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
|
import com.github.libretube.api.StreamsExtractor
|
||||||
import com.github.libretube.api.obj.Playlists
|
import com.github.libretube.api.obj.Playlists
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.DialogAddToPlaylistBinding
|
import com.github.libretube.databinding.DialogAddToPlaylistBinding
|
||||||
@ -112,7 +113,7 @@ class AddToPlaylistDialog : DialogFragment() {
|
|||||||
val streams = when {
|
val streams = when {
|
||||||
videoId != null -> listOfNotNull(
|
videoId != null -> listOfNotNull(
|
||||||
runCatching {
|
runCatching {
|
||||||
RetrofitInstance.api.getStreams(videoId!!).toStreamItem(videoId!!)
|
StreamsExtractor.extractStreams(videoId!!).toStreamItem(videoId!!)
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import androidx.fragment.app.setFragmentResult
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
|
import com.github.libretube.api.StreamsExtractor
|
||||||
import com.github.libretube.api.obj.PipedStream
|
import com.github.libretube.api.obj.PipedStream
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
import com.github.libretube.api.obj.Subtitle
|
import com.github.libretube.api.obj.Subtitle
|
||||||
@ -81,7 +82,7 @@ class DownloadDialog : DialogFragment() {
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val response = try {
|
val response = try {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
RetrofitInstance.api.getStreams(videoId)
|
StreamsExtractor.extractStreams(videoId)
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
println(e)
|
println(e)
|
||||||
|
@ -10,6 +10,7 @@ import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
|||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.JsonHelper
|
import com.github.libretube.api.JsonHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
|
import com.github.libretube.api.StreamsExtractor
|
||||||
import com.github.libretube.api.obj.Message
|
import com.github.libretube.api.obj.Message
|
||||||
import com.github.libretube.api.obj.Segment
|
import com.github.libretube.api.obj.Segment
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
@ -57,7 +58,7 @@ class PlayerViewModel : ViewModel() {
|
|||||||
if (isOrientationChangeInProgress && streamsInfo != null) return@withContext streamsInfo to null
|
if (isOrientationChangeInProgress && streamsInfo != null) return@withContext streamsInfo to null
|
||||||
|
|
||||||
streamsInfo = try {
|
streamsInfo = try {
|
||||||
RetrofitInstance.api.getStreams(videoId).deArrow(videoId)
|
StreamsExtractor.extractStreams(videoId).deArrow(videoId)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
return@withContext null to context.getString(R.string.unknown_error)
|
return@withContext null to context.getString(R.string.unknown_error)
|
||||||
} catch (e: HttpException) {
|
} catch (e: HttpException) {
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package com.github.libretube.util
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Request
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Response
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||||
|
|
||||||
|
class NewPipeDownloaderImpl : Downloader() {
|
||||||
|
private val client: OkHttpClient = OkHttpClient.Builder()
|
||||||
|
.readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Throws(IOException::class, ReCaptchaException::class)
|
||||||
|
override fun execute(request: Request): Response {
|
||||||
|
val url = request.url()
|
||||||
|
|
||||||
|
val requestBody = request.dataToSend()?.let {
|
||||||
|
it.toRequestBody(APPLICATION_JSON, 0, it.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
val requestBuilder = okhttp3.Request.Builder()
|
||||||
|
.method(request.httpMethod(), requestBody)
|
||||||
|
.url(url)
|
||||||
|
.addHeader(USER_AGENT_HEADER_NAME, USER_AGENT)
|
||||||
|
|
||||||
|
for ((headerName, headerValueList) in request.headers()) {
|
||||||
|
requestBuilder.removeHeader(headerName)
|
||||||
|
for (headerValue in headerValueList) {
|
||||||
|
requestBuilder.addHeader(headerName, headerValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = client.newCall(requestBuilder.build()).execute()
|
||||||
|
if (response.code == CAPTCHA_STATUS_CODE) {
|
||||||
|
response.close()
|
||||||
|
throw ReCaptchaException("reCaptcha Challenge requested", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
response.code,
|
||||||
|
response.message,
|
||||||
|
response.headers.toMultimap(),
|
||||||
|
response.body?.string(),
|
||||||
|
response.request.url.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val USER_AGENT_HEADER_NAME = "User-Agent"
|
||||||
|
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0"
|
||||||
|
private const val CAPTCHA_STATUS_CODE = 429
|
||||||
|
private val APPLICATION_JSON = "application/json".toMediaType()
|
||||||
|
private const val READ_TIMEOUT_SECONDS = 30L
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import android.util.Log
|
|||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import com.github.libretube.api.PlaylistsHelper
|
import com.github.libretube.api.PlaylistsHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
|
import com.github.libretube.api.StreamsExtractor
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.extensions.move
|
import com.github.libretube.extensions.move
|
||||||
import com.github.libretube.extensions.runCatchingIO
|
import com.github.libretube.extensions.runCatchingIO
|
||||||
@ -179,7 +180,7 @@ object PlayingQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun insertByVideoId(videoId: String) = runCatchingIO {
|
fun insertByVideoId(videoId: String) = runCatchingIO {
|
||||||
val streams = RetrofitInstance.api.getStreams(videoId.toID())
|
val streams = StreamsExtractor.extractStreams(videoId.toID())
|
||||||
add(streams.toStreamItem(videoId))
|
add(streams.toStreamItem(videoId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,6 +510,8 @@
|
|||||||
<string name="gestures">Gestures</string>
|
<string name="gestures">Gestures</string>
|
||||||
<string name="external_download_provider">External download provider</string>
|
<string name="external_download_provider">External download provider</string>
|
||||||
<string name="external_download_provider_summary">Enter the package name of the app you want to use for downloading videos. Leave blank to use LibreTube\'s inbuilt downloader.</string>
|
<string name="external_download_provider_summary">Enter the package name of the app you want to use for downloading videos. Leave blank to use LibreTube\'s inbuilt downloader.</string>
|
||||||
|
<string name="local_stream_extraction">Local stream extraction</string>
|
||||||
|
<string name="local_stream_extraction_summary">Directly fetch video playback information from YouTube without using Piped.</string>
|
||||||
|
|
||||||
<!-- Notification channel strings -->
|
<!-- Notification channel strings -->
|
||||||
<string name="download_channel_name">Download Service</string>
|
<string name="download_channel_name">Download Service</string>
|
||||||
|
@ -73,6 +73,14 @@
|
|||||||
android:title="@string/disable_proxy"
|
android:title="@string/disable_proxy"
|
||||||
app:key="disable_video_image_proxy" />
|
app:key="disable_video_image_proxy" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:icon="@drawable/ic_region"
|
||||||
|
android:summary="@string/local_stream_extraction_summary"
|
||||||
|
android:title="@string/local_stream_extraction"
|
||||||
|
android:dependency="disable_video_image_proxy"
|
||||||
|
app:key="local_stream_extraction" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
@ -20,6 +20,7 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { setUrl("https://jitpack.io") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ constraintlayout = "2.1.4"
|
|||||||
loggingInterceptor = "4.12.0"
|
loggingInterceptor = "4.12.0"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
navigation = "2.7.7"
|
navigation = "2.7.7"
|
||||||
|
newpipeextractor = "6e3a4a6d9de61eafb73e7eb1b714847b5077856d"
|
||||||
preference = "1.2.1"
|
preference = "1.2.1"
|
||||||
extJunit = "1.2.1"
|
extJunit = "1.2.1"
|
||||||
espresso = "3.6.1"
|
espresso = "3.6.1"
|
||||||
@ -62,6 +63,7 @@ androidx-media3-exoplayer-dash = { group = "androidx.media3", name="media3-exopl
|
|||||||
androidx-media3-datasource-cronet = { group = "androidx.media3", name = "media3-datasource-cronet", version.ref = "media3" }
|
androidx-media3-datasource-cronet = { group = "androidx.media3", name = "media3-datasource-cronet", version.ref = "media3" }
|
||||||
androidx-media3-session = { group="androidx.media3", name="media3-session", version.ref="media3" }
|
androidx-media3-session = { group="androidx.media3", name="media3-session", version.ref="media3" }
|
||||||
androidx-media3-ui = { group="androidx.media3", name="media3-ui", version.ref="media3" }
|
androidx-media3-ui = { group="androidx.media3", name="media3-ui", version.ref="media3" }
|
||||||
|
newpipeextractor = { module = "com.github.TeamNewPipe:NewPipeExtractor", version.ref = "newpipeextractor" }
|
||||||
square-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
square-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
||||||
desugaring = { group = "com.android.tools", name = "desugar_jdk_libs_nio", version.ref = "desugaring" }
|
desugaring = { group = "com.android.tools", name = "desugar_jdk_libs_nio", version.ref = "desugaring" }
|
||||||
cronet-embedded = { group = "org.chromium.net", name = "cronet-embedded", version.ref = "cronetEmbedded" }
|
cronet-embedded = { group = "org.chromium.net", name = "cronet-embedded", version.ref = "cronetEmbedded" }
|
||||||
|
Loading…
Reference in New Issue
Block a user