Skip to content

Commit

Permalink
Merge branch 'hotfix/mainThread' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
SoneeJohn committed Feb 18, 2020
2 parents f58a413 + 860485c commit 86bdba1
Show file tree
Hide file tree
Showing 11 changed files with 1,982 additions and 337 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

50 changes: 47 additions & 3 deletions XCDYouTubeKit Tests/XCDYouTubeClientTestCase.m
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ - (void) testVideo1ReturnsSomePlayableStreams

XCTAssertNil(queryError);
XCTAssertNotNil(streamURLs);
XCTAssertTrue([NSThread isMainThread]);

for (NSNumber *itag in playableStreamKeys)
{
Expand All @@ -253,6 +254,7 @@ - (void) testVideo1ReturnsSomePlayableStreams
}

// Disable internet connection before running to allow some queries to fail
// Also, this test requires using Charles Proxy tools (or similar app) to block some of the streamURLs
- (void) testVideo1ReturnsSomePlayableStreamsEvenIfSomeFailDueToConnectionError_offline
{
__weak XCTestExpectation *expectation = [self expectationWithDescription:@""];
Expand All @@ -265,6 +267,7 @@ - (void) testVideo1ReturnsSomePlayableStreamsEvenIfSomeFailDueToConnectionError_

XCTAssertNil(queryError);
XCTAssertNotNil(streamURLs);
XCTAssertTrue([NSThread isMainThread]);

for (id key in streamURLs.allKeys)
{
Expand All @@ -274,7 +277,7 @@ - (void) testVideo1ReturnsSomePlayableStreamsEvenIfSomeFailDueToConnectionError_
XCTAssertTrue(streamErrors.count != 0);
for (NSError *streamError in streamErrors.allValues)
{
XCTAssertEqualObjects(streamError.localizedDescription, @"The Internet connection appears to be offline.");
XCTAssertNotNil(streamError.localizedDescription);
}

XCTAssertNotEqual(video.streamURLs.count, streamURLs.count, @"`streamURLs` count should not be equal since this video contains some streams are unplayable");
Expand All @@ -300,10 +303,11 @@ - (void) testVideo1ReturnsNoPlayableStreamsBecauseConnectionError_offline
XCTAssertNotNil(queryError);
XCTAssertNil(streamURLs);
XCTAssertTrue(streamErrors.count != 0);
XCTAssertTrue([NSThread isMainThread]);

for (NSError *streamError in streamErrors.allValues)
{
XCTAssertEqualObjects(streamError.localizedDescription, @"The Internet connection appears to be offline.");
XCTAssertNotNil(streamError.localizedDescription);
}

XCTAssertNotEqual(video.streamURLs.count, streamURLs.count, @"`streamURLs` count should not be equal since this video contains some streams are unplayable");
Expand All @@ -328,6 +332,7 @@ - (void) testVideo2ReturnsAllPlayableStreams
XCTAssertNil(queryError);
XCTAssertNil(streamErrors);
XCTAssertNotNil(streamURLs);
XCTAssertTrue([NSThread isMainThread]);

for (id key in streamURLs.allKeys)
{
Expand All @@ -340,7 +345,46 @@ - (void) testVideo2ReturnsAllPlayableStreams
}];
}];

[self waitForExpectationsWithTimeout:90 handler:nil];
[self waitForExpectationsWithTimeout:5 handler:nil];
}

- (void) testVideo3ReturnsSomePlayableStreams
{
/**
* This video `550S-6XVRsw` contains some streams (e.g. itag=22) that don't play (the file appeas to be incomplete on YouTube's servers).
* This test ensures that we catch those kinds of errors and they aren't included in the `streamURLs`
* See https://github.com/0xced/XCDYouTubeKit/issues/456 for more information.
*/
__weak XCTestExpectation *expectation = [self expectationWithDescription:@""];
NSNumber *nonPlayableStreamKey = @(XCDYouTubeVideoQualityHD720);

[[XCDYouTubeClient defaultClient] getVideoWithIdentifier:@"550S-6XVRsw" completionHandler:^(XCDYouTubeVideo *video, NSError *error)
{
XCTAssertNotNil(video);
XCTAssertNil(error);

[[XCDYouTubeClient defaultClient]queryVideo:video cookies:nil completionHandler:^(NSDictionary * _Nonnull streamURLs, NSError * _Nullable queryError, NSDictionary<id, NSError *> *streamErrors) {

XCTAssertNil(queryError);
XCTAssertNotNil(streamErrors);
XCTAssertNotNil(streamURLs);
XCTAssertTrue([NSThread isMainThread]);

for (id key in streamURLs.allKeys)
{
XCTAssertNotNil(streamURLs[key]);
}

XCTAssertNotEqual(video.streamURLs.count, streamURLs.count, @"`streamURLs` count should not be equal since this video contains some streams are unplayable");
XCTAssertNil(streamURLs[nonPlayableStreamKey], @"itag 22 should not be available in this stream.");
//I noticed when the file stored on the server is not complete we get this error
XCTAssertTrue([streamErrors.allValues.firstObject.domain isEqual:NSURLErrorDomain]);
XCTAssertEqual(streamErrors.allValues.firstObject.code, NSURLErrorNetworkConnectionLost);
[expectation fulfill];
}];
}];

[self waitForExpectationsWithTimeout:5 handler:nil];
}

- (void) testExpiredLiveVideo
Expand Down
10 changes: 10 additions & 0 deletions XCDYouTubeKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
objects = {

/* Begin PBXBuildFile section */
017234C223FC223D00196707 /* XCDURLGetOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 017234C023FC223D00196707 /* XCDURLGetOperation.h */; };
017234C323FC223D00196707 /* XCDURLGetOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 017234C123FC223D00196707 /* XCDURLGetOperation.m */; };
017234C423FC223D00196707 /* XCDURLGetOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 017234C123FC223D00196707 /* XCDURLGetOperation.m */; };
0178E76D23F49BBC001C382E /* XCDYouTubeVideoQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0178E76C23F49BBC001C382E /* XCDYouTubeVideoQueryOperation.m */; };
0178E76E23F4A023001C382E /* XCDYouTubeVideoQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 0178E76B23F49BBC001C382E /* XCDYouTubeVideoQueryOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
0178E76F23F4A02A001C382E /* XCDYouTubeVideoQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0178E76C23F49BBC001C382E /* XCDYouTubeVideoQueryOperation.m */; };
Expand Down Expand Up @@ -107,6 +110,8 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
017234C023FC223D00196707 /* XCDURLGetOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCDURLGetOperation.h; sourceTree = "<group>"; };
017234C123FC223D00196707 /* XCDURLGetOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCDURLGetOperation.m; sourceTree = "<group>"; };
0178E76B23F49BBC001C382E /* XCDYouTubeVideoQueryOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCDYouTubeVideoQueryOperation.h; sourceTree = "<group>"; };
0178E76C23F49BBC001C382E /* XCDYouTubeVideoQueryOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCDYouTubeVideoQueryOperation.m; sourceTree = "<group>"; };
0178E77123F4A0D8001C382E /* XCDURLHeadOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCDURLHeadOperation.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -246,6 +251,8 @@
0178E76C23F49BBC001C382E /* XCDYouTubeVideoQueryOperation.m */,
0178E77123F4A0D8001C382E /* XCDURLHeadOperation.h */,
0178E77223F4A0D8001C382E /* XCDURLHeadOperation.m */,
017234C023FC223D00196707 /* XCDURLGetOperation.h */,
017234C123FC223D00196707 /* XCDURLGetOperation.m */,
C2A3F2D517F4827800AC1C3B /* XCDYouTubeVideoPlayerViewController.h */,
C2A3F2D717F4827800AC1C3B /* XCDYouTubeVideoPlayerViewController.m */,
C2386B1C1974036300646123 /* XCDYouTubeVideoWebpage.h */,
Expand Down Expand Up @@ -305,6 +312,7 @@
C2F0E5941944F84A00D8EBA8 /* XCDYouTubeVideoOperation.h in Headers */,
C215BEB91BE2E5B500F9783B /* XCDYouTubeVideoWebpage.h in Headers */,
C2F0E5951944F85500D8EBA8 /* XCDYouTubeVideoPlayerViewController.h in Headers */,
017234C223FC223D00196707 /* XCDURLGetOperation.h in Headers */,
C2F0E5921944F84400D8EBA8 /* XCDYouTubeVideo+Private.h in Headers */,
C2F0E5911944F84200D8EBA8 /* XCDYouTubePlayerScript.h in Headers */,
);
Expand Down Expand Up @@ -517,6 +525,7 @@
C25308C518D7392500132734 /* XCDYouTubeClient.m in Sources */,
0178E76D23F49BBC001C382E /* XCDYouTubeVideoQueryOperation.m in Sources */,
C24C162E18E9A139005E92E9 /* XCDYouTubeVideoOperation.m in Sources */,
017234C323FC223D00196707 /* XCDURLGetOperation.m in Sources */,
0178E77423F4A0D8001C382E /* XCDURLHeadOperation.m in Sources */,
01D2370A1FA03AC100A13E5F /* XCDYouTubeDashManifestXML.m in Sources */,
C25308C818D739EB00132734 /* XCDYouTubeVideo.m in Sources */,
Expand All @@ -534,6 +543,7 @@
C2F0E59C1944F8CF00D8EBA8 /* XCDYouTubeVideoOperation.m in Sources */,
0178E76F23F4A02A001C382E /* XCDYouTubeVideoQueryOperation.m in Sources */,
C2F0E5991944F8C200D8EBA8 /* XCDYouTubeClient.m in Sources */,
017234C423FC223D00196707 /* XCDURLGetOperation.m in Sources */,
0178E77523F4A0D8001C382E /* XCDURLHeadOperation.m in Sources */,
01D2370C1FA03AF100A13E5F /* XCDYouTubeDashManifestXML.m in Sources */,
C2F0E59A1944F8C700D8EBA8 /* XCDYouTubePlayerScript.m in Sources */,
Expand Down
28 changes: 28 additions & 0 deletions XCDYouTubeKit/XCDURLGetOperation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// XCDURLGetOperation.h
// XCDYouTubeKit
//
// Created by Soneé John on 2/18/20.
// Copyright © 2020 Cédric Luthi. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

__attribute__((visibility("hidden")))
@interface XCDURLGetOperation : NSOperation

- (instancetype) initWithURL:(NSURL *)url info:(nullable NSDictionary *)info cookes:(nullable NSArray <NSHTTPCookie *> *)cookies;

@property (atomic, strong, readonly) NSURL *url;
@property (atomic, copy, readonly, nullable) NSDictionary *info;
@property (atomic, copy, readonly, nullable) NSArray <NSHTTPCookie *> *cookies;

@property (atomic, readonly, nullable) NSURLResponse *response;

@property (atomic, readonly, nullable) NSError *error;

@end

NS_ASSUME_NONNULL_END
160 changes: 160 additions & 0 deletions XCDYouTubeKit/XCDURLGetOperation.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//
// XCDURLGetOperation.m
// XCDYouTubeKit
//
// Created by Soneé John on 2/18/20.
// Copyright © 2020 Cédric Luthi. All rights reserved.
//

#import "XCDURLGetOperation.h"

@interface XCDURLGetOperation() <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (atomic, assign) BOOL isExecuting;
@property (atomic, assign) BOOL isFinished;

@property (atomic, strong) NSURLSessionDataTask *dataTask;
@property (atomic, strong) NSURLSession *session;
@property (atomic, readonly) dispatch_semaphore_t operationStartSemaphore;

@property (atomic, readwrite, nullable) NSURLResponse *response;

@property (atomic, readwrite, nullable) NSError *error;

@end

@implementation XCDURLGetOperation

- (instancetype) initWithURL:(NSURL *)url info:(NSDictionary *)info cookes:(NSArray<NSHTTPCookie *> *)cookies
{
if (!(self = [super init]))
return nil;

_url = url;
_info = [info copy];
_cookies = [cookies copy];

_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:nil];

for (NSHTTPCookie *cookie in _cookies) {
[_session.configuration.HTTPCookieStorage setCookie:cookie];
}

_operationStartSemaphore = dispatch_semaphore_create(0);

return self;
}

#pragma mark - NSOperation

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key
{
SEL selector = NSSelectorFromString(key);
return selector == @selector(isExecuting) || selector == @selector(isFinished) || [super automaticallyNotifiesObserversForKey:key];
}

- (BOOL) isAsynchronous
{
return YES;
}

- (void) start
{
dispatch_semaphore_signal(self.operationStartSemaphore);

if (self.isCancelled)
return;

self.isExecuting = YES;

[self startRequest];
}

- (void) cancel
{
if (self.isCancelled || self.isFinished)
return;

[super cancel];

[self.dataTask cancel];

dispatch_semaphore_wait(self.operationStartSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(200 * NSEC_PER_MSEC)));

[self finish];
}

#pragma mark -

- (void)finishWithError:(NSError *)error
{
self.error = error;
[self finish];
}

- (void)finish
{
self.isExecuting = NO;
self.isFinished = YES;
}

#pragma mark -

- (void)startRequest
{
//Start request by downloading the first ~1MB of the file
//This helps to catch errors such as the one in this issue here: https://github.com/0xced/XCDYouTubeKit/issues/456
NSUInteger rangeStart = 0;
NSUInteger rangeEnd = 1000000;

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.url];
[request addValue:[NSString stringWithFormat:@"bytes=%@-%@", @(rangeStart), @(rangeEnd)] forHTTPHeaderField:@"Range"];

self.dataTask = [self.session dataTaskWithRequest:request];

[self.dataTask resume];
}

#pragma mark - NSURLSessionDataDelegate

- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{

NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
self.response = response;

if (httpResponse.statusCode != 200 && httpResponse.statusCode != 206)
{
//statusCode is not 200 and isn't 206
//Bad server response
[self finish];
return;
}

if (httpResponse.statusCode != 206)
{
// Does not support partial content so we will simply finish the operation.
// Continuing will cause us to download the entire file
[self finish];
return;
}

completionHandler(NSURLSessionResponseAllow);
}

- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
self.response = dataTask.response;
}

#pragma mark - NSURLSessionTaskDelegate

- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)dataTask didCompleteWithError:(NSError *)error
{
self.response = dataTask.response;
if (self.isCancelled)
return;

[self finishWithError:error];
}

@end
20 changes: 11 additions & 9 deletions XCDYouTubeKit/XCDYouTubeClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,20 @@ - (XCDYouTubeVideoQueryOperation *) queryVideo:(XCDYouTubeVideo *)video cookies:

XCDYouTubeVideoQueryOperation *operation = [[XCDYouTubeVideoQueryOperation alloc]initWithVideo:video cookies:cookies];
operation.completionBlock = ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
if (operation.streamURLs || operation.error)
{
NSAssert(!(operation.streamURLs && operation.error), @"One of `streamURLs` or `error` must be nil.");
completionHandler(operation.streamURLs, operation.error, operation.streamErrors);
} else
{
NSAssert(operation.isCancelled, @"Both `streamURLs` and `error` can not be nil if the operation was not canceled.");
}
operation.completionBlock = nil;
if (operation.streamURLs || operation.error)
{
NSAssert(!(operation.streamURLs && operation.error), @"One of `streamURLs` or `error` must be nil.");
completionHandler(operation.streamURLs, operation.error, operation.streamErrors);
} else
{
NSAssert(operation.isCancelled, @"Both `streamURLs` and `error` can not be nil if the operation was not canceled.");
}
operation.completionBlock = nil;
#pragma clang diagnostic pop
}];
};

[self.queue addOperation:operation];
Expand Down
Loading

0 comments on commit 86bdba1

Please sign in to comment.