Skip to content

Commit

Permalink
Refactor webpage parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
0xced committed Jul 14, 2014
1 parent 3108680 commit a208433
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 35 deletions.
10 changes: 10 additions & 0 deletions XCDYouTubeKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
C20F57B11881806700EDBFB0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C274158917F48AD10026834B /* UIKit.framework */; };
C224EB661919241B0038186E /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C224EB641919241B0038186E /* JavaScriptCore.framework */; };
C224EB75191AF0370038186E /* XCDYouTubeKitTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = C224EB74191AF0370038186E /* XCDYouTubeKitTestCase.m */; };
C2386B1E1974036300646123 /* XCDYouTubeVideoWebpage.h in Headers */ = {isa = PBXBuildFile; fileRef = C2386B1C1974036300646123 /* XCDYouTubeVideoWebpage.h */; };
C2386B1F1974036300646123 /* XCDYouTubeVideoWebpage.m in Sources */ = {isa = PBXBuildFile; fileRef = C2386B1D1974036300646123 /* XCDYouTubeVideoWebpage.m */; };
C2386B201974036300646123 /* XCDYouTubeVideoWebpage.m in Sources */ = {isa = PBXBuildFile; fileRef = C2386B1D1974036300646123 /* XCDYouTubeVideoWebpage.m */; };
C24C162E18E9A139005E92E9 /* XCDYouTubeVideoOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C24C162D18E9A139005E92E9 /* XCDYouTubeVideoOperation.m */; };
C25308C518D7392500132734 /* XCDYouTubeClient.m in Sources */ = {isa = PBXBuildFile; fileRef = C25308C418D7392500132734 /* XCDYouTubeClient.m */; };
C25308C818D739EB00132734 /* XCDYouTubeVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = C25308C718D739EB00132734 /* XCDYouTubeVideo.m */; };
Expand Down Expand Up @@ -123,6 +126,8 @@
C224EB641919241B0038186E /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
C224EB74191AF0370038186E /* XCDYouTubeKitTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCDYouTubeKitTestCase.m; sourceTree = "<group>"; };
C224EB76191AF0570038186E /* XCDYouTubeKitTestCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCDYouTubeKitTestCase.h; sourceTree = "<group>"; };
C2386B1C1974036300646123 /* XCDYouTubeVideoWebpage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCDYouTubeVideoWebpage.h; sourceTree = "<group>"; };
C2386B1D1974036300646123 /* XCDYouTubeVideoWebpage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCDYouTubeVideoWebpage.m; sourceTree = "<group>"; };
C24C162918E4DA6E005E92E9 /* XCDYouTubeError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCDYouTubeError.h; sourceTree = "<group>"; };
C24C162C18E9A139005E92E9 /* XCDYouTubeVideoOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCDYouTubeVideoOperation.h; sourceTree = "<group>"; };
C24C162D18E9A139005E92E9 /* XCDYouTubeVideoOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCDYouTubeVideoOperation.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -298,6 +303,8 @@
C24C162D18E9A139005E92E9 /* XCDYouTubeVideoOperation.m */,
C2A3F2D517F4827800AC1C3B /* XCDYouTubeVideoPlayerViewController.h */,
C2A3F2D717F4827800AC1C3B /* XCDYouTubeVideoPlayerViewController.m */,
C2386B1C1974036300646123 /* XCDYouTubeVideoWebpage.h */,
C2386B1D1974036300646123 /* XCDYouTubeVideoWebpage.m */,
);
path = XCDYouTubeKit;
sourceTree = "<group>";
Expand Down Expand Up @@ -339,6 +346,7 @@
buildActionMask = 2147483647;
files = (
C26231AB191D751B00D23900 /* XCDYouTubeClient.h in Headers */,
C2386B1E1974036300646123 /* XCDYouTubeVideoWebpage.h in Headers */,
C26231AC191D752200D23900 /* XCDYouTubeError.h in Headers */,
C26231AD191D752500D23900 /* XCDYouTubeKit.h in Headers */,
C26231AE191D752800D23900 /* XCDYouTubeOperation.h in Headers */,
Expand Down Expand Up @@ -526,6 +534,7 @@
buildActionMask = 2147483647;
files = (
C26231A7191D750200D23900 /* XCDYouTubeClient.m in Sources */,
C2386B201974036300646123 /* XCDYouTubeVideoWebpage.m in Sources */,
C26231A8191D750600D23900 /* XCDYouTubePlayerScript.m in Sources */,
C26231A9191D750900D23900 /* XCDYouTubeVideo.m in Sources */,
C26231AA191D750D00D23900 /* XCDYouTubeVideoOperation.m in Sources */,
Expand All @@ -541,6 +550,7 @@
C25308C818D739EB00132734 /* XCDYouTubeVideo.m in Sources */,
C2FB52DE1918F88C00B2CBE6 /* XCDYouTubePlayerScript.m in Sources */,
C2A3F2D817F4827800AC1C3B /* XCDYouTubeVideoPlayerViewController.m in Sources */,
C2386B1F1974036300646123 /* XCDYouTubeVideoWebpage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
41 changes: 6 additions & 35 deletions XCDYouTubeKit/XCDYouTubeVideoOperation.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#import "XCDYouTubeVideo+Private.h"
#import "XCDYouTubeError.h"
#import "XCDYouTubeVideoWebpage.h"
#import "XCDYouTubePlayerScript.h"

@interface XCDYouTubeVideoOperation () <NSURLConnectionDataDelegate, NSURLConnectionDelegate>
Expand All @@ -20,7 +21,7 @@ @interface XCDYouTubeVideoOperation () <NSURLConnectionDataDelegate, NSURLConnec
@property (atomic, assign) BOOL isExecuting;
@property (atomic, assign) BOOL isFinished;

@property (atomic, strong) NSDictionary *info;
@property (atomic, strong) XCDYouTubeVideoWebpage *webpage;
@property (atomic, strong) XCDYouTubeVideo *noStreamVideo;
@property (atomic, strong) NSError *lastError;
@property (atomic, strong) NSError *youTubeError; // Error actually coming from the YouTube API, i.e. explicit and localized error
Expand Down Expand Up @@ -112,40 +113,10 @@ - (void) handleVideoInfoResponseWithInfo:(NSDictionary *)info playerScript:(XCDY

- (void) handleWebPageResponse
{
__block NSURL *javaScriptPlayerURL = nil;
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)self.response.textEncodingName ?: CFSTR(""));
NSString *html = CFBridgingRelease(CFStringCreateWithBytes(kCFAllocatorDefault, [self.connectionData bytes], (CFIndex)[self.connectionData length], encoding != kCFStringEncodingInvalidId ? encoding : kCFStringEncodingISOLatin1, false));
NSRegularExpression *playerConfigRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"ytplayer.config\\s*=\\s*(\\{.*?\\});" options:NSRegularExpressionCaseInsensitive error:NULL];
[playerConfigRegularExpression enumerateMatchesInString:html options:(NSMatchingOptions)0 range:NSMakeRange(0, html.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *regexpStop) {
NSString *configString = [html substringWithRange:[result rangeAtIndex:1]];
NSDictionary *playerConfiguration = [NSJSONSerialization JSONObjectWithData:[configString dataUsingEncoding:NSUTF8StringEncoding] options:(NSJSONReadingOptions)0 error:NULL];
if ([playerConfiguration isKindOfClass:[NSDictionary class]])
{
NSDictionary *args = playerConfiguration[@"args"];
if ([args isKindOfClass:[NSDictionary class]])
{
NSMutableDictionary *info = [NSMutableDictionary new];
[args enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *argsStop) {
if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])
info[key] = [value description];
}];
self.info = [info copy];
NSString *jsAssets = [playerConfiguration valueForKeyPath:@"assets.js"];
if ([jsAssets isKindOfClass:[NSString class]])
{
NSString *javaScriptPlayerURLString = jsAssets;
if ([jsAssets hasPrefix:@"//"])
javaScriptPlayerURLString = [@"https:" stringByAppendingString:jsAssets];

javaScriptPlayerURL = [NSURL URLWithString:javaScriptPlayerURLString];
*regexpStop = YES;
}
}
}
}];
self.webpage = [[XCDYouTubeVideoWebpage alloc] initWithData:self.connectionData response:self.response];

if (javaScriptPlayerURL)
[self startRequestWithURL:javaScriptPlayerURL];
if (self.webpage.javaScriptPlayerURL)
[self startRequestWithURL:self.webpage.javaScriptPlayerURL];
else
[self startNextVideoInfoRequest];
}
Expand All @@ -156,7 +127,7 @@ - (void) handleJavaScriptPlayerResponse
XCDYouTubePlayerScript *playerScript = [[XCDYouTubePlayerScript alloc] initWithString:script];

if (playerScript)
[self handleVideoInfoResponseWithInfo:self.info playerScript:playerScript];
[self handleVideoInfoResponseWithInfo:self.webpage.videoInfo playerScript:playerScript];
else
[self startNextVideoInfoRequest];
}
Expand Down
15 changes: 15 additions & 0 deletions XCDYouTubeKit/XCDYouTubeVideoWebpage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Copyright (c) 2013-2014 Cédric Luthi. All rights reserved.
//

#import <Foundation/Foundation.h>

__attribute__((visibility("hidden")))
@interface XCDYouTubeVideoWebpage : NSObject

- (instancetype) initWithData:(NSData *)data response:(NSURLResponse *)response;

@property (nonatomic, readonly) NSDictionary *videoInfo;
@property (nonatomic, readonly) NSURL *javaScriptPlayerURL;

@end
89 changes: 89 additions & 0 deletions XCDYouTubeKit/XCDYouTubeVideoWebpage.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// Copyright (c) 2013-2014 Cédric Luthi. All rights reserved.
//

#import "XCDYouTubeVideoWebpage.h"

@interface XCDYouTubeVideoWebpage ()
@property (nonatomic, strong) NSData *data;
@property (nonatomic, strong) NSURLResponse *response;
@end

@implementation XCDYouTubeVideoWebpage
{
NSDictionary *_playerConfiguration;
NSDictionary *_videoInfo;
NSURL *_javaScriptPlayerURL;
}

- (instancetype) initWithData:(NSData *)data response:(NSURLResponse *)response
{
if (!(self = [super init]))
return nil;

_data = data;
_response = response;

return self;
}

#pragma clang diagnostic ignored "-Wdirect-ivar-access"

- (NSDictionary *) playerConfiguration
{
if (!_playerConfiguration)
{
__block NSDictionary *playerConfigurationDictionary;
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)self.response.textEncodingName ?: CFSTR(""));
NSString *html = CFBridgingRelease(CFStringCreateWithBytes(kCFAllocatorDefault, [self.data bytes], (CFIndex)[self.data length], encoding != kCFStringEncodingInvalidId ? encoding : kCFStringEncodingISOLatin1, false));
NSRegularExpression *playerConfigRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"ytplayer.config\\s*=\\s*(\\{.*?\\});" options:NSRegularExpressionCaseInsensitive error:NULL];
[playerConfigRegularExpression enumerateMatchesInString:html options:(NSMatchingOptions)0 range:NSMakeRange(0, html.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSString *configString = [html substringWithRange:[result rangeAtIndex:1]];
NSDictionary *playerConfiguration = [NSJSONSerialization JSONObjectWithData:[configString dataUsingEncoding:NSUTF8StringEncoding] options:(NSJSONReadingOptions)0 error:NULL];
if ([playerConfiguration isKindOfClass:[NSDictionary class]])
{
playerConfigurationDictionary = playerConfiguration;
*stop = YES;
}
}];
_playerConfiguration = playerConfigurationDictionary;
}
return _playerConfiguration;
}

- (NSDictionary *) videoInfo
{
if (!_videoInfo)
{
NSDictionary *args = self.playerConfiguration[@"args"];
if ([args isKindOfClass:[NSDictionary class]])
{
NSMutableDictionary *info = [NSMutableDictionary new];
[args enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])
info[key] = [value description];
}];
_videoInfo = [info copy];
}
}
return _videoInfo;
}

- (NSURL *) javaScriptPlayerURL
{
if (!_javaScriptPlayerURL)
{
NSString *jsAssets = [self.playerConfiguration valueForKeyPath:@"assets.js"];
if ([jsAssets isKindOfClass:[NSString class]])
{
NSString *javaScriptPlayerURLString = jsAssets;
if ([jsAssets hasPrefix:@"//"])
javaScriptPlayerURLString = [@"https:" stringByAppendingString:jsAssets];

_javaScriptPlayerURL = [NSURL URLWithString:javaScriptPlayerURLString];
}
}
return _javaScriptPlayerURL;
}

@end

0 comments on commit a208433

Please sign in to comment.