Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Player fetch #4089

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.header
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.append
import io.ktor.serialization.kotlinx.json.json
import it.fast4x.innertube.models.MusicNavigationButtonRenderer
import it.fast4x.innertube.models.NavigationEndpoint
Expand All @@ -23,9 +24,27 @@ import java.net.InetSocketAddress
import java.net.Proxy

object Innertube {
val client = HttpClient(OkHttp) {
BrowserUserAgent()

const val BASE_URL = "music.youtube.com"
const val ORIGIN = "https://music.youtube.com"

private val HEADERS = mapOf(
HttpHeaders.Accept to "*/*",
HttpHeaders.Host to BASE_URL,
HttpHeaders.ContentType to ContentType.Application.Json.toString(),
/*
This UserAgent requires frequent update
This may reduce the chance of being block by a tiny bit
But anything at this point should be taken for advantage.

The example below is "Chrome 129.0.0, Windows' @ https://useragents.me/
*/
HttpHeaders.UserAgent to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.3",
HttpHeaders.Origin to ORIGIN,
HttpHeaders.Referrer to ORIGIN
)

val client = HttpClient(OkHttp) {
expectSuccess = true

install(ContentNegotiation) {
Expand Down Expand Up @@ -54,9 +73,8 @@ object Innertube {
}

defaultRequest {
url(scheme = "https", host ="music.youtube.com") {
headers.append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
headers.append("X-Goog-Api-Key", "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8")
url(scheme = "https", host = BASE_URL ) {
HEADERS.forEach( headers::append )
parameters.append("prettyPrint", "false")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
package it.fast4x.innertube.models.bodies

import it.fast4x.innertube.models.Context
import kotlinx.serialization.Serializable
import me.knighthat.innertube.body.Context
import me.knighthat.innertube.body.PlaybackContext

@Serializable
data class PlayerBody(
val context: Context = Context.DefaultAndroid,
//val context: Context = Context.DefaultWeb,
val videoId: String,
val playlistId: String? = null
)
val playlistId: String? = null,
val context: Context = Context.DEFAULT_ANDROID,
val playbackContext: PlaybackContext = PlaybackContext.DEFAULT
) {

/**
* Required parameter as mentioned in
* [#3](https://github.com/zerodytrash/YouTube-Internal-Clients/issues/3)
*/
val racyCheckOk: Boolean
get() = true

/**
* Required parameter as mentioned in
* [#3](https://github.com/zerodytrash/YouTube-Internal-Clients/issues/3)
*/
val contentCheckOk: Boolean
get() = true
}
Original file line number Diff line number Diff line change
@@ -1,95 +1,56 @@
package it.fast4x.innertube.requests

import io.ktor.client.call.body
import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import it.fast4x.innertube.Innertube
import it.fast4x.innertube.models.Context
import it.fast4x.innertube.models.PlayerResponse
import it.fast4x.innertube.models.bodies.PlayerBody
import it.fast4x.innertube.utils.runCatchingNonCancellable
import it.fast4x.piped.models.Session
import me.knighthat.innertube.body.Context
import me.knighthat.innertube.client.AndroidMusic
import me.knighthat.innertube.client.IOSMusic

suspend fun Innertube.player(
body: PlayerBody,
//pipedApiInstance: String = "pipedapi.adminforge.de",
pipedSession: Session
) = runCatchingNonCancellable {
val response = client.post(player) {
setBody(body)
mask("playabilityStatus.status,playerConfig.audioConfig,streamingData.adaptiveFormats,videoDetails.videoId")
}.body<PlayerResponse>()
) = runCatchingNonCancellable {

println("PlayerService Innertube.player response $response")
var response = client.post(player) {
setBody(body)
mask("playabilityStatus.status,playerConfig.audioConfig,streamingData.adaptiveFormats,videoDetails.videoId")
}.body<PlayerResponse>()

println("PlayerService Innertube.player response $response")

if (response.playabilityStatus?.status == "OK") {
response
} else {
val safePlayerResponse = client.post(player) {
setBody(
body.copy(
context = Context.DefaultAndroid.copy(
thirdParty = Context.ThirdParty(
embedUrl = "https://www.youtube.com/watch?v=${body.videoId}"
)
),
)
)
mask("playabilityStatus.status,playerConfig.audioConfig,streamingData.adaptiveFormats,videoDetails.videoId")
}.body<PlayerResponse>()
// Retry this process with API key
if( response.playabilityStatus?.status == "LOGIN_REQUIRED" ) {
println( "Innertube.player fetch failed! Resending with API key" )

println("PlayerService Innertube.player response safePlayerResponse $safePlayerResponse")
response = client.post( player ) {
parameter( "key", AndroidMusic.API_KEY )
setBody( body )
mask( "playabilityStatus.status,playerConfig.audioConfig,streamingData.adaptiveFormats,videoDetails.videoId" )
}.body<PlayerResponse>()
}

if (safePlayerResponse.playabilityStatus?.status == "OK") {
safePlayerResponse
} else {
/*
@Serializable
data class AudioStream(
val url: String,
val bitrate: Long
)
// If problem persists, switch platform
if( response.playabilityStatus?.status == "LOGIN_REQUIRED" ) {
println( "Innertube.player fetch failed #2! Switching to IOS" )

@Serializable
data class PipedResponse(
val audioStreams: List<AudioStream>
response = client.post( player ) {
parameter( "key", IOSMusic.API_KEY )
setBody(
body.copy(
context = Context.DEFAULT_IOS
)

val audioStreams = client.get("https://$pipedApiInstance/streams/${body.videoId}") {
contentType(ContentType.Application.Json)
}.body<PipedResponse>().audioStreams
*/

safePlayerResponse // temporaly used

/*
// TODO() Piped api streams not working, improve with other service
val audioStreams = Piped.media.audioStreams(
session = pipedSession,
videoId = body.videoId
)?.getOrNull()

println("PlayerService Innertube.player PIPED audiostreams $audioStreams")

safePlayerResponse.copy(
streamingData = safePlayerResponse.streamingData?.copy(
adaptiveFormats = safePlayerResponse.streamingData.adaptiveFormats?.map { adaptiveFormat ->
adaptiveFormat.copy(
url = audioStreams?.find { it.bitrate == adaptiveFormat.bitrate }?.url
)
}
)
)

*/


}

}



)
mask( "playabilityStatus.status,playerConfig.audioConfig,streamingData.adaptiveFormats,videoDetails.videoId" )
}.body<PlayerResponse>()
}

response
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package me.knighthat.innertube.body

import kotlinx.serialization.Serializable
import me.knighthat.innertube.client.AndroidMusic
import me.knighthat.innertube.client.IOSMusic

@Serializable
data class Context(
val client: Client,
val thirdParty: ThirdParty = ThirdParty()
) {

companion object {

val DEFAULT_ANDROID = Context(
client = Client(
clientName = AndroidMusic.CLIENT_NAME,
clientVersion = AndroidMusic.CLIENT_VERSION
)
)

val DEFAULT_IOS = Context (
client = Client(
clientName = IOSMusic.CLIENT_NAME,
clientVersion = IOSMusic.CLIENT_VERSION
)
)
}

@Serializable
data class Client(
val hl: String = "en",
val gl: String = "US",
val clientName: String,
val clientVersion: String,
val clientScreen: String = "WATCH",
val androidSdkVersion: Int = 24
)

@Serializable
data class ThirdParty(
val embedUrl: String = "https://music.youtube.com"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package me.knighthat.innertube.body

import kotlinx.serialization.Serializable
import java.time.LocalDate

@Serializable
data class PlaybackContext(
val contextPlaybackContext: ContentPlaybackContext
) {

companion object {

val DEFAULT = PlaybackContext(
ContentPlaybackContext( ContentPlaybackContext.getTimeStamp() )
)
}

@Serializable
data class ContentPlaybackContext( val signatureTimestamp: Long ) {

companion object {

fun getTimeStamp(): Long =
LocalDate.now().toEpochDay() - LocalDate.ofEpochDay(0).toEpochDay()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.knighthat.innertube.client

object AndroidMusic {

const val API_KEY = "AIzaSyAOghZGza2MQSZkY_zfZ370N-PUdXEo8AI"

const val CLIENT_NAME = "ANDROID_MUSIC"
const val CLIENT_VERSION = "6.33.52"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.knighthat.innertube.client

object IOSMusic {

const val API_KEY = "AIzaSyBAETezhkwP0ZWA02RsqT1zu78Fpt0bC_s"

const val CLIENT_NAME = "IOS_MUSIC"
const val CLIENT_VERSION = "6.33.52"
}