Skip to content

Commit

Permalink
Restore NewPipeExtractor as fallback (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
JeelPatel231 authored Nov 25, 2023
1 parent 6aafd2d commit 0295f05
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 4 deletions.
4 changes: 4 additions & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ kotlin {
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)

/* use `com.github.teamnewpipe` and NOT `com.github.TeamNewPipe`
* its case sensitive and the readme notation DOES NOT WORK */
implementation("com.github.teamnewpipe:NewPipeExtractor:v0.22.7")

implementation(project(":ComposeKit:lib"))

implementation("com.squareup.okhttp3:okhttp:4.10.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager
Expand Down Expand Up @@ -569,7 +570,7 @@ actual class PlatformPlayerService: MediaSessionService(), PlayerService {
throw NotImplementedError()
}

override fun loadBitmap(uri: Uri): ListenableFuture<Bitmap> {
override fun loadBitmap(uri: Uri, options: BitmapFactory.Options?): ListenableFuture<Bitmap> {
return executor.submit<Bitmap> {
runBlocking {
val song = SongRef(uri.toString())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.toasterofbread.spmp.youtubeapi.formats

import com.toasterofbread.spmp.youtubeapi.YoutubeApi
import com.toasterofbread.spmp.youtubeapi.YoutubeVideoFormat
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.ServiceList
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.ParsingException
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory
import org.schabi.newpipe.extractor.stream.AudioStream
import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.extractor.stream.VideoStream
import java.io.IOException
import java.util.concurrent.TimeUnit

class NewPipeVideoFormatsEndpoint(override val api: YoutubeApi): VideoFormatsEndpoint() {

private fun VideoStream.toYoutubeVideoFormat(): YoutubeVideoFormat {
return YoutubeVideoFormat(itag, format!!.mimeType, bitrate, url = content)
}

private fun AudioStream.toYoutubeVideoFormat(): YoutubeVideoFormat {
return YoutubeVideoFormat(itag, format!!.mimeType, bitrate, url = content)
}


override suspend fun getVideoFormats(id: String, filter: ((YoutubeVideoFormat) -> Boolean)?): Result<List<YoutubeVideoFormat>> {

val linkHandler = YoutubeStreamLinkHandlerFactory.getInstance().fromId(id)
val youtubeStreamExtractor = NewPipe.getService(ServiceList.YouTube.serviceId).getStreamExtractor(linkHandler)

val streamInfo = try {
StreamInfo.getInfo(youtubeStreamExtractor)
} catch (e: ParsingException) {
return Result.failure(e)
}

val filteredAudio = streamInfo.audioStreams
.map { it.toYoutubeVideoFormat() }
.filter { filter?.invoke(it) ?: true }

val filteredVideo = streamInfo.videoStreams
.map { it.toYoutubeVideoFormat() }
.filter { filter?.invoke(it) ?: true }

return Result.success(filteredAudio + filteredVideo)
}
}


class CustomDownloader private constructor(builder: OkHttpClient.Builder) : Downloader() {
private val client: OkHttpClient = builder.readTimeout(30, TimeUnit.SECONDS).build()

@Throws(IOException::class, ReCaptchaException::class)
override fun execute(request: Request): Response {
val httpMethod = request.httpMethod()
val url = request.url()
val headers = request.headers()
val requestBody = request.dataToSend()?.toRequestBody(null, 0)

val requestBuilder = okhttp3.Request.Builder()
.url(url)
.method(httpMethod, requestBody)
.addHeader("User-Agent", USER_AGENT)

for ((headerName, headerValueList) in headers) {
if (headerValueList.size > 1) {
requestBuilder.removeHeader(headerName)
for (headerValue in headerValueList) {
requestBuilder.addHeader(headerName, headerValue!!)
}
} else if (headerValueList.size == 1) {
requestBuilder.header(headerName, headerValueList[0])
}
}

client.newCall(requestBuilder.build()).execute().use { response ->
if (response.code == 429) {
throw ReCaptchaException("reCaptcha Challenge requested", url)
}

val responseBodyToReturn = response.body?.string()
val latestUrl = response.request.url.toString()

return Response(
response.code, response.message, response.headers.toMultimap(),
responseBodyToReturn, latestUrl
)
}
}


companion object {
private lateinit var _instance: CustomDownloader

private fun init(builder: OkHttpClient.Builder = OkHttpClient.Builder()) {
_instance = CustomDownloader(builder)
}

fun getInstance(): CustomDownloader {
if (!::_instance.isInitialized) { init() }
return _instance
}

private const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0"
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import com.toasterofbread.spmp.youtubeapi.YoutubeVideoFormat

enum class VideoFormatsEndpointType {
YOUTUBEI,
PIPED;
PIPED,
NEWPIPE;

fun instantiate(api: YoutubeApi): VideoFormatsEndpoint =
when(this) {
YOUTUBEI -> YoutubeiVideoFormatsEndpoint(api)
PIPED -> PipedVideoFormatsEndpoint(api)
NEWPIPE -> NewPipeVideoFormatsEndpoint(api)
}

fun getReadable(): String =
when(this) {
YOUTUBEI -> getString("video_format_endpoint_youtubei")
PIPED -> getString("video_format_endpoint_piped")
NEWPIPE -> getString("video_format_endpoint_newpipe")
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ suspend fun testVideoFormatMethods(

val methods: Map<String, VideoFormatsEndpoint> = mapOf(
Pair("Piped", PipedVideoFormatsEndpoint(api)),
Pair("Youtubei", YoutubeiVideoFormatsEndpoint(api))
Pair("Youtubei", YoutubeiVideoFormatsEndpoint(api)),
Pair("NewPipe", NewPipeVideoFormatsEndpoint(api))
)

println("--- Begin test ---")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import com.toasterofbread.composekit.platform.PlatformPreferencesListener
import com.toasterofbread.db.Database
import com.toasterofbread.spmp.model.mediaitem.MediaItem
import com.toasterofbread.spmp.model.settings.Settings
import com.toasterofbread.spmp.model.settings.category.YoutubeAuthSettings
import com.toasterofbread.spmp.model.settings.category.StreamingSettings
import com.toasterofbread.spmp.model.settings.category.SystemSettings
import com.toasterofbread.spmp.model.settings.category.YoutubeAuthSettings
import com.toasterofbread.spmp.platform.AppContext
import com.toasterofbread.spmp.platform.getDataLanguage
import com.toasterofbread.spmp.platform.getUiLanguage
Expand All @@ -22,6 +22,7 @@ import com.toasterofbread.spmp.youtubeapi.YoutubeApi
import com.toasterofbread.spmp.youtubeapi.YoutubeApi.PostBodyContext
import com.toasterofbread.spmp.youtubeapi.endpoint.ArtistRadioEndpoint
import com.toasterofbread.spmp.youtubeapi.executeResult
import com.toasterofbread.spmp.youtubeapi.formats.CustomDownloader
import com.toasterofbread.spmp.youtubeapi.formats.VideoFormatsEndpoint
import com.toasterofbread.spmp.youtubeapi.formats.VideoFormatsEndpointType
import com.toasterofbread.spmp.youtubeapi.impl.youtubemusic.composable.YTMLoginPage
Expand Down Expand Up @@ -53,6 +54,7 @@ import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.apache.commons.text.StringSubstitutor
import org.schabi.newpipe.extractor.NewPipe
import java.io.Reader
import java.time.Duration
import java.util.logging.Level
Expand Down Expand Up @@ -128,6 +130,9 @@ data class YoutubeMusicApi(
launch {
updateYtmContext()
}
launch {
NewPipe.init(CustomDownloader.getInstance())
}
}
}

Expand Down
1 change: 1 addition & 0 deletions shared/src/commonMain/resources/assets/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@

<string name="video_format_endpoint_piped">Piped API</string>
<string name="video_format_endpoint_youtubei">Youtubei</string>
<string name="video_format_endpoint_newpipe">NewPipe</string>

<string name="error_message_generic">A error occurred</string>
<string name="error_yt_feed_parse_failed">YouTube feed parsing failed</string>
Expand Down

0 comments on commit 0295f05

Please sign in to comment.