diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 8d57f21..73da697 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -29,7 +29,7 @@ Stack trace or log information > The simulator is a valid device as well. If all versions or devices seem to be affected, simply enter 'Any' * Library version: _version_ -* iOS version: _version_ +* iOS / tvOS version: _version_ * Device: _model_ ### Reproducibility diff --git a/Demo/Demo.xcconfig b/Demo/Demo.xcconfig index be27dba..5b2c3bb 100644 --- a/Demo/Demo.xcconfig +++ b/Demo/Demo.xcconfig @@ -1,5 +1,5 @@ // Version information -MARKETING_VERSION = 3.0.0 +MARKETING_VERSION = 3.0.1 // Deployment targets IPHONEOS_DEPLOYMENT_TARGET = 9.0 diff --git a/Demo/Sources/Demos/DemosViewController.m b/Demo/Sources/Demos/DemosViewController.m index 10b6142..b4d9026 100644 --- a/Demo/Sources/Demos/DemosViewController.m +++ b/Demo/Sources/Demos/DemosViewController.m @@ -147,7 +147,9 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath AVPlayerViewController *playerViewController = [[AVPlayerViewController alloc] init]; playerViewController.player = player; - playerViewController.allowsPictureInPicturePlayback = NO; + if (@available(iOS 9, tvOS 14, *)) { + playerViewController.allowsPictureInPicturePlayback = NO; + } [self presentViewController:playerViewController animated:YES completion:^{ [player play]; diff --git a/Package.swift b/Package.swift index e4ea551..13c62e6 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ import PackageDescription struct ProjectSettings { - static let marketingVersion: String = "3.0.0" + static let marketingVersion: String = "3.0.1" } let package = Package( diff --git a/Sources/SRGContentProtection/SRGAkamaiAssetResourceLoaderDelegate.m b/Sources/SRGContentProtection/SRGAkamaiAssetResourceLoaderDelegate.m index 76a71e3..a42c15b 100644 --- a/Sources/SRGContentProtection/SRGAkamaiAssetResourceLoaderDelegate.m +++ b/Sources/SRGContentProtection/SRGAkamaiAssetResourceLoaderDelegate.m @@ -99,24 +99,38 @@ - (BOOL)shouldProcessResourceLoadingRequest:(AVAssetResourceLoadingRequest *)loa [diagnosticInformation setString:error.localizedDescription forKey:@"errorMessage"]; [diagnosticInformation stopTimeMeasurementForKey:@"duration"]; - NSMutableURLRequest *playlistRequest = loadingRequest.request.mutableCopy; - playlistRequest.URL = URL; - SRGRequest *request = [[SRGRequest dataRequestWithURLRequest:playlistRequest session:self.session completionBlock:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { - loadingRequest.response = response; - if (error) { - NSMutableDictionary *userInfo = @{ NSLocalizedDescriptionKey : SRGContentProtectionLocalizedString(@"This content is protected and cannot be played without proper rights.", @"User-facing message displayed proper authorization to play a stream has not been obtained") }.mutableCopy; + // Redirecting to the tokenized URL preserves cookies on iOS 11+ and restores original scheme url. + if (@available(iOS 11, *)) { + NSMutableURLRequest *redirect = loadingRequest.request.mutableCopy; + redirect.URL = URL; + loadingRequest.redirect = redirect.copy; + + loadingRequest.response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:303 HTTPVersion:nil headerFields:nil]; + [loadingRequest finishLoading]; + } + // Retrieve the master playlist on iOS 9 and 10 to preserve cookies. + // Known issue if next resource url is relative: it reuses the previous scheme url, host and path, which includes the custom scheme url again. + // See https://github.com/SRGSSR/srgcontentprotection-apple/issues/6 + else { + NSMutableURLRequest *playlistRequest = loadingRequest.request.mutableCopy; + playlistRequest.URL = URL; + SRGRequest *request = [[SRGRequest dataRequestWithURLRequest:playlistRequest session:self.session completionBlock:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + loadingRequest.response = response; if (error) { - userInfo[NSUnderlyingErrorKey] = error; + NSMutableDictionary *userInfo = @{ NSLocalizedDescriptionKey : SRGContentProtectionLocalizedString(@"This content is protected and cannot be played without proper rights.", @"User-facing message displayed proper authorization to play a stream has not been obtained") }.mutableCopy; + if (error) { + userInfo[NSUnderlyingErrorKey] = error; + } + NSError *friendlyError = [NSError errorWithDomain:SRGContentProtectionErrorDomain code:SRGContentProtectionErrorUnauthorized userInfo:userInfo.copy]; + [loadingRequest finishLoadingWithError:friendlyError]; } - NSError *friendlyError = [NSError errorWithDomain:SRGContentProtectionErrorDomain code:SRGContentProtectionErrorUnauthorized userInfo:userInfo.copy]; - [loadingRequest finishLoadingWithError:friendlyError]; - } - else { - [loadingRequest.dataRequest respondWithData:data]; - [loadingRequest finishLoading]; - } - }] requestWithOptions:SRGRequestOptionBackgroundCompletionEnabled]; - [self.requestQueue addRequest:request resume:YES]; + else { + [loadingRequest.dataRequest respondWithData:data]; + [loadingRequest finishLoading]; + } + }] requestWithOptions:SRGRequestOptionBackgroundCompletionEnabled]; + [self.requestQueue addRequest:request resume:YES]; + } }] requestWithOptions:SRGRequestOptionBackgroundCompletionEnabled]; [self.requestQueue addRequest:request resume:YES]; return YES; diff --git a/Tests/SRGContentProtectionTests/AkamaiTokenTestCase.m b/Tests/SRGContentProtectionTests/AkamaiTokenTestCase.m index 2d8b042..f7c145e 100644 --- a/Tests/SRGContentProtectionTests/AkamaiTokenTestCase.m +++ b/Tests/SRGContentProtectionTests/AkamaiTokenTestCase.m @@ -61,7 +61,7 @@ - (void)testTokenizeNonAkamaiURL XCTestExpectation *expectation = [self expectationWithDescription:@"Request ended"]; // No specific measure is preventing tokenization of non-Akamai URLs - [[SRGAkamaiToken tokenizeURL:[NSURL URLWithString:@"http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8"] withSession:[NSURLSession sharedSession] completionBlock:^(NSURL * _Nonnull URL, NSHTTPURLResponse * _Nonnull HTTPResponse, NSError * _Nullable error) { + [[SRGAkamaiToken tokenizeURL:[NSURL URLWithString:@"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8"] withSession:[NSURLSession sharedSession] completionBlock:^(NSURL * _Nonnull URL, NSHTTPURLResponse * _Nonnull HTTPResponse, NSError * _Nullable error) { XCTAssertNotNil(TestURLParameter(URL, @"hdnts")); XCTAssertEqual(HTTPResponse.statusCode, 200); XCTAssertNil(error); diff --git a/docs/README.md b/docs/README.md index 52a28cd..f2061e3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -45,6 +45,8 @@ If the protection used does not match the one required by the content, playback FairPlay stream playback requires a physical iOS device. Streams will not play in the simulator. +While the HLS specification allows relative URLs in playlist, this implementation supports only Akamai master playlists containing full URLs only (for variants and video / audio / subtitles tracks) on iOS 9 and iOS 10. + ## License See the [LICENSE](../LICENSE) file for more information.