Skip to content

Commit

Permalink
implemented extraction of signature timestamp to fix some playback is…
Browse files Browse the repository at this point in the history
…sues
  • Loading branch information
alexeichhorn committed Oct 15, 2024
1 parent 307649f commit 41e7f45
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 6 deletions.
12 changes: 12 additions & 0 deletions Sources/YouTubeKit/Extraction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ class Extraction {
return (nil, [nil])
}

/// Extracts the signature timestamp (sts) from javascript.
/// Used to pass into InnerTube to tell API what sig/player is in use.
/// - parameter js: The javascript contents of the watch page
/// - returns: The signature timestamp (sts) or nil if not found
class func extractSignatureTimestamp(fromJS js: String) -> Int? {
let pattern = NSRegularExpression(#"(?:signatureTimestamp|sts)\s*:\s*([0-9]{5})"#)
if let match = pattern.firstMatch(in: js, group: 1) {
return Int(match.content)
}
return nil
}

/// Parses input html to find the end of a JavaScript object.
/// - parameter html: HTML to be parsed for an object.
/// - parameter precedingRegex: Regex to find the string preceding the object.
Expand Down
19 changes: 16 additions & 3 deletions Sources/YouTubeKit/InnerTube.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,17 @@ class InnerTube {
private let context: Context
private let headers: [String: String]
private let playerParams: String

private let signatureTimestamp: Int?

private let baseURL = "https://www.youtube.com/youtubei/v1"

init(client: ClientType = .ios, useOAuth: Bool = false, allowCache: Bool = true) {
init(client: ClientType = .ios, signatureTimestamp: Int?, useOAuth: Bool = false, allowCache: Bool = true) {
self.context = defaultClients[client]!.context
self.apiKey = defaultClients[client]!.apiKey
self.headers = defaultClients[client]!.headers
self.playerParams = defaultClients[client]!.playerParams ?? "8AEB"
self.signatureTimestamp = signatureTimestamp
self.useOAuth = useOAuth
self.allowCache = allowCache

Expand Down Expand Up @@ -205,17 +208,27 @@ class InnerTube {
}
}

private struct PlaybackContext: Encodable {
let contentPlaybackContext: Context

struct Context: Encodable {
let html5Preference = "HTML5_PREF_WANTS"
let signatureTimestamp: Int?
}
}

private struct PlayerRequest: Encodable {
let context: Context
let videoId: String
let params: String
//let paybackContext
let paybackContext: PlaybackContext
let contentCheckOk: Bool = true
let racyCheckOk: Bool = true
}

private func playerRequest(forVideoID videoID: String) -> PlayerRequest {
PlayerRequest(context: context, videoId: videoID, params: playerParams)
let playbackContext = PlaybackContext(contentPlaybackContext: PlaybackContext.Context(signatureTimestamp: signatureTimestamp))
return PlayerRequest(context: context, videoId: videoID, params: playerParams, paybackContext: playbackContext)
}

func player(videoID: String) async throws -> VideoInfo {
Expand Down
22 changes: 19 additions & 3 deletions Sources/YouTubeKit/YouTube.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class YouTube {
private var _embedHTML: String?
private var playerConfigArgs: [String: Any]?
private var _ageRestricted: Bool?
private var _signatureTimestamp: Int?

private var _fmtStreams: [Stream]?

Expand Down Expand Up @@ -188,6 +189,17 @@ public class YouTube {
return _js!
}
}

var signatureTimestamp: Int? {
get async throws {
if let cached = _signatureTimestamp {
return cached
}

_signatureTimestamp = try await Extraction.extractSignatureTimestamp(fromJS: js)
return _signatureTimestamp!
}
}

/// Interface to query both adaptive (DASH) and progressive streams.
/// Returns a list of streams if they have been initialized.
Expand Down Expand Up @@ -316,11 +328,13 @@ public class YouTube {
return nil
}
}

let signatureTimestamp = try await signatureTimestamp

let innertubeClients: [InnerTube.ClientType] = [.ios, .mWeb]

let results: [Result<InnerTube.VideoInfo, Error>] = await innertubeClients.concurrentMap { [videoID, useOAuth, allowOAuthCache] client in
let innertube = InnerTube(client: client, useOAuth: useOAuth, allowCache: allowOAuthCache)
let innertube = InnerTube(client: client, signatureTimestamp: signatureTimestamp, useOAuth: useOAuth, allowCache: allowOAuthCache)

do {
let innertubeResponse = try await innertube.player(videoID: videoID)
Expand Down Expand Up @@ -363,7 +377,8 @@ public class YouTube {
}

private func loadAdditionalVideoInfos(forClient client: InnerTube.ClientType) async throws -> InnerTube.VideoInfo {
let innertube = InnerTube(client: client, useOAuth: useOAuth, allowCache: allowOAuthCache)
let signatureTimestamp = try await signatureTimestamp
let innertube = InnerTube(client: client, signatureTimestamp: signatureTimestamp, useOAuth: useOAuth, allowCache: allowOAuthCache)
let videoInfo = try await innertube.player(videoID: videoID)

// ignore if incorrect videoID
Expand All @@ -376,7 +391,8 @@ public class YouTube {
}

private func bypassAgeGate() async throws {
let innertube = InnerTube(client: .tvEmbed, useOAuth: useOAuth, allowCache: allowOAuthCache)
let signatureTimestamp = try await signatureTimestamp
let innertube = InnerTube(client: .tvEmbed, signatureTimestamp: signatureTimestamp, useOAuth: useOAuth, allowCache: allowOAuthCache)
let innertubeResponse = try await innertube.player(videoID: videoID)

if innertubeResponse.playabilityStatus?.status == "UNPLAYABLE" || innertubeResponse.playabilityStatus?.status == "LOGIN_REQUIRED" {
Expand Down

0 comments on commit 41e7f45

Please sign in to comment.