0xced/XCDYouTubeKit

Error XCDYouTubeVideoErrorDomain -2 when i try open video

Dem909 opened this issue ยท 23 comments

when i try to open any video it returns an error XCDYouTubeVideoErrorDomain, Code: -2

Before that, other tips from other discussions helped, but now something new :(

yes, it does not work anymore

And now what i can do? :)

I'm seeing the same issue here.

When using a VPN to the US, videos still play. Maybe it's related to the consent cookie tweak we've had to implement before? That only affected non-US clients I believe.

Ah shit, here we go again

Maybe this can help:
https://stackoverflow.com/questions/67615278/get-video-info-youtube-endpoint-suddenly-returning-404-not-found/67629882#67629882

There's a new answer named UPDATE (July 2021)

POST: https://youtubei.googleapis.com/youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8

with the body :

{
"context": {
"client": {
"hl": "en",
"clientName": "WEB",
"clientVersion": "2.20210721.00.00",
"mainAppWebInfo": {
"graftUrl": "/watch?v={VIDEO_ID}"
}
}
},
"videoId": "{VIDEO_ID}"
}

the above mentioned fix seems to work
checkout #545

the innertube api key is hardcoded and we don't know how long it will last so it is recommended to scrap the api key from youtube and set it via XCDYouTubeClient.setInnertubeApiKey

func scrapInnertubeApiKey(){
    let link = "https://www.youtube.com"
    Alamofire.request(link).responseString { (response) in  // network lib https://github.com/Alamofire/Alamofire
        if let html = response.value {
            if let doc = try? HTML(html: html, encoding: .utf8) {   // HTML parser https://github.com/tid-kijyun/Kanna
                if let text = doc.xpath("//script[contains(., 'INNERTUBE_API_KEY')]/text()").first?.text {
                    if let results = text.match("ytcfg.set\\((\\{.*?\\})\\)").last?.last {
                        if let data = results.data(using: .utf8), let model = try? JSONDecoder().decode(InnertubeScrap.self, from: data) {
                            let key = model.INNERTUBE_API_KEY
                            XCDYouTubeClient.setInnertubeApiKey(key)
                        }
                    }
                }
            }
        }
    }
}


struct InnertubeScrap : Codable {
    let INNERTUBE_API_KEY : String
}

extension String {
    func match(_ regex: String) -> [[String]] {
        let nsString = self as NSString
        return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)).map { match in
            (0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
        } ?? []
    }
}

I can confirm the post method works, but the response schema is different than the current get_video_info. So, I think there are some meaningful changes required to get this working from within XCDYouTubeKit. In case it helps anyone in the short-term, here's snippet from my swift app. But I started needing to re-implement some of the signature stuff inside XCDYouTubeKit, and decided to slow my roll.

Note that postToUrl is a shared helper which is pretty simple, so left out here for brevity.

func getVideoUrl(videoId: String, completion: @escaping ([UInt:String]) -> Void) {
	let infoParams: [String: Any] = [
		"graftUrl": "/watch?v=\(videoId)"
	]
	let clientParams: [String: Any] = [
		"hl": "en",
		"clientName": "WEB",
		"clientVersion": "2.20210721.00.00",
		"mainAppWebInfo": infoParams
	]
	let contextParams: [String: Any] = [
		"client": clientParams
	]
	let parameters: [String: Any] = [
		"context": contextParams,
		"videoId": videoId
	]
	
	postToUrl(urlStr: "https://youtubei.googleapis.com/youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", parameters: parameters, commandCompletion: { (data, response, error) in
		var streamURLs: [UInt:String] = [:]
		
		//let dataString = String(decoding: data!, as: UTF8.self)
		//STLog(logStr: dataString)
		
		if  let data = data,
		    let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
		    let streamingData = json["streamingData"] as? [String: Any],
			let formats = streamingData["formats"] as? [[String: Any]] {
				for format in formats
				{
					if let itag = format["itag"] as? UInt
					{
						if let url = format["url"] as? String {
							streamURLs[itag] = url
						}
						else if let signatureCipher = format["signatureCipher"] as? String,
								let queryDict = signatureCipher.queryDictionary,
								let url = queryDict["url"],
								let sig = queryDict["s"] {
							// TODO: twiddle the signature, should likely do this in XCDYouTubeKit
							streamURLs[itag] = url
						}
					}
				}
			}
		
		completion(streamURLs)
	})
}

Note that postToUrl is a shared helper which is pretty simple, so left out here for brevity.

Return error "Precondition check failed" :(

the above mentioned fix seems to work
checkout #545

the innertube api key is hardcoded and we don't know how long it will last so it is recommended to scrap the api key from youtube and set it via XCDYouTubeClient.setInnertubeApiKey

func scrapInnertubeApiKey(){
    let link = "https://www.youtube.com"
    Alamofire.request(link).responseString { (response) in  // network lib https://github.com/Alamofire/Alamofire
        if let html = response.value {
            if let doc = try? HTML(html: html, encoding: .utf8) {   // HTML parser https://github.com/tid-kijyun/Kanna
                if let text = doc.xpath("//script[contains(., 'INNERTUBE_API_KEY')]/text()").first?.text {
                    if let results = text.match("ytcfg.set\\((\\{.*?\\})\\)").last?.last {
                        if let data = results.data(using: .utf8), let model = try? JSONDecoder().decode(InnertubeScrap.self, from: data) {
                            let key = model.INNERTUBE_API_KEY
                            XCDYouTubeClient.setInnertubeApiKey(key)
                        }
                    }
                }
            }
        }
    }
}


struct InnertubeScrap : Codable {
    let INNERTUBE_API_KEY : String
}

extension String {
    func match(_ regex: String) -> [[String]] {
        let nsString = self as NSString
        return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)).map { match in
            (0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
        } ?? []
    }
}

Looks good, but the problem is that at my stage
if let text = doc.xpath("//script[contains(., 'INNERTUBE_API_KEY')]/text()").first?.text {
does not return anything, and a screen with the YouTube privacy policy is displayed in the doc. content

But i tested key "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" - he is work, but Privacy Policy blocked all and video not returned(

When i try get video info, google return this:

<NSHTTPURLResponse: 0x283dae420> { URL: https://youtubei.googleapis.com/youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8 } { Status Code: 404, Headers { "Alt-Svc" = ( "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000,h3-T051=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"" ); "Content-Encoding" = ( br ); "Content-Length" = ( 138 ); "Content-Type" = ( "application/json; charset=UTF-8" ); Date = ( "Sun, 25 Jul 2021 21:16:49 GMT" ); Server = ( ESF ); Vary = ( Origin, "X-Origin", Referer ); "x-content-type-options" = ( nosniff ); "x-frame-options" = ( SAMEORIGIN ); "x-xss-protection" = ( 0 ); } }

Note that postToUrl is a shared helper which is pretty simple, so left out here for brevity.

Return error "Precondition check failed" :(

I saw that, you need to make sure that the content type is application/json and you don't percent encode it.

Note that postToUrl is a shared helper which is pretty simple, so left out here for brevity.

Return error "Precondition check failed" :(

I saw that, you need to make sure that the content type is application/json and you don't percent encode it.

It's... work...

I hope this doesn't sound weird, but I love you, thank you very much! :)
You helped a lot, because the application in the store began to pour out, users complained and real hell began for me ... You really saved me, thanks!

I followed the code in #545, but still getting XCDYouTubeVideoErrorDomain -2 for upcoming live stream video.
This is no doubt that if the problem solved, this will be the perfect fix.
Any idea for this? Thanks.

@Kiu212 +1
Addition. This problem is specific to the European Region

After combining #545 with the changes I already had locally (consent cookie etc), it started playing videos again in europe.

When I try a livestream however, the POST works but youtubekit fails to play the stream. The log eventually notifies that a request went timeout. Anyone else seeing this?

The timeout error:
[XCDYouTubeKit] URL GET operation finished with error: The request timed out. Domain: NSURLErrorDomain Code: -1001 User Info: { NSErrorFailingURLKey = "https://r9---sn-uxaxoxu-cg0r.googlevideo.com/videoplayback?expire=1627321020&ei=XJ7-YIKUIYKx1wK-2pCADw&ip=2a02%3A1810%3Aa418%3Af700%3A38fc%3Aa27c%3Aa8bc%3A63e6&id=DWcJFNfaw9c.3&itag=243&aitags=133%2C134%2C135%2C136%2C137%2C160%2C242%2C243%2C244%2C247%2C248%2C278&source=yt_live_broadcast&requiressl=yes&mh=Ms&mm=44%2C29&mn=sn-uxaxoxu-cg0r%2Csn-5hnekn7d&ms=lva%2Crdu&mv=u&mvi=9&pl=45&pcm2=yes&vprv=1&live=1&hang=1&noclen=1&mime=video%2Fwebm&ns=EtxyS696fTkFieyGVFLQZrAG&gir=yes&mt=1627298950&fvip=4&keepalive=yes&fexp=24001373%2C24007246&beids=9466588&c=WEB&n=FSF8yu_YZ5HouZ&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cpcm2%2Cvprv%2Clive%2Chang%2Cnoclen%2Cmime%2Cns%2Cgir&sig=AOq0QJ8wRQIhAKPUVg1BS0dny9J4cVDhB57s1VBGBrZw2NppdaL-APesAiAU8b2kXbeM9iV-Yt_s4qMftNiJhNKDitq8M0FD6ESGMg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl&lsig=AG3C_xAwRgIhAI8vIV-EqXsXq0Y5zwdqvYYlrTKy3TqHoGRy0l4rXNJ5AiEAyspZ6SOPFDdQ-CnjDvqHl55kOAVMenro0IxH-jFdcoA%3D&ratebypass=yes"; NSErrorFailingURLStringKey = "https://r9---sn-uxaxoxu-cg0r.googlevideo.com/videoplayback?expire=1627321020&ei=XJ7-YIKUIYKx1wK-2pCADw&ip=2a02%3A1810%3Aa418%3Af700%3A38fc%3Aa27c%3Aa8bc%3A63e6&id=DWcJFNfaw9c.3&itag=243&aitags=133%2C134%2C135%2C136%2C137%2C160%2C242%2C243%2C244%2C247%2C248%2C278&source=yt_live_broadcast&requiressl=yes&mh=Ms&mm=44%2C29&mn=sn-uxaxoxu-cg0r%2Csn-5hnekn7d&ms=lva%2Crdu&mv=u&mvi=9&pl=45&pcm2=yes&vprv=1&live=1&hang=1&noclen=1&mime=video%2Fwebm&ns=EtxyS696fTkFieyGVFLQZrAG&gir=yes&mt=1627298950&fvip=4&keepalive=yes&fexp=24001373%2C24007246&beids=9466588&c=WEB&n=FSF8yu_YZ5HouZ&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cpcm2%2Cvprv%2Clive%2Chang%2Cnoclen%2Cmime%2Cns%2Cgir&sig=AOq0QJ8wRQIhAKPUVg1BS0dny9J4cVDhB57s1VBGBrZw2NppdaL-APesAiAU8b2kXbeM9iV-Yt_s4qMftNiJhNKDitq8M0FD6ESGMg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl&lsig=AG3C_xAwRgIhAI8vIV-EqXsXq0Y5zwdqvYYlrTKy3TqHoGRy0l4rXNJ5AiEAyspZ6SOPFDdQ-CnjDvqHl55kOAVMenro0IxH-jFdcoA%3D&ratebypass=yes"; NSLocalizedDescription = "The request timed out."; NSUnderlyingError = "Error Domain=kCFErrorDomainCFNetwork Code=-1001 \"(null)\" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}"; "_NSURLErrorFailingURLSessionTaskErrorKey" = "LocalDataTask <2272A8E5-2351-4FC3-942F-980D3DCBAEC5>.<1>"; "_NSURLErrorRelatedURLSessionTaskErrorKey" = ( "LocalDataTask <2272A8E5-2351-4FC3-942F-980D3DCBAEC5>.<1>" ); "_kCFStreamErrorCodeKey" = "-2102"; "_kCFStreamErrorDomainKey" = 4; }

I have same error... If someone have solution - pls share...
Thanks.

need a fix :'(

Well damn, it seems I didn't apply one of the previous fixes and it came back to bite me.

The only missing code I needed was near line 173 in XCDYoutubeVideo.m:
if (httpLiveStream.length == 0) { httpLiveStream = info[@"streamingData"][@"hlsManifestUrl"]; }

Now both livestreams and past broadcasts are working for me.

no of the fixes works anymore (for me). it looks like it will only play videos randomly.

anyone got the solution ??

Hello guys, someone got the solution?

I was getting this error when I tried to pass the video url instead of the video id.