diff --git a/.swiftpm/xcode/xcuserdata/soneejohn.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/soneejohn.xcuserdatad/xcschemes/xcschememanagement.plist
deleted file mode 100644
index d1054dc45..000000000
--- a/.swiftpm/xcode/xcuserdata/soneejohn.xcuserdatad/xcschemes/xcschememanagement.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- SchemeUserState
-
- XCDYouTubeKit.xcscheme_^#shared#^_
-
- orderHint
- 0
-
-
- SuppressBuildableAutocreation
-
- XCDYouTubeKit
-
- primary
-
-
-
-
-
diff --git a/Package.swift b/Package.swift
index cc066ac27..6f2f7de7f 100644
--- a/Package.swift
+++ b/Package.swift
@@ -2,21 +2,21 @@
import PackageDescription
let package = Package(
- name: "XCDYouTubeKit",
- products: [
- .library(name: "XCDYouTubeKit", targets: ["XCDYouTubeKit"])
- ],
- targets: [
- .target(
- name: "XCDYouTubeKit",
- path: ".",
- exclude: [
- "XCDYouTubeKit/Info.plist",
- "XCDYouTubeKit/Configuration.plist",
- "XCDYouTubeKit/AppledocSettings.plist"
- ],
- sources: ["XCDYouTubeKit"],
- publicHeadersPath: "XCDYouTubeKit"
- )
- ]
+ name: "XCDYouTubeKit",
+ products: [
+ .library(name: "XCDYouTubeKit", targets: ["XCDYouTubeKit"])
+ ],
+ targets: [
+ .target(
+ name: "XCDYouTubeKit",
+ path: ".",
+ exclude: [
+ "XCDYouTubeKit/Info.plist",
+ "XCDYouTubeKit/Configuration.plist",
+ "XCDYouTubeKit/AppledocSettings.plist"
+ ],
+ sources: ["XCDYouTubeKit"],
+ publicHeadersPath: "XCDYouTubeKit"
+ )
+ ]
)
diff --git a/XCDYouTubeKit Demo/Playground.playground/Contents.swift b/XCDYouTubeKit Demo/Playground.playground/Contents.swift
index 8be03b019..c2c7535e9 100644
--- a/XCDYouTubeKit Demo/Playground.playground/Contents.swift
+++ b/XCDYouTubeKit Demo/Playground.playground/Contents.swift
@@ -1,19 +1,19 @@
import XCDYouTubeKit
#if swift(>=3.0)
- import PlaygroundSupport
- struct YouTubeVideoQuality {
- static let hd720 = NSNumber(value: XCDYouTubeVideoQuality.HD720.rawValue)
- static let medium360 = NSNumber(value: XCDYouTubeVideoQuality.medium360.rawValue)
- static let small240 = NSNumber(value: XCDYouTubeVideoQuality.small240.rawValue)
- }
+import PlaygroundSupport
+struct YouTubeVideoQuality {
+ static let hd720 = NSNumber(value: XCDYouTubeVideoQuality.HD720.rawValue)
+ static let medium360 = NSNumber(value: XCDYouTubeVideoQuality.medium360.rawValue)
+ static let small240 = NSNumber(value: XCDYouTubeVideoQuality.small240.rawValue)
+}
#else
- import XCPlayground
- typealias Error = NSError
- struct YouTubeVideoQuality {
- static let hd720 = NSNumber(unsignedLong: XCDYouTubeVideoQuality.HD720.rawValue)
- static let medium360 = NSNumber(unsignedLong: XCDYouTubeVideoQuality.Medium360.rawValue)
- static let small240 = NSNumber(unsignedLong: XCDYouTubeVideoQuality.Small240.rawValue)
- }
+import XCPlayground
+typealias Error = NSError
+enum YouTubeVideoQuality {
+ static let hd720 = NSNumber(unsignedLong: XCDYouTubeVideoQuality.HD720.rawValue)
+ static let medium360 = NSNumber(unsignedLong: XCDYouTubeVideoQuality.Medium360.rawValue)
+ static let small240 = NSNumber(unsignedLong: XCDYouTubeVideoQuality.Small240.rawValue)
+}
#endif
setenv("XCDYouTubeKitLogLevel", "0", 1)
@@ -38,7 +38,7 @@ client.getVideoWithIdentifier("xxxxxxxxxxx") { (video: XCDYouTubeVideo?, error:
}
#if swift(>=3.0)
- PlaygroundPage.current.needsIndefiniteExecution = true
+PlaygroundPage.current.needsIndefiniteExecution = true
#else
- XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
+XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
#endif
diff --git a/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift b/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift
index 53eaeb18b..93cb7df48 100644
--- a/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift
+++ b/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift
@@ -6,85 +6,86 @@
// Copyright © 2019 Cédric Luthi. All rights reserved.
//
-import Foundation
import AVKit
+import Foundation
import MediaPlayer
extension UIViewController {
- func topMostViewController() -> UIViewController {
- if self.presentedViewController == nil {
- return self
- }
- if let navigation = self.presentedViewController as? UINavigationController {
- return navigation.visibleViewController!.topMostViewController()
- }
- if let tab = self.presentedViewController as? UITabBarController {
- if let selectedTab = tab.selectedViewController {
- return selectedTab.topMostViewController()
- }
- return tab.topMostViewController()
- }
- return self.presentedViewController!.topMostViewController()
- }
+ func topMostViewController() -> UIViewController {
+ if presentedViewController == nil {
+ return self
+ }
+ if let navigation = presentedViewController as? UINavigationController {
+ return navigation.visibleViewController!.topMostViewController()
+ }
+ if let tab = presentedViewController as? UITabBarController {
+ if let selectedTab = tab.selectedViewController {
+ return selectedTab.topMostViewController()
+ }
+ return tab.topMostViewController()
+ }
+ return presentedViewController!.topMostViewController()
+ }
}
extension UIView {
- var parentViewController: UIViewController? {
- var parentResponder: UIResponder? = self
- while parentResponder != nil {
- parentResponder = parentResponder!.next
- if let viewController = parentResponder as? UIViewController {
- return viewController
- }
- }
- return nil
- }
+ var parentViewController: UIViewController? {
+ var parentResponder: UIResponder? = self
+ while parentResponder != nil {
+ parentResponder = parentResponder!.next
+ if let viewController = parentResponder as? UIViewController {
+ return viewController
+ }
+ }
+ return nil
+ }
}
@objcMembers class AVPlayerViewControllerManager: NSObject {
- //MARK: - Public
- public static let shared = AVPlayerViewControllerManager()
+ // MARK: - Public
+
+ public static let shared = AVPlayerViewControllerManager()
public var lowQualityMode = false
public dynamic var duration: Float = 0
-
+
public var video: XCDYouTubeVideo? {
didSet {
guard let video = video else { return }
guard lowQualityMode == false else {
guard let streamURL = video.streamURLs[XCDYouTubeVideoQualityHTTPLiveStreaming] ?? video.streamURLs[XCDYouTubeVideoQuality.medium360.rawValue] ?? video.streamURLs[XCDYouTubeVideoQuality.small240.rawValue] else { fatalError("No stream URL") }
-
- self.player = AVPlayer(url: streamURL)
- self.controller.player = self.player
+
+ player = AVPlayer(url: streamURL)
+ controller.player = player
return
}
- guard let streamURL = video.streamURL else { fatalError("No stream URL")}
- self.player = AVPlayer(url: streamURL)
- self.controller.player = self.player
+ guard let streamURL = video.streamURL else { fatalError("No stream URL") }
+ player = AVPlayer(url: streamURL)
+ controller.player = player
}
}
- public var player: AVPlayer? {
- didSet {
+ public var player: AVPlayer? {
+ didSet {
if let playerRateObserverToken = playerRateObserverToken {
playerRateObserverToken.invalidate()
self.playerRateObserverToken = nil
}
-
- self.playerRateObserverToken = player?.observe(\.rate, changeHandler: { (item, value) in
- self.updatePlaybackRateMetadata()
- })
-
+
+ playerRateObserverToken = player?.observe(\.rate, changeHandler: { _, _ in
+ self.updatePlaybackRateMetadata()
+ })
+
guard let video = self.video else { return }
if let token = timeObserverToken {
oldValue?.removeTimeObserver(token)
timeObserverToken = nil
}
- self.setupRemoteTransportControls()
- self.updateGeneralMetadata(video: video)
- self.updatePlaybackDuration()
- }
- }
-
+ setupRemoteTransportControls()
+ updateGeneralMetadata(video: video)
+ updatePlaybackDuration()
+ }
+ }
+
public lazy var controller: AVPlayerViewController = {
let controller = AVPlayerViewController()
if #available(iOS 10.0, *) {
@@ -92,22 +93,22 @@ extension UIView {
}
return controller
}()
-
+
override init() {
super.init()
-
- NotificationCenter.default.addObserver(forName: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance(), queue: .main) { (notification) in
-
+
+ NotificationCenter.default.addObserver(forName: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance(), queue: .main) { notification in
+
guard let userInfo = notification.userInfo,
- let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
- let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
- return
+ let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
+ let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
+ return
}
-
+
if type == .began {
self.player?.pause()
} else if type == .ended {
- guard ((try? AVAudioSession.sharedInstance().setActive(true)) != nil) else { return }
+ guard (try? AVAudioSession.sharedInstance().setActive(true)) != nil else { return }
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
guard options.contains(.shouldResume) else { return }
@@ -115,84 +116,84 @@ extension UIView {
}
}
}
-
+
public func disconnectPlayer() {
- self.controller.player = nil
- }
-
+ controller.player = nil
+ }
+
public func reconnectPlayer(rootViewController: UIViewController) {
let viewController = rootViewController.topMostViewController()
guard let playerViewController = viewController as? AVPlayerViewController else {
if rootViewController is UINavigationController {
guard let vc = (rootViewController as! UINavigationController).visibleViewController else { return }
- for childVC in vc.children {
+ for childVC in vc.children {
guard let playerViewController = childVC as? AVPlayerViewController else { continue }
- playerViewController.player = self.player
+ playerViewController.player = player
break
}
}
return
}
- playerViewController.player = self.player
+ playerViewController.player = player
}
-
- //MARK: Private
-
+
+ // MARK: Private
+
fileprivate var playerRateObserverToken: NSKeyValueObservation?
fileprivate var timeObserverToken: Any?
- fileprivate let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
-
- fileprivate func setupRemoteTransportControls() {
- let commandCenter = MPRemoteCommandCenter.shared()
- commandCenter.playCommand.addTarget { [unowned self] event in
- if self.player?.rate == 0.0 {
- self.player?.play()
- return .success
- }
- return .commandFailed
- }
-
- commandCenter.pauseCommand.addTarget { event in
- if self.player?.rate == 1.0 {
- self.player?.pause()
- return .success
- }
- return .commandFailed
- }
- }
-
+ fileprivate let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
+
+ fileprivate func setupRemoteTransportControls() {
+ let commandCenter = MPRemoteCommandCenter.shared()
+ commandCenter.playCommand.addTarget { [unowned self] _ in
+ if self.player?.rate == 0.0 {
+ self.player?.play()
+ return .success
+ }
+ return .commandFailed
+ }
+
+ commandCenter.pauseCommand.addTarget { _ in
+ if self.player?.rate == 1.0 {
+ self.player?.pause()
+ return .success
+ }
+ return .commandFailed
+ }
+ }
+
fileprivate func updateGeneralMetadata(video: XCDYouTubeVideo) {
- guard player?.currentItem != nil else {
- nowPlayingInfoCenter.nowPlayingInfo = nil
- return
- }
-
- var nowPlayingInfo = nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()
+ guard player?.currentItem != nil else {
+ nowPlayingInfoCenter.nowPlayingInfo = nil
+ return
+ }
+
+ var nowPlayingInfo = nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()
let title = video.title
-
+
if let thumbnailURL = video.thumbnailURL {
- URLSession.shared.dataTask(with: thumbnailURL) { (data, _, error) in
+ URLSession.shared.dataTask(with: thumbnailURL) { data, _, error in
guard error == nil else { return }
guard data != nil else { return }
guard let image = UIImage(data: data!) else { return }
-
+
let artwork = MPMediaItemArtwork(image: image)
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
self.nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
}.resume()
}
-
- nowPlayingInfo[MPMediaItemPropertyTitle] = title
- nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
- }
-
+
+ nowPlayingInfo[MPMediaItemPropertyTitle] = title
+ nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
+ }
+
fileprivate func updatePlaybackDuration() {
let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
-
- timeObserverToken = self.player?.addPeriodicTimeObserver(forInterval: interval, queue: .main, using: { [weak self] (time) in
+
+ timeObserverToken = player?.addPeriodicTimeObserver(forInterval: interval, queue: .main, using: { [weak self] _ in
guard let player = self?.player else { return }
guard player.currentItem != nil else { return }
-
+
var nowPlayingInfo = self!.nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()
self!.duration = Float(CMTimeGetSeconds(player.currentItem!.duration))
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = self!.duration
@@ -200,16 +201,16 @@ extension UIView {
self!.nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
})
}
-
- fileprivate func updatePlaybackRateMetadata() {
- guard player?.currentItem != nil else {
- duration = 0
- nowPlayingInfoCenter.nowPlayingInfo = nil
- return
- }
-
- var nowPlayingInfo = nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()
- nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player!.rate
- nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = player!.rate
- }
+
+ fileprivate func updatePlaybackRateMetadata() {
+ guard player?.currentItem != nil else {
+ duration = 0
+ nowPlayingInfoCenter.nowPlayingInfo = nil
+ return
+ }
+
+ var nowPlayingInfo = nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()
+ nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player!.rate
+ nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = player!.rate
+ }
}
diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift b/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift
index 0a42b55b9..5f9c64c4c 100644
--- a/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift
+++ b/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift
@@ -6,46 +6,45 @@
// Copyright © 2019 Cédric Luthi. All rights reserved.
//
-import UIKit
import AVKit
+import UIKit
import XCDYouTubeKit
extension DemoFullScreenViewController: VideoPickerControllerDelegate {
func videoPickerController(_ videoPickerController: VideoPickerController!, didSelectVideoWithIdentifier videoIdentifier: String!) {
- self.videoIdentifierTextField.text = videoIdentifier
+ videoIdentifierTextField.text = videoIdentifier
UserDefaults.standard.set(videoIdentifier, forKey: "VideoIdentifier")
}
}
extension DemoFullScreenViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
- self.play(textField)
+ play(textField)
return true
}
-
+
func textFieldDidEndEditing(_ textField: UITextField) {
- UserDefaults.standard.set(self.videoIdentifierTextField.text, forKey: "VideoIdentifier")
+ UserDefaults.standard.set(videoIdentifierTextField.text, forKey: "VideoIdentifier")
}
}
class DemoFullScreenViewController: UIViewController {
-
- @IBOutlet weak open var lowQualitySwitch: UISwitch!
- @IBOutlet weak open var videoIdentifierTextField: UITextField!
+ @IBOutlet open var lowQualitySwitch: UISwitch!
+ @IBOutlet open var videoIdentifierTextField: UITextField!
var ob: NSKeyValueObservation?
private var timeObserverToken: Any?
-
+
override func viewDidLoad() {
super.viewDidLoad()
- self.videoIdentifierTextField.text = UserDefaults.standard.string(forKey: "VideoIdentifier")
+ videoIdentifierTextField.text = UserDefaults.standard.string(forKey: "VideoIdentifier")
}
-
+
@IBAction open func endEditing(_ sender: Any!) {
- self.view.endEditing(true)
+ view.endEditing(true)
}
-
+
@IBAction open func play(_ sender: Any!) {
- XCDYouTubeClient.default().getVideoWithIdentifier(self.videoIdentifierTextField.text) { (video, error) in
+ XCDYouTubeClient.default().getVideoWithIdentifier(videoIdentifierTextField.text) { video, error in
guard error == nil else {
Utilities.shared.displayError(error! as NSError, originViewController: self)
return
diff --git a/XCDYouTubeKit Demo/iOS Demo/Utilities.swift b/XCDYouTubeKit Demo/iOS Demo/Utilities.swift
index a43939296..7a9bc398f 100644
--- a/XCDYouTubeKit Demo/iOS Demo/Utilities.swift
+++ b/XCDYouTubeKit Demo/iOS Demo/Utilities.swift
@@ -10,7 +10,7 @@ import UIKit
@objcMembers class Utilities: NSObject {
static let shared = Utilities()
-
+
func displayError(_ error: NSError, originViewController: UIViewController) {
OperationQueue.main.addOperation {
originViewController.dismiss(animated: true) {
diff --git a/XCDYouTubeKit.podspec b/XCDYouTubeKit-kbexdev.podspec
similarity index 65%
rename from XCDYouTubeKit.podspec
rename to XCDYouTubeKit-kbexdev.podspec
index 01e9a26d3..d10e1ca9e 100644
--- a/XCDYouTubeKit.podspec
+++ b/XCDYouTubeKit-kbexdev.podspec
@@ -1,13 +1,13 @@
Pod::Spec.new do |s|
- s.name = "XCDYouTubeKit"
- s.version = "2.15.2"
- s.summary = "YouTube video player for iOS and OS X."
- s.homepage = "https://github.com/0xced/XCDYouTubeKit"
+ s.name = "XCDYouTubeKit-kbexdev"
+ s.version = "2.16.0"
+ s.summary = "Fork of YouTube video player for iOS and OS X."
+ s.homepage = "https://github.com/kbex-dev/XCDYouTubeKit"
s.screenshot = "https://raw.github.com/0xced/XCDYouTubeKit/#{s.version}/Screenshots/XCDYouTubeVideoPlayerViewController.png"
s.license = { :type => "MIT", :file => "LICENSE" }
- s.author = { "Cédric Luthi" => "cedric.luthi@gmail.com" }
- s.social_media_url = "https://twitter.com/0xced"
- s.source = { :git => "https://github.com/0xced/XCDYouTubeKit.git", :tag => s.version.to_s }
+ s.author = { "kbexdev" => "kbexdev@gmail.com" }
+ s.social_media_url = ""
+ s.source = { :git => "https://github.com/kbex-dev/XCDYouTubeKit.git", :tag => s.version.to_s }
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.9"
s.tvos.deployment_target = "9.0"
diff --git a/XCDYouTubeKit/XCDYouTubeClient.h b/XCDYouTubeKit/XCDYouTubeClient.h
index 573a97155..1fba33cab 100644
--- a/XCDYouTubeKit/XCDYouTubeClient.h
+++ b/XCDYouTubeKit/XCDYouTubeClient.h
@@ -25,6 +25,9 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface XCDYouTubeClient : NSObject
++ (NSString *)innertubeApiKey;
++ (void)setInnertubeApiKey:(NSString *)key;
+
/**
* ------------------
* @name Initializing
diff --git a/XCDYouTubeKit/XCDYouTubeClient.m b/XCDYouTubeKit/XCDYouTubeClient.m
index 5305d1aea..2ae8e2bd4 100644
--- a/XCDYouTubeKit/XCDYouTubeClient.m
+++ b/XCDYouTubeKit/XCDYouTubeClient.m
@@ -14,6 +14,8 @@ @implementation XCDYouTubeClient
@synthesize languageIdentifier = _languageIdentifier;
+static NSString * _innertubeApiKey = @"AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
+
+ (instancetype) defaultClient
{
static XCDYouTubeClient *defaultClient;
@@ -29,6 +31,15 @@ - (instancetype) init
return [self initWithLanguageIdentifier:nil];
}
+
++ (NSString *)innertubeApiKey {
+ return _innertubeApiKey;
+}
+
++ (void)setInnertubeApiKey:(NSString *)key {
+ _innertubeApiKey = key;
+}
+
- (instancetype) initWithLanguageIdentifier:(NSString *)languageIdentifier
{
if (!(self = [super init]))
diff --git a/XCDYouTubeKit/XCDYouTubeVideo.m b/XCDYouTubeKit/XCDYouTubeVideo.m
index 637fafe20..761ed770f 100644
--- a/XCDYouTubeKit/XCDYouTubeVideo.m
+++ b/XCDYouTubeKit/XCDYouTubeVideo.m
@@ -171,6 +171,9 @@ - (instancetype) initWithIdentifier:(NSString *)identifier info:(NSDictionary *)
NSString *streamMap = info[@"url_encoded_fmt_stream_map"];
NSArray *alternativeStreamMap = XCDStreamingDataWithString(playerResponse)[@"formats"] == nil ? info[@"streamingData"][@"formats"] : XCDStreamingDataWithString(playerResponse)[@"formats"];
NSString *httpLiveStream = info[@"hlsvp"] ?: XCDHTTPLiveStreamingStringWithString(playerResponse);
+ if(httpLiveStream.length == 0){
+ httpLiveStream = info[@"streamingData"][@"hlsManifestUrl"];
+ }
NSString *adaptiveFormats = info[@"adaptive_fmts"];
NSArray *alternativeAdaptiveFormats = XCDStreamingDataWithString(playerResponse)[@"adaptiveFormats"] == nil ? info[@"streamingData"][@"adaptiveFormats"] : XCDStreamingDataWithString(playerResponse)[@"adaptiveFormats"];
NSDictionary *videoDetails = XCDDictionaryWithString(playerResponse)[@"videoDetails"] == nil ? info[@"videoDetails"] : XCDDictionaryWithString(playerResponse)[@"videoDetails"];
@@ -220,9 +223,18 @@ - (instancetype) initWithIdentifier:(NSString *)identifier info:(NSDictionary *)
NSString *thumbnail = info[@"thumbnail_url"] ?: info[@"iurl"];
NSURL *thumbnailURL = thumbnail ? [NSURL URLWithString:thumbnail] : nil;
_thumbnailURL = thumbnailURL;
-
+
if (!_thumbnailURL) {
NSArray *thumbnails = XCDThumnailArrayWithString(playerResponse);
+ if (!thumbnails) {
+ NSDictionary *thumbnailDictionary = videoDetails[@"thumbnail"];
+ if (thumbnailDictionary && [thumbnailDictionary isKindOfClass:[NSDictionary class]]) {
+ NSArray *thumbnailArray = thumbnailDictionary[@"thumbnails"];
+ if (thumbnailArray && [thumbnailArray isKindOfClass:[NSArray class]]) {
+ thumbnails = thumbnailArray;
+ }
+ }
+ }
if (thumbnails.count >= 1) {
// Prepare array of thumbnails URLs.
NSMutableArray *thumbnailURLs = [[NSMutableArray alloc] initWithCapacity:thumbnails.count];
@@ -251,8 +263,10 @@ - (instancetype) initWithIdentifier:(NSString *)identifier info:(NSDictionary *)
NSMutableDictionary *streamURLs = [NSMutableDictionary new];
- if (httpLiveStream)
+ if (httpLiveStream != nil) {
+ _streamURL = [NSURL URLWithString:httpLiveStream];
streamURLs[XCDYouTubeVideoQualityHTTPLiveStreaming] = [NSURL URLWithString:httpLiveStream];
+ }
NSMutableDictionary *captionURLs = [NSMutableDictionary new];
NSMutableDictionary *autoGeneratedCaptionURLs = [NSMutableDictionary new];
diff --git a/XCDYouTubeKit/XCDYouTubeVideoOperation.m b/XCDYouTubeKit/XCDYouTubeVideoOperation.m
index be4ce9ee1..dbaa8c1b6 100644
--- a/XCDYouTubeKit/XCDYouTubeVideoOperation.m
+++ b/XCDYouTubeKit/XCDYouTubeVideoOperation.m
@@ -12,6 +12,7 @@
#import "XCDYouTubeDashManifestXML.h"
#import "XCDYouTubePlayerScript.h"
#import "XCDYouTubeLogger+Private.h"
+#import "XCDYouTubeClient.h"
typedef NS_ENUM(NSUInteger, XCDYouTubeRequestType) {
XCDYouTubeRequestTypeGetVideoInfo = 1,
@@ -35,6 +36,8 @@ @interface XCDYouTubeVideoOperation ()
@property (atomic, readonly) NSURLSession *session;
@property (atomic, strong) NSURLSessionDataTask *dataTask;
++(NSDateFormatter*) dateFormatter;
+
@property (atomic, assign) BOOL isExecuting;
@property (atomic, assign) BOOL isFinished;
@property (atomic, readonly) dispatch_semaphore_t operationStartSemaphore;
@@ -146,19 +149,30 @@ - (void) startNextRequest
}
else
{
- NSString *eventLabel = [self.eventLabels objectAtIndex:0];
[self.eventLabels removeObjectAtIndex:0];
- NSDictionary *query = @{ @"video_id": self.videoIdentifier, @"hl": self.languageIdentifier, @"el": eventLabel, @"ps": @"default" };
- NSString *queryString = XCDQueryStringWithDictionary(query);
- NSURL *videoInfoURL = [NSURL URLWithString:[@"https://www.youtube.com/get_video_info?" stringByAppendingString:queryString]];
- [self startRequestWithURL:videoInfoURL type:XCDYouTubeRequestTypeGetVideoInfo];
+ NSString *urlString = [NSString stringWithFormat:@"https://www.youtube.com/youtubei/v1/player?key=%@", XCDYouTubeClient.innertubeApiKey];
+ NSURL *url = [NSURL URLWithString:urlString];
+
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
+
+ [request setHTTPMethod:@"POST"];
+
+ NSString *string = [NSString stringWithFormat:@"{'context': {'client': {'hl': 'en','clientName': 'ANDROID','clientVersion': '16.20','playbackContext': {'contentPlaybackContext': {'html5Preference': 'HTML5_PREF_WANTS'}}}},'contentCheckOk': true,'racyCheckOk': true,'videoId': '%@'}", self.videoIdentifier];
+
+ NSData *postData = [string dataUsingEncoding:NSASCIIStringEncoding];
+
+ [request setHTTPBody:postData];
+
+ [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
+
+ [self startRequestWith:request type:XCDYouTubeRequestTypeGetVideoInfo];
}
}
- (void) startWatchPageRequest
{
- NSDictionary *query = @{ @"v": self.videoIdentifier, @"hl": self.languageIdentifier, @"has_verified": @YES, @"bpctr": @9999999999 };
+ NSDictionary *query = @{ @"v": self.videoIdentifier, @"hl": self.languageIdentifier, @"has_verified": @YES, @"bpctr": @9999999999, @"c": @"ANDROID" };
NSString *queryString = XCDQueryStringWithDictionary(query);
NSURL *webpageURL = [NSURL URLWithString:[@"https://www.youtube.com/watch?" stringByAppendingString:queryString]];
[self startRequestWithURL:webpageURL type:XCDYouTubeRequestTypeWatchPage];
@@ -206,6 +220,39 @@ - (void) startRequestWithURL:(NSURL *)url type:(XCDYouTubeRequestType)requestTyp
self.requestType = requestType;
}
+- (void) startRequestWith:(NSMutableURLRequest *)request type:(XCDYouTubeRequestType)requestType
+{
+ if (self.isCancelled)
+ return;
+
+ // Max (age-restricted VEVO) = 2×GetVideoInfo + 1×WatchPage + 2×EmbedPage + 1×JavaScriptPlayer + 1×GetVideoInfo + 1xDashManifest
+ if (++self.requestCount > 8)
+ {
+ // This condition should never happen but the request flow is quite complex so better abort here than go into an infinite loop of requests
+ [self finishWithError];
+ return;
+ }
+
+ XCDYouTubeLogDebug(@"Starting request: %@", [request URL]);
+
+ [request setValue:self.languageIdentifier forHTTPHeaderField:@"Accept-Language"];
+ [request setValue:[NSString stringWithFormat:@"https://youtube.com/watch?v=%@", self.videoIdentifier] forHTTPHeaderField:@"Referer"];
+
+ self.dataTask = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
+ {
+ if (self.isCancelled)
+ return;
+
+ if (error)
+ [self handleConnectionError:error requestType:requestType];
+ else
+ [self handleConnectionSuccessWithData:data response:response requestType:requestType];
+ }];
+ [self.dataTask resume];
+
+ self.requestType = requestType;
+}
+
#pragma mark - Response Dispatch
- (void) handleConnectionSuccessWithData:(NSData *)data response:(NSURLResponse *)response requestType:(XCDYouTubeRequestType)requestType
@@ -222,6 +269,11 @@ - (void) handleConnectionSuccessWithData:(NSData *)data response:(NSURLResponse
[self handleConnectionError:[NSError errorWithDomain:XCDYouTubeVideoErrorDomain code:XCDYouTubeErrorTooManyRequests userInfo:@{NSLocalizedDescriptionKey : @"The operation couldn’t be completed because too many requests were sent."}] requestType:requestType];
return;
}
+ if ([(NSHTTPURLResponse *)response statusCode] == 404 && responseString.length == 0)
+ {
+ [self handleConnectionError:[NSError errorWithDomain:XCDYouTubeVideoErrorDomain code:XCDYouTubeErrorEmptyResponse userInfo:@{NSLocalizedDescriptionKey : @"The response is empty."}] requestType:requestType];
+ return;
+ }
if (responseString.length == 0)
{
//Previously we would throw an assertion here, however, this has been changed to an error
@@ -235,7 +287,7 @@ - (void) handleConnectionSuccessWithData:(NSData *)data response:(NSURLResponse
switch (requestType)
{
case XCDYouTubeRequestTypeGetVideoInfo:
- [self handleVideoInfoResponseWithInfo:XCDDictionaryWithQueryString(responseString) response:response];
+ [self handleVideoInfoResponseWithInfo:XCDDictionaryWithString(responseString) response:response];
break;
case XCDYouTubeRequestTypeWatchPage:
[self handleWebPageWithHTMLString:responseString];
@@ -270,10 +322,69 @@ - (void) handleConnectionError:(NSError *)connectionError requestType:(XCDYouTub
#pragma mark - Response Parsing
+- (void) initializeConsentWithResponse:(NSURLResponse *)response {
+ NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
+ if (httpResponse && response.URL) {
+ NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:httpResponse.allHeaderFields forURL:(NSURL *_Nonnull)response.URL];
+
+ for (NSHTTPCookie *cookie in cookies) {
+ if ([cookie.name isEqualToString:@"__Secure-3PSID"]) return;
+ }
+
+ for (NSHTTPCookie *cookie in cookies) {
+ if ([cookie.name isEqualToString:@"CONSENT"]) {
+ if ([cookie.value isEqualToString:@"YES"]) return;
+
+ NSString *rawConsentID = [cookie.value stringByReplacingOccurrencesOfString:@"PENDING+" withString:@""];
+ int consentID = [rawConsentID intValue];
+
+ // generate random consent id, if doesn't match expected format
+ if (consentID < 100 || consentID > 999) {
+ consentID = 100 + (int)arc4random_uniform((uint32_t)(999 - 100 + 1));
+ }
+
+ NSString *cookieValue = [[NSString alloc] initWithFormat:@"YES+cb.%@-17-p0.en+FX+%i", [self youtubeConsentDateString], consentID];
+ NSHTTPCookie *consentCookie = [NSHTTPCookie cookieWithProperties:@{
+ NSHTTPCookiePath: @"/",
+ NSHTTPCookieName: @"CONSENT",
+ NSHTTPCookieValue: cookieValue,
+ NSHTTPCookieDomain:@".youtube.com",
+ NSHTTPCookieSecure:@"TRUE"
+ }];
+ [self.session.configuration.HTTPCookieStorage setCookie:consentCookie];
+ return;
+ }
+ }
+
+ }
+}
+
+- (NSString *) youtubeConsentDateString {
+ NSDateComponents *offset = [NSDateComponents new];
+ [offset setDay: -1];
+ NSDate *yesterday = [NSCalendar.currentCalendar dateByAddingComponents:offset toDate:[NSDate new] options:0];
+ return [XCDYouTubeVideoOperation.dateFormatter stringFromDate: yesterday];
+}
+
++ (NSDateFormatter*) dateFormatter {
+ static NSDateFormatter *formatter = nil;
+
+ static dispatch_once_t oncePredicate;
+
+ dispatch_once(&oncePredicate, ^{
+ formatter = [NSDateFormatter new];
+ [formatter setDateFormat:@"yyyyMMdd"];
+ });
+
+ return formatter;
+}
+
- (void) handleVideoInfoResponseWithInfo:(NSDictionary *)info response:(NSURLResponse *)response
{
XCDYouTubeLogDebug(@"Handling video info response");
+ [self initializeConsentWithResponse:response];
+
NSError *error = nil;
XCDYouTubeVideo *video = [[XCDYouTubeVideo alloc] initWithIdentifier:self.videoIdentifier info:info playerScript:self.playerScript response:response error:&error];
if (video)
@@ -358,7 +469,7 @@ - (void) handleJavaScriptPlayerWithScript:(NSString *)script
{
NSString *eurl = [@"https://youtube.googleapis.com/v/" stringByAppendingString:self.videoIdentifier];
NSString *sts = self.embedWebpage.sts ?: self.webpage.sts ?: @"";
- NSDictionary *query = @{ @"video_id": self.videoIdentifier, @"hl": self.languageIdentifier, @"eurl": eurl, @"sts": sts};
+ NSDictionary *query = @{ @"video_id": self.videoIdentifier, @"hl": self.languageIdentifier, @"eurl": eurl, @"sts": sts, @"html5" : @"1", @"c": @"ANDROID", @"cver": @"16.05.7"};
NSString *queryString = XCDQueryStringWithDictionary(query);
NSURL *videoInfoURL = [NSURL URLWithString:[@"https://www.youtube.com/get_video_info?" stringByAppendingString:queryString]];
[self startRequestWithURL:videoInfoURL type:XCDYouTubeRequestTypeGetVideoInfo];
@@ -439,7 +550,7 @@ - (void) start
self.isExecuting = YES;
- self.eventLabels = [[NSMutableArray alloc] initWithArray:@[ @"embedded", @"detailpage" ]];
+ self.eventLabels = [[NSMutableArray alloc] initWithArray:@[ @"embedded", @"detailpage", @"embedded" ]];
[self startNextRequest];
}