0xced / XCDYouTubeKit

YouTube video player for iOS, tvOS and macOS
MIT License
2.92k stars 626 forks source link

Updated repo with all fixes so far, including slow download #552

Open cbg-dev-k opened 2 years ago

cbg-dev-k commented 2 years ago

This PR contains all the fixes I had applied locally (from other comments on the repo) + a fix for the slow downloads

zhengyanWork commented 2 years ago

@kbex-dev I have just tried your repo, and everything works well, except i can't get the thumbnail in the iOS demo. it says unsupported URL, please help.

cbg-dev-k commented 2 years ago

Hi @zhengyanWork, I'm using a different API to get video_info but didn't change the mapping provided by YoutubeKit. It's possible the thumbnail url was moved to another field and a change is needed on the Video model in this repo?

I'm sorry to say that this isn't a priority for my use case, I won't have time to look into it.

kunalsood commented 2 years ago

@kbex-dev Thanks for this! To fix thumbnails, insert the following after Line 228 in XCDYouTubeVideo.m file :-

if (!thumbnails) {
    NSDictionary *thumbnailDictionary = videoDetails[@"thumbnail"];
    if (thumbnailDictionary && [thumbnailDictionary isKindOfClass:[NSDictionary class]]) {
        NSArray *thumbnailArray = thumbnailDictionary[@"thumbnails"];
        if (thumbnailArray && [thumbnailArray isKindOfClass:[NSArray class]]) {
            thumbnails = thumbnailArray;
        }
    }
}
cbg-dev-k commented 2 years ago

Hey @kunalsood, thanks for sharing.

I've added your code to the fork, though I haven't confirmed the fix. Hopefully it helps others needing those thumbnails.

kunalsood commented 2 years ago

No worries @kbex-dev.

One question (since I haven't get been able to go through all the issues open about this, on this repo here): Where is the _innertubeApiKey here from? Is it an API key I generate for my own use? or is it safe to use the one already there?

cbg-dev-k commented 2 years ago

The default key is scraped from their website. I think you could create your own API key from a YT account, but I never bothered since we're not exactly following their terms of service :|

In my own codebase I occasionally scrape the YT website to see if the key was updated, but I think it's still the same.

Another gotcha is that their API expects a consent/gdpr cookie (at least when in Europe). This can also be mocked.

I'll add my own (Swift 5) implementation for both issues below as a reference:


import Alamofire
import Foundation
import Kanna
import XCDYouTubeKit

enum Hacks {
    static func provideYoutubeCookie() {
        guard let cookieStorage = Alamofire.SessionManager.default.session.configuration.httpCookieStorage else { return }
        let cookieName = "CONSENT"
        let cookieValue = "YES+cb.\(youtubeDateString)-17-p0.en+FX+\(Int.random(in: 101...998))"

        if let existingCookie = cookieStorage.cookies?.first(where: { $0.name == cookieName }),
           existingCookie.value.contains("YES") {
            // existing cookie should be good
            return
        }
        let hackyYoutubeCookie = HTTPCookie(properties: [
            .domain: ".youtube.com",
            .path: "/",
            .name: cookieName,
            .value: cookieValue,
            .secure: "TRUE"
        ])
        hackyYoutubeCookie.map { cookieStorage.setCookie($0) }
    }

    /// Using a recent date for consent cookie, in case youtube verifies this
    private static var youtubeDateString: String {
        let defaultDateString = "20210519"
        guard let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date()) else { return defaultDateString }
        return youtubeDateFormatter.string(from: yesterday)
    }

    private static let youtubeDateFormatter = DateFormatter().then {
        $0.locale = Locale(identifier: "en_US_POSIX")
        $0.dateFormat = "yyyyMMdd"
    }
}

extension Hacks {
    static func scrapeYoutubeApiKey() {
        Hacks.provideYoutubeCookie()

        let link = "https://www.youtube.com"
        Alamofire.request(link).responseString { response in
            if let html = response.value {
                if let doc = try? HTML(html: html, encoding: .utf8) {
                    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
        // swiftlint:disable:next legacy_constructor
        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)) }
        } ?? []
    }
}
kunalsood commented 2 years ago

Awesome! Thanks.

cbg-dev-k commented 2 years ago

The slow download issue seems to be popping up more and more for our users.

yt-dlp seems to handle it fine, but I'm having a hard time finding out what they do to fix it.

If anyone is feeling up for it, I'd happily accept PRs on my fork :)

mkwitko commented 2 years ago

I'm having trouble with Iphone 8 without sound, anyone has seen this behavior?

vrdriver commented 2 years ago

Hi @kbex-dev,

Thanks for the work you've done. Instead of deleting my original comments, I thought I'd just pass on what I'd done. Brilliant work! https://stephenmonro.wordpress.com/2022/04/13/getting-youtube-videos-to-play-again-in-ionic5/

Thanks, Steve

cbg-dev-k commented 2 years ago

Hi Steve

I'm glad you got it to work!

As a word of warning, these fixes aren't a complete solution. It worked for a while, but I've since received new reports about slow/stalled videos.

I haven't been able to fix those issues on this repo without a complete refactor to mimick youtubedl :/

vrdriver commented 2 years ago

Well, it's much better than having them not working for the moment. :) Apparently it hasn't been working since last year... so I'll take the gamble and push an upload to the Apple App store. I'll keep you posted if I notice anything weird though.