From e9a8804f123218598d75d139b9a6faa9a8830750 Mon Sep 17 00:00:00 2001 From: Leo Thomas Date: Thu, 8 Sep 2016 01:37:41 +0200 Subject: [PATCH 1/4] migrated to swift 3 (shipped with Xcode 8 GM) --- AppDelegate.swift | 18 ++-- .../project.pbxproj | 29 +++++-- .../AppIcon.appiconset/Contents.json | 20 +++++ YoutubeSourceParserKit/Youtube.swift | 87 +++++++++---------- .../YoutubeSourceParserKitTests.swift | 32 +++---- 5 files changed, 110 insertions(+), 76 deletions(-) diff --git a/AppDelegate.swift b/AppDelegate.swift index d8540ff..29530c5 100644 --- a/AppDelegate.swift +++ b/AppDelegate.swift @@ -12,12 +12,12 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - let testURL = NSURL(string: "https://www.youtube.com/watch?v=swZJwZeMesk")! + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + let testURL = URL(string: "https://www.youtube.com/watch?v=swZJwZeMesk")! Youtube.h264videosWithYoutubeURL(testURL) { (videoInfo, error) -> Void in if let videoURLString = videoInfo?["url"] as? String, - videoTitle = videoInfo?["title"] as? String { + let videoTitle = videoInfo?["title"] as? String { print("\(videoTitle)") print("\(videoURLString)") } @@ -25,13 +25,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } - func applicationWillResignActive(application: UIApplication) { } + func applicationWillResignActive(_ application: UIApplication) { } - func applicationDidEnterBackground(application: UIApplication) { } + func applicationDidEnterBackground(_ application: UIApplication) { } - func applicationWillEnterForeground(application: UIApplication) { } + func applicationWillEnterForeground(_ application: UIApplication) { } - func applicationDidBecomeActive(application: UIApplication) { } + func applicationDidBecomeActive(_ application: UIApplication) { } - func applicationWillTerminate(application: UIApplication) { } + func applicationWillTerminate(_ application: UIApplication) { } } diff --git a/YoutubeSourceParserKit.xcodeproj/project.pbxproj b/YoutubeSourceParserKit.xcodeproj/project.pbxproj index 46460e7..a1c0b9b 100644 --- a/YoutubeSourceParserKit.xcodeproj/project.pbxproj +++ b/YoutubeSourceParserKit.xcodeproj/project.pbxproj @@ -33,7 +33,7 @@ B24965E31C3398310050DF64 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C3A838771BB746A0000C5D33 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; C3FC20CD1B4996150000E818 /* YoutubeSourceParserKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YoutubeSourceParserKit.app; sourceTree = BUILT_PRODUCTS_DIR; }; - C3FC20E21B4996150000E818 /* YoutubeSourceParserKit.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YoutubeSourceParserKit.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C3FC20E21B4996150000E818 /* YoutubeSourceParserKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YoutubeSourceParserKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -88,7 +88,7 @@ isa = PBXGroup; children = ( C3FC20CD1B4996150000E818 /* YoutubeSourceParserKit.app */, - C3FC20E21B4996150000E818 /* YoutubeSourceParserKit.xctest */, + C3FC20E21B4996150000E818 /* YoutubeSourceParserKitTests.xctest */, ); name = Products; sourceTree = ""; @@ -128,7 +128,7 @@ ); name = YoutubeSourceParserKitTests; productName = "youtube-parserTests"; - productReference = C3FC20E21B4996150000E818 /* YoutubeSourceParserKit.xctest */; + productReference = C3FC20E21B4996150000E818 /* YoutubeSourceParserKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -139,14 +139,16 @@ attributes = { LastSwiftMigration = 0710; LastSwiftUpdateCheck = 0710; - LastUpgradeCheck = 0710; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = "Toygar Dündaralp"; TargetAttributes = { C3FC20CC1B4996150000E818 = { CreatedOnToolsVersion = 6.4; + LastSwiftMigration = 0800; }; C3FC20E11B4996150000E818 = { CreatedOnToolsVersion = 6.4; + LastSwiftMigration = 0800; TestTargetID = C3FC20CC1B4996150000E818; }; }; @@ -231,13 +233,16 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -277,13 +282,16 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -297,6 +305,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -312,6 +321,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.dundaralp.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = YoutubeSourceParserKit; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -325,6 +335,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.dundaralp.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = YoutubeSourceParserKit; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -337,10 +348,11 @@ "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = "youtube-parserTests/Info.plist"; + INFOPLIST_FILE = YoutubeSourceParserKitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.dundaralp.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = YoutubeSourceParserKit; + PRODUCT_NAME = YoutubeSourceParserKitTests; + SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YoutubeSourceParserKit.app/YoutubeSourceParserKit"; }; name = Debug; @@ -350,10 +362,11 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; FRAMEWORK_SEARCH_PATHS = ""; - INFOPLIST_FILE = "youtube-parserTests/Info.plist"; + INFOPLIST_FILE = YoutubeSourceParserKitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.dundaralp.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = YoutubeSourceParserKit; + PRODUCT_NAME = YoutubeSourceParserKitTests; + SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YoutubeSourceParserKit.app/YoutubeSourceParserKit"; }; name = Release; diff --git a/YoutubeSourceParserKit/Images.xcassets/AppIcon.appiconset/Contents.json b/YoutubeSourceParserKit/Images.xcassets/AppIcon.appiconset/Contents.json index eeea76c..1d060ed 100644 --- a/YoutubeSourceParserKit/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/YoutubeSourceParserKit/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", @@ -30,6 +40,16 @@ "size" : "60x60", "scale" : "3x" }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, { "idiom" : "ipad", "size" : "29x29", diff --git a/YoutubeSourceParserKit/Youtube.swift b/YoutubeSourceParserKit/Youtube.swift index 15c57ad..bc2f83a 100644 --- a/YoutubeSourceParserKit/Youtube.swift +++ b/YoutubeSourceParserKit/Youtube.swift @@ -6,9 +6,9 @@ // Copyright (c) 2015 Toygar Dündaralp. All rights reserved. // -import UIKit +import Foundation -public extension NSURL { +public extension URL { /** Parses a query string of an NSURL @@ -20,7 +20,7 @@ public extension NSURL { } // Note: find youtube ID in m.youtube.com "https://m.youtube.com/#/watch?v=1hZ98an9wjo" - let result = absoluteString.componentsSeparatedByString("?") + let result = absoluteString.components(separatedBy: "?") if result.count > 1 { return result.last?.dictionaryFromQueryStringComponents() } @@ -33,8 +33,8 @@ public extension NSString { Convenient method for decoding a html encoded string */ func stringByDecodingURLFormat() -> String { - let result = self.stringByReplacingOccurrencesOfString("+", withString:" ") - return result.stringByRemovingPercentEncoding! + let result = self.replacingOccurrences(of: "+", with:" ") + return result.removingPercentEncoding! } /** @@ -44,20 +44,20 @@ public extension NSString { */ func dictionaryFromQueryStringComponents() -> [String: AnyObject] { var parameters = [String: AnyObject]() - for keyValue in componentsSeparatedByString("&") { - let keyValueArray = keyValue.componentsSeparatedByString("=") + for keyValue in components(separatedBy: "&") { + let keyValueArray = keyValue.components(separatedBy: "=") if keyValueArray.count < 2 { continue } let key = keyValueArray[0].stringByDecodingURLFormat() let value = keyValueArray[1].stringByDecodingURLFormat() - parameters[key] = value + parameters[key] = value as AnyObject? } return parameters } } -public class Youtube: NSObject { +open class Youtube: NSObject { static let infoURL = "http://www.youtube.com/get_video_info?video_id=" static var userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4" /** @@ -66,21 +66,20 @@ public class Youtube: NSObject { @param youtubeURL the the complete youtube video url, either youtu.be or youtube.com @return string with desired youtube id */ - public static func youtubeIDFromYoutubeURL(youtubeURL: NSURL) -> String? { - if let - youtubeHost = youtubeURL.host, - youtubePathComponents = youtubeURL.pathComponents { + open static func youtubeIDFromYoutubeURL(_ youtubeURL: URL) -> String? { + if let youtubeHost = youtubeURL.host { + let youtubePathComponents = youtubeURL.pathComponents let youtubeAbsoluteString = youtubeURL.absoluteString if youtubeHost == "youtu.be" as String? { return youtubePathComponents[1] - } else if youtubeAbsoluteString.rangeOfString("www.youtube.com/embed") != nil { + } else if youtubeAbsoluteString.range(of: "www.youtube.com/embed") != nil { return youtubePathComponents[2] } else if youtubeHost == "youtube.googleapis.com" || - youtubeURL.pathComponents!.first == "www.youtube.com" as String? { + youtubeURL.pathComponents.first == "www.youtube.com" as String? { return youtubePathComponents[2] } else if let queryString = youtubeURL.dictionaryForQueryString(), - searchParam = queryString["v"] as? String { + let searchParam = queryString["v"] as? String { return searchParam } } @@ -93,24 +92,24 @@ public class Youtube: NSObject { @return dictionary with the available formats for the selected video */ - public static func h264videosWithYoutubeID(youtubeID: String) -> [String: AnyObject]? { + open static func h264videosWithYoutubeID(_ youtubeID: String) -> [String: AnyObject]? { let urlString = String(format: "%@%@", infoURL, youtubeID) as String - let url = NSURL(string: urlString)! - let request = NSMutableURLRequest(URL: url) + let url = URL(string: urlString)! + var request = URLRequest(url:url) request.timeoutInterval = 5.0 request.setValue(userAgent, forHTTPHeaderField: "User-Agent") - request.HTTPMethod = "GET" + request.httpMethod = "GET" var responseString = NSString() - let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration()) - let group = dispatch_group_create() - dispatch_group_enter(group) - session.dataTaskWithRequest(request, completionHandler: { (data, response, _) -> Void in - if let data = data as NSData? { - responseString = NSString(data: data, encoding: NSUTF8StringEncoding)! + let session = URLSession(configuration: URLSessionConfiguration.default) + let group = DispatchGroup() + group.enter() + session.dataTask(with: request, completionHandler: { (data, response, _) -> Void in + if let data = data as Data? { + responseString = String(data: data, encoding: .utf8)! as NSString } - dispatch_group_leave(group) + group.leave() }).resume() - dispatch_group_wait(group, DISPATCH_TIME_FOREVER) + group.wait(timeout: DispatchTime.distantFuture) let parts = responseString.dictionaryFromQueryStringComponents() if parts.count > 0 { var videoTitle: String = "" @@ -126,18 +125,18 @@ public class Youtube: NSObject { if let _: AnyObject = parts["live_playback"]{ if let hlsvp = parts["hlsvp"] as? String { return [ - "url": "\(hlsvp)", - "title": "\(videoTitle)", - "image": "\(streamImage)", - "isStream": true + "url": "\(hlsvp)" as AnyObject, + "title": "\(videoTitle)" as AnyObject, + "image": "\(streamImage)" as AnyObject, + "isStream": true as AnyObject ] } } else { - let fmtStreamMapArray = fmtStreamMap.componentsSeparatedByString(",") + let fmtStreamMapArray = fmtStreamMap.components(separatedBy: ",") for videoEncodedString in fmtStreamMapArray { var videoComponents = videoEncodedString.dictionaryFromQueryStringComponents() - videoComponents["title"] = videoTitle - videoComponents["isStream"] = false + videoComponents["title"] = videoTitle as AnyObject? + videoComponents["isStream"] = false as AnyObject? return videoComponents as [String: AnyObject] } } @@ -153,17 +152,17 @@ public class Youtube: NSObject { @param completeBlock the block which is called on completion */ - public static func h264videosWithYoutubeURL(youtubeURL: NSURL,completion: (( - videoInfo: [String: AnyObject]?, error: NSError?) -> Void)?) { - let priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND - dispatch_async(dispatch_get_global_queue(priority, 0)) { - if let youtubeID = self.youtubeIDFromYoutubeURL(youtubeURL), videoInformation = self.h264videosWithYoutubeID(youtubeID) { - dispatch_async(dispatch_get_main_queue()) { - completion?(videoInfo: videoInformation, error: nil) + open static func h264videosWithYoutubeURL(_ youtubeURL: URL,completion: (( + _ videoInfo: [String: AnyObject]?, _ error: NSError?) -> Void)?) { + let priority = DispatchQueue.GlobalQueuePriority.background + DispatchQueue.global(priority: priority).async { + if let youtubeID = self.youtubeIDFromYoutubeURL(youtubeURL), let videoInformation = self.h264videosWithYoutubeID(youtubeID) { + DispatchQueue.main.async { + completion?(videoInformation, nil) } }else{ - dispatch_async(dispatch_get_main_queue()) { - completion?(videoInfo: nil, error: NSError(domain: "com.player.youtube.backgroundqueue", code: 1001, userInfo: ["error": "Invalid YouTube URL"])) + DispatchQueue.main.async { + completion?(nil, NSError(domain: "com.player.youtube.backgroundqueue", code: 1001, userInfo: ["error": "Invalid YouTube URL"])) } } } diff --git a/YoutubeSourceParserKitTests/YoutubeSourceParserKitTests.swift b/YoutubeSourceParserKitTests/YoutubeSourceParserKitTests.swift index f4fad58..43511f5 100644 --- a/YoutubeSourceParserKitTests/YoutubeSourceParserKitTests.swift +++ b/YoutubeSourceParserKitTests/YoutubeSourceParserKitTests.swift @@ -6,8 +6,10 @@ // Copyright (c) 2015 Toygar Dündaralp. All rights reserved. // -import UIKit +import Foundation import XCTest +@testable import YoutubeSourceParserKit + class YoutubeSourceParserKitTests: XCTestCase { @@ -40,7 +42,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testInvalidURLs1() { - let sampleLink = NSURL(string: "?v=1hZ98an9wjo")! + let sampleLink = URL(string: "?v=1hZ98an9wjo")! XCTAssertNil(Youtube.youtubeIDFromYoutubeURL(sampleLink), "Youtube ID is not nil") if let youtubeID = Youtube.youtubeIDFromYoutubeURL(sampleLink) { XCTAssertEqual(youtubeID, "1hZ98an9wjo", "Youtube ID not same") @@ -48,7 +50,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testInvalidURLs2() { - let sampleLink = NSURL(string: "?v=")! + let sampleLink = URL(string: "?v=")! XCTAssertNil(Youtube.youtubeIDFromYoutubeURL(sampleLink), "Youtube ID is not nil") if let youtubeID = Youtube.youtubeIDFromYoutubeURL(sampleLink) { XCTAssertEqual(youtubeID, "", "Youtube ID not same") @@ -56,7 +58,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testInvalidURLs3() { - let sampleLink = NSURL(string: "v=1hZ98an9wjo")! + let sampleLink = URL(string: "v=1hZ98an9wjo")! XCTAssertNil(Youtube.youtubeIDFromYoutubeURL(sampleLink), "Youtube ID is not nil") if let youtubeID = Youtube.youtubeIDFromYoutubeURL(sampleLink) { XCTAssertEqual(youtubeID, "v=1hZ98an9wjo", "Youtube ID not same") @@ -64,7 +66,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testInvalidURLs4() { - let sampleLink = NSURL(string: "v1hZ98an9wjo")! + let sampleLink = URL(string: "v1hZ98an9wjo")! XCTAssertNil(Youtube.youtubeIDFromYoutubeURL(sampleLink), "Youtube ID is not nil") if let youtubeID = Youtube.youtubeIDFromYoutubeURL(sampleLink) { XCTAssertEqual(youtubeID, "v1hZ98an9wjo", "Youtube ID not same") @@ -72,7 +74,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testYoutubeIDFromYoutubeURL() { - let sampleLink = NSURL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo")! + let sampleLink = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo")! XCTAssertNotNil(Youtube.youtubeIDFromYoutubeURL(sampleLink), "Youtube ID is nil") if let youtubeID = Youtube.youtubeIDFromYoutubeURL(sampleLink) { XCTAssertEqual(youtubeID, "1hZ98an9wjo", "Youtube ID not same") @@ -80,7 +82,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testYoutubeIDFromMobileYoutubeURL() { - let sampleLink = NSURL(string: "https://m.youtube.com/#/watch?v=1hZ98an9wjo")! + let sampleLink = URL(string: "https://m.youtube.com/#/watch?v=1hZ98an9wjo")! XCTAssertNotNil(Youtube.youtubeIDFromYoutubeURL(sampleLink), "Youtube ID is nil") if let youtubeID = Youtube.youtubeIDFromYoutubeURL(sampleLink) { XCTAssertEqual(youtubeID, "1hZ98an9wjo", "Youtube ID not same") @@ -106,7 +108,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testVideoQuality(){ - if let videoURL = NSURL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { + if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") if let info = videoInfo as [String:AnyObject]? { @@ -119,7 +121,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testItagControl(){ - if let videoURL = NSURL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { + if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") if let info = videoInfo as [String:AnyObject]? { @@ -132,7 +134,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testVideoTypeControl(){ - if let videoURL = NSURL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { + if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") if let info = videoInfo as [String:AnyObject]? { @@ -145,7 +147,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testFallbackHostControl(){ - if let videoURL = NSURL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { + if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") if let info = videoInfo as [String:AnyObject]? { @@ -158,7 +160,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testIsLiveVideoTest() { - if let liveVideoURL = NSURL(string: "https://www.youtube.com/watch?v=rxGoGg7n77A"){ + if let liveVideoURL = URL(string: "https://www.youtube.com/watch?v=kMcL3Zr9iyQ"){ Youtube.h264videosWithYoutubeURL(liveVideoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") if let info = videoInfo as [String:AnyObject]? { @@ -177,8 +179,8 @@ class YoutubeSourceParserKitTests: XCTestCase { } func testH264videosWithYoutubeURLBlock(){ - let expectation: XCTestExpectation = self.expectationWithDescription("Handler called") - if let videoURL = NSURL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { + let expectation: XCTestExpectation = self.expectation(description: "Handler called") + if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in expectation.fulfill() if let info = videoInfo as [String:AnyObject]? { @@ -187,7 +189,7 @@ class YoutubeSourceParserKitTests: XCTestCase { } } }) - self.waitForExpectationsWithTimeout(2, handler: nil) + self.waitForExpectations(timeout: 2, handler: nil) } } From ec945eaf3e116ad1a191f59eedd7f9cbfb20b2eb Mon Sep 17 00:00:00 2001 From: Leo Thomas Date: Thu, 8 Sep 2016 01:40:15 +0200 Subject: [PATCH 2/4] replaced all AnyObject with Any --- YoutubeSourceParserKit/Youtube.swift | 28 +++++++++---------- .../YoutubeSourceParserKitTests.swift | 14 +++++----- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/YoutubeSourceParserKit/Youtube.swift b/YoutubeSourceParserKit/Youtube.swift index bc2f83a..af716d9 100644 --- a/YoutubeSourceParserKit/Youtube.swift +++ b/YoutubeSourceParserKit/Youtube.swift @@ -14,7 +14,7 @@ public extension URL { @return key value dictionary with each parameter as an array */ - func dictionaryForQueryString() -> [String: AnyObject]? { + func dictionaryForQueryString() -> [String: Any]? { if let query = self.query { return query.dictionaryFromQueryStringComponents() } @@ -42,8 +42,8 @@ public extension NSString { @return key value dictionary with each parameter as an array */ - func dictionaryFromQueryStringComponents() -> [String: AnyObject] { - var parameters = [String: AnyObject]() + func dictionaryFromQueryStringComponents() -> [String: Any] { + var parameters = [String: Any]() for keyValue in components(separatedBy: "&") { let keyValueArray = keyValue.components(separatedBy: "=") if keyValueArray.count < 2 { @@ -51,7 +51,7 @@ public extension NSString { } let key = keyValueArray[0].stringByDecodingURLFormat() let value = keyValueArray[1].stringByDecodingURLFormat() - parameters[key] = value as AnyObject? + parameters[key] = value as Any? } return parameters } @@ -92,7 +92,7 @@ open class Youtube: NSObject { @return dictionary with the available formats for the selected video */ - open static func h264videosWithYoutubeID(_ youtubeID: String) -> [String: AnyObject]? { + open static func h264videosWithYoutubeID(_ youtubeID: String) -> [String: Any]? { let urlString = String(format: "%@%@", infoURL, youtubeID) as String let url = URL(string: urlString)! var request = URLRequest(url:url) @@ -122,22 +122,22 @@ open class Youtube: NSObject { } if let fmtStreamMap = parts["url_encoded_fmt_stream_map"] as? String { // Live Stream - if let _: AnyObject = parts["live_playback"]{ + if let _: Any = parts["live_playback"]{ if let hlsvp = parts["hlsvp"] as? String { return [ - "url": "\(hlsvp)" as AnyObject, - "title": "\(videoTitle)" as AnyObject, - "image": "\(streamImage)" as AnyObject, - "isStream": true as AnyObject + "url": "\(hlsvp)" as Any, + "title": "\(videoTitle)" as Any, + "image": "\(streamImage)" as Any, + "isStream": true as Any ] } } else { let fmtStreamMapArray = fmtStreamMap.components(separatedBy: ",") for videoEncodedString in fmtStreamMapArray { var videoComponents = videoEncodedString.dictionaryFromQueryStringComponents() - videoComponents["title"] = videoTitle as AnyObject? - videoComponents["isStream"] = false as AnyObject? - return videoComponents as [String: AnyObject] + videoComponents["title"] = videoTitle as Any? + videoComponents["isStream"] = false as Any? + return videoComponents as [String: Any] } } } @@ -153,7 +153,7 @@ open class Youtube: NSObject { */ open static func h264videosWithYoutubeURL(_ youtubeURL: URL,completion: (( - _ videoInfo: [String: AnyObject]?, _ error: NSError?) -> Void)?) { + _ videoInfo: [String: Any]?, _ error: NSError?) -> Void)?) { let priority = DispatchQueue.GlobalQueuePriority.background DispatchQueue.global(priority: priority).async { if let youtubeID = self.youtubeIDFromYoutubeURL(youtubeURL), let videoInformation = self.h264videosWithYoutubeID(youtubeID) { diff --git a/YoutubeSourceParserKitTests/YoutubeSourceParserKitTests.swift b/YoutubeSourceParserKitTests/YoutubeSourceParserKitTests.swift index 43511f5..13a8e1c 100644 --- a/YoutubeSourceParserKitTests/YoutubeSourceParserKitTests.swift +++ b/YoutubeSourceParserKitTests/YoutubeSourceParserKitTests.swift @@ -31,7 +31,7 @@ class YoutubeSourceParserKitTests: XCTestCase { func testDictionaryFromQueryStringComponents() { let sampleLink = "https://www.youtube.com/watch?v=o0jJiB2Ygpg&list=RDo0jJiB2Ygpg" - let dictionary = sampleLink.dictionaryFromQueryStringComponents() as [String:AnyObject] + let dictionary = sampleLink.dictionaryFromQueryStringComponents() as [String:Any] XCTAssertNotNil(dictionary["list"], "url dictionary parse error") if let list = dictionary["list"] as? String { XCTAssertEqual(list, "RDo0jJiB2Ygpg", "list not equal value") @@ -111,7 +111,7 @@ class YoutubeSourceParserKitTests: XCTestCase { if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") - if let info = videoInfo as [String:AnyObject]? { + if let info = videoInfo as [String:Any]? { if let quality = info["quality"] as? String { XCTAssertEqual(quality, "hd720", "quality not equal") } @@ -124,7 +124,7 @@ class YoutubeSourceParserKitTests: XCTestCase { if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") - if let info = videoInfo as [String:AnyObject]? { + if let info = videoInfo as [String:Any]? { if let itag = info["itag"] as? String { XCTAssertEqual(itag, "22", "itag not equal") } @@ -137,7 +137,7 @@ class YoutubeSourceParserKitTests: XCTestCase { if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") - if let info = videoInfo as [String:AnyObject]? { + if let info = videoInfo as [String:Any]? { if let type = info["type"] as? String { XCTAssertEqual(type, "video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"", "type not equal") } @@ -150,7 +150,7 @@ class YoutubeSourceParserKitTests: XCTestCase { if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") - if let info = videoInfo as [String:AnyObject]? { + if let info = videoInfo as [String:Any]? { if let fallback_host = info["fallback_host"] as? String { XCTAssertEqual(fallback_host, "tc.v7.cache1.googlevideo.com", "fallback_host not equal") } @@ -163,7 +163,7 @@ class YoutubeSourceParserKitTests: XCTestCase { if let liveVideoURL = URL(string: "https://www.youtube.com/watch?v=kMcL3Zr9iyQ"){ Youtube.h264videosWithYoutubeURL(liveVideoURL, completion: { (videoInfo, error) -> Void in XCTAssertNotNil(videoInfo, "video dictionary is nil") - if let info = videoInfo as [String:AnyObject]? { + if let info = videoInfo as [String:Any]? { XCTAssertNotNil(info["url"], "live stream url is nil") } }) @@ -183,7 +183,7 @@ class YoutubeSourceParserKitTests: XCTestCase { if let videoURL = URL(string: "http://www.youtube.com/watch?v=1hZ98an9wjo") { Youtube.h264videosWithYoutubeURL(videoURL, completion: { (videoInfo, error) -> Void in expectation.fulfill() - if let info = videoInfo as [String:AnyObject]? { + if let info = videoInfo as [String:Any]? { if let quality = info["itag"] as? String { XCTAssertEqual(quality, "22", "itag not equal") } From 03609f4707f97faa2ac57f3d17d37720f4633911 Mon Sep 17 00:00:00 2001 From: Leo Thomas Date: Thu, 8 Sep 2016 02:19:02 +0200 Subject: [PATCH 3/4] code improvements moved info http into seperate methode & other minor code improvements --- YoutubeSourceParserKit/Youtube.swift | 286 ++++++++++++++------------- 1 file changed, 150 insertions(+), 136 deletions(-) diff --git a/YoutubeSourceParserKit/Youtube.swift b/YoutubeSourceParserKit/Youtube.swift index af716d9..d681535 100644 --- a/YoutubeSourceParserKit/Youtube.swift +++ b/YoutubeSourceParserKit/Youtube.swift @@ -9,162 +9,176 @@ import Foundation public extension URL { - /** - Parses a query string of an NSURL - - @return key value dictionary with each parameter as an array - */ - func dictionaryForQueryString() -> [String: Any]? { - if let query = self.query { - return query.dictionaryFromQueryStringComponents() - } - - // Note: find youtube ID in m.youtube.com "https://m.youtube.com/#/watch?v=1hZ98an9wjo" - let result = absoluteString.components(separatedBy: "?") - if result.count > 1 { - return result.last?.dictionaryFromQueryStringComponents() + /** + Parses a query string of an NSURL + + @return key value dictionary with each parameter as an array + */ + func dictionaryForQueryString() -> [String: Any]? { + if let query = self.query { + return query.dictionaryFromQueryStringComponents() + } + + // Note: find youtube ID in m.youtube.com "https://m.youtube.com/#/watch?v=1hZ98an9wjo" + let result = absoluteString.components(separatedBy: "?") + if result.count > 1 { + return result.last?.dictionaryFromQueryStringComponents() + } + return nil } - return nil - } } -public extension NSString { - /** - Convenient method for decoding a html encoded string - */ - func stringByDecodingURLFormat() -> String { - let result = self.replacingOccurrences(of: "+", with:" ") - return result.removingPercentEncoding! - } - - /** - Parses a query string - - @return key value dictionary with each parameter as an array - */ - func dictionaryFromQueryStringComponents() -> [String: Any] { - var parameters = [String: Any]() - for keyValue in components(separatedBy: "&") { - let keyValueArray = keyValue.components(separatedBy: "=") - if keyValueArray.count < 2 { - continue - } - let key = keyValueArray[0].stringByDecodingURLFormat() - let value = keyValueArray[1].stringByDecodingURLFormat() - parameters[key] = value as Any? +public extension String { + /** + Convenient method for decoding a html encoded string + */ + func stringByDecodingURLFormat() -> String { + let result = self.replacingOccurrences(of: "+", with:" ") + return result.removingPercentEncoding! + } + + /** + Parses a query string + + @return key value dictionary with each parameter as an array + */ + func dictionaryFromQueryStringComponents() -> [String: Any] { + var parameters = [String: Any]() + for keyValue in components(separatedBy: "&") { + let keyValueArray = keyValue.components(separatedBy: "=") + if keyValueArray.count < 2 { + continue + } + let key = keyValueArray[0].stringByDecodingURLFormat() + let value = keyValueArray[1].stringByDecodingURLFormat() + parameters[key] = value as Any? + } + return parameters } - return parameters - } } open class Youtube: NSObject { - static let infoURL = "http://www.youtube.com/get_video_info?video_id=" - static var userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4" - /** - Method for retrieving the youtube ID from a youtube URL - - @param youtubeURL the the complete youtube video url, either youtu.be or youtube.com - @return string with desired youtube id - */ - open static func youtubeIDFromYoutubeURL(_ youtubeURL: URL) -> String? { - if let youtubeHost = youtubeURL.host { + static let infoURL = "http://www.youtube.com/get_video_info?video_id=" + static var userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4" + /** + Method for retrieving the youtube ID from a youtube URL + + @param youtubeURL the the complete youtube video url, either youtu.be or youtube.com + @return string with desired youtube id + */ + open static func youtubeIDFromYoutubeURL(_ youtubeURL: URL) -> String? { + guard let youtubeHost = youtubeURL.host else { + return nil + } + let youtubePathComponents = youtubeURL.pathComponents let youtubeAbsoluteString = youtubeURL.absoluteString if youtubeHost == "youtu.be" as String? { - return youtubePathComponents[1] + return youtubePathComponents[1] } else if youtubeAbsoluteString.range(of: "www.youtube.com/embed") != nil { - return youtubePathComponents[2] + return youtubePathComponents[2] } else if youtubeHost == "youtube.googleapis.com" || - youtubeURL.pathComponents.first == "www.youtube.com" as String? { + youtubeURL.pathComponents.first == "www.youtube.com" as String? { return youtubePathComponents[2] } else if let - queryString = youtubeURL.dictionaryForQueryString(), - let searchParam = queryString["v"] as? String { + queryString = youtubeURL.dictionaryForQueryString(), + let searchParam = queryString["v"] as? String { return searchParam } + + return nil + } - return nil - } - /** - Method for retreiving a iOS supported video link - - @param youtubeURL the the complete youtube video url - @return dictionary with the available formats for the selected video - - */ - open static func h264videosWithYoutubeID(_ youtubeID: String) -> [String: Any]? { - let urlString = String(format: "%@%@", infoURL, youtubeID) as String - let url = URL(string: urlString)! - var request = URLRequest(url:url) - request.timeoutInterval = 5.0 - request.setValue(userAgent, forHTTPHeaderField: "User-Agent") - request.httpMethod = "GET" - var responseString = NSString() - let session = URLSession(configuration: URLSessionConfiguration.default) - let group = DispatchGroup() - group.enter() - session.dataTask(with: request, completionHandler: { (data, response, _) -> Void in - if let data = data as Data? { - responseString = String(data: data, encoding: .utf8)! as NSString - } - group.leave() - }).resume() - group.wait(timeout: DispatchTime.distantFuture) - let parts = responseString.dictionaryFromQueryStringComponents() - if parts.count > 0 { - var videoTitle: String = "" - var streamImage: String = "" - if let title = parts["title"] as? String { - videoTitle = title - } - if let image = parts["iurl"] as? String { - streamImage = image - } - if let fmtStreamMap = parts["url_encoded_fmt_stream_map"] as? String { + /** + Method for retreiving a iOS supported video link + + @param youtubeURL the the complete youtube video url + @return dictionary with the available formats for the selected video + + */ + open static func h264videosWithYoutubeID(_ youtubeID: String) -> [String: Any]? { + guard let parts = loadVideoInfos(youtubeID: youtubeID), parts.count > 0 else { + return nil + } + + let videoTitle = parts["title"] as? String ?? "" + let streamImage = parts["iurl"] as? String ?? "" + + guard let fmtStreamMap = parts["url_encoded_fmt_stream_map"] as? String else { + return nil + } // Live Stream - if let _: Any = parts["live_playback"]{ - if let hlsvp = parts["hlsvp"] as? String { - return [ - "url": "\(hlsvp)" as Any, - "title": "\(videoTitle)" as Any, - "image": "\(streamImage)" as Any, - "isStream": true as Any - ] - } + if parts["live_playback"] != nil { + if let hlsvp = parts["hlsvp"] as? String { + return [ + "url": "\(hlsvp)" as Any, + "title": "\(videoTitle)" as Any, + "image": "\(streamImage)" as Any, + "isStream": true as Any + ] + } } else { - let fmtStreamMapArray = fmtStreamMap.components(separatedBy: ",") - for videoEncodedString in fmtStreamMapArray { - var videoComponents = videoEncodedString.dictionaryFromQueryStringComponents() - videoComponents["title"] = videoTitle as Any? - videoComponents["isStream"] = false as Any? - return videoComponents as [String: Any] - } + let fmtStreamMapArray = fmtStreamMap.components(separatedBy: ",") + for videoEncodedString in fmtStreamMapArray { + var videoComponents = videoEncodedString.dictionaryFromQueryStringComponents() + videoComponents["title"] = videoTitle as Any? + videoComponents["isStream"] = false as Any? + return videoComponents as [String: Any] + } } - } + + return nil } - return nil - } - - /** - Block based method for retreiving a iOS supported video link - - @param youtubeURL the the complete youtube video url - @param completeBlock the block which is called on completion - - */ - open static func h264videosWithYoutubeURL(_ youtubeURL: URL,completion: (( - _ videoInfo: [String: Any]?, _ error: NSError?) -> Void)?) { - let priority = DispatchQueue.GlobalQueuePriority.background - DispatchQueue.global(priority: priority).async { - if let youtubeID = self.youtubeIDFromYoutubeURL(youtubeURL), let videoInformation = self.h264videosWithYoutubeID(youtubeID) { - DispatchQueue.main.async { - completion?(videoInformation, nil) - } - }else{ - DispatchQueue.main.async { - completion?(nil, NSError(domain: "com.player.youtube.backgroundqueue", code: 1001, userInfo: ["error": "Invalid YouTube URL"])) - } + + private static func loadVideoInfos(youtubeID: String) -> [String: Any]? { + + var result: [String: Any]? + let urlString = String(format: "%@%@", infoURL, youtubeID) + + guard let url = URL(string: urlString) else { + return result } - } + + var request = URLRequest(url: url) + request.timeoutInterval = 5.0 + request.setValue(userAgent, forHTTPHeaderField: "User-Agent") + request.httpMethod = "GET" + + let session = URLSession(configuration: URLSessionConfiguration.default) + let group = DispatchGroup() + group.enter() + session.dataTask(with: request, completionHandler: { (data, response, _) -> Void in + if let data = data as Data?, + let resultString = String(data: data, encoding: String.Encoding.utf8) { + + result = resultString.dictionaryFromQueryStringComponents() + + } + group.leave() + }).resume() + _ = group.wait(timeout: DispatchTime.distantFuture) + return result } - } + + /** + Block based method for retreiving a iOS supported video link + + @param youtubeURL the the complete youtube video url + @param completeBlock the block which is called on completion + + */ + open static func h264videosWithYoutubeURL(_ youtubeURL: URL,completion: (( + _ videoInfo: [String: Any]?, _ error: NSError?) -> Void)?) { + DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async{ + if let youtubeID = self.youtubeIDFromYoutubeURL(youtubeURL), let videoInformation = self.h264videosWithYoutubeID(youtubeID) { + DispatchQueue.main.async { + completion?(videoInformation, nil) + } + } else { + DispatchQueue.main.async { + completion?(nil, NSError(domain: "com.player.youtube.backgroundqueue", code: 1001, userInfo: ["error": "Invalid YouTube URL"])) + } + } + } + } +} From 98f3bcf9886c20eefd82249a6cfbb3068ad86b80 Mon Sep 17 00:00:00 2001 From: Leo Thomas Date: Thu, 8 Sep 2016 02:54:08 +0200 Subject: [PATCH 4/4] updated travis osx_image to xcode8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 88e60c8..2a50755 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: objective-c -osx_image: xcode61 +osx_image: xcode8 xcodebuild: xctool -project youtube-parser.xcodeproj -scheme youtube-parser build xcode_scheme: youtube-parserTests