From df21c96b628c8e7c7b8dc56c6d52e2eba335317c Mon Sep 17 00:00:00 2001 From: Jeroen Versteege Date: Tue, 8 Oct 2019 23:00:52 +0200 Subject: [PATCH 01/22] Fixes for SPM: Changed target path/sources/publicHeadersPath Changed imports to file ref Excluded XCDYouTubeVideoPlayerViewController to temporary fix macOS & tvOS compatibility --- Package.swift | 7 ++++--- .../XCDYouTubeVideoPlayerViewController.h | 0 .../XCDYouTubeVideoPlayerViewController.m | 0 XCDYouTubeKit/XCDYouTubeClient.h | 6 +++--- XCDYouTubeKit/XCDYouTubeKit.h | 16 +++++++++------- XCDYouTubeKit/XCDYouTubeVideo+Private.h | 2 +- XCDYouTubeKit/XCDYouTubeVideoOperation.h | 4 ++-- 7 files changed, 19 insertions(+), 16 deletions(-) rename {XCDYouTubeKit => XCDYouTubeKit.Exclude}/XCDYouTubeVideoPlayerViewController.h (100%) rename {XCDYouTubeKit => XCDYouTubeKit.Exclude}/XCDYouTubeVideoPlayerViewController.m (100%) diff --git a/Package.swift b/Package.swift index c3a522fa..70d734b5 100644 --- a/Package.swift +++ b/Package.swift @@ -3,14 +3,15 @@ import PackageDescription let package = Package( name: "XCDYouTubeKit", - // platforms: [.iOS("8.0"), .macOS("10.10"), tvOS("9.0")], products: [ - .library(name: "XCDYouTubeKit", targets: ["XCDYouTubeKit"]) + .library(name: "XCDYouTubeKit" , targets: ["XCDYouTubeKit"]) ], targets: [ .target( name: "XCDYouTubeKit", - path: "XCDYouTubeKit" + path: ".", + sources: ["XCDYouTubeKit"], + publicHeadersPath: "XCDYouTubeKit" ) ] ) diff --git a/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h b/XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.h similarity index 100% rename from XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h rename to XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.h diff --git a/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m b/XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.m similarity index 100% rename from XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m rename to XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.m diff --git a/XCDYouTubeKit/XCDYouTubeClient.h b/XCDYouTubeKit/XCDYouTubeClient.h index 2b89201c..e9a2778a 100644 --- a/XCDYouTubeKit/XCDYouTubeClient.h +++ b/XCDYouTubeKit/XCDYouTubeClient.h @@ -11,9 +11,9 @@ #import -#import -#import -#import +#import "XCDYouTubeOperation.h" +#import "XCDYouTubeVideo.h" +#import "XCDYouTubeError.h" NS_ASSUME_NONNULL_BEGIN diff --git a/XCDYouTubeKit/XCDYouTubeKit.h b/XCDYouTubeKit/XCDYouTubeKit.h index 250ef88f..c1c9b5ba 100644 --- a/XCDYouTubeKit/XCDYouTubeKit.h +++ b/XCDYouTubeKit/XCDYouTubeKit.h @@ -4,13 +4,15 @@ #import -#import -#import -#import -#import -#import -#import +#import "XCDYouTubeClient.h" +#import "XCDYouTubeError.h" +#import "XCDYouTubeLogger.h" +#import "XCDYouTubeOperation.h" +#import "XCDYouTubeVideo.h" +#import "XCDYouTubeVideoOperation.h" +/* #if TARGET_OS_IOS || (!defined(TARGET_OS_IOS) && TARGET_OS_IPHONE) -#import +#import "XCDYouTubeVideoPlayerViewController.h" #endif +*/ diff --git a/XCDYouTubeKit/XCDYouTubeVideo+Private.h b/XCDYouTubeKit/XCDYouTubeVideo+Private.h index 3f1c196c..54c0c3cd 100644 --- a/XCDYouTubeKit/XCDYouTubeVideo+Private.h +++ b/XCDYouTubeKit/XCDYouTubeVideo+Private.h @@ -2,7 +2,7 @@ // Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. // -#import +#import "XCDYouTubeVideo.h" #import "XCDYouTubePlayerScript.h" diff --git a/XCDYouTubeKit/XCDYouTubeVideoOperation.h b/XCDYouTubeKit/XCDYouTubeVideoOperation.h index 4ee8b7b0..644aa4ef 100644 --- a/XCDYouTubeKit/XCDYouTubeVideoOperation.h +++ b/XCDYouTubeKit/XCDYouTubeVideoOperation.h @@ -10,8 +10,8 @@ #import -#import -#import +#import "XCDYouTubeOperation.h" +#import "XCDYouTubeVideo.h" NS_ASSUME_NONNULL_BEGIN From cf1673d80e42087ec02991e1ea828cd39ac44ee9 Mon Sep 17 00:00:00 2001 From: Jeroen Versteege Date: Wed, 9 Oct 2019 09:38:30 +0200 Subject: [PATCH 02/22] Added conditional compile flags for videoplayerviewcontroller --- XCDYouTubeKit/XCDYouTubeKit.h | 3 +-- .../XCDYouTubeVideoPlayerViewController.h | 4 ++++ .../XCDYouTubeVideoPlayerViewController.m | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) rename {XCDYouTubeKit.Exclude => XCDYouTubeKit}/XCDYouTubeVideoPlayerViewController.h (98%) rename {XCDYouTubeKit.Exclude => XCDYouTubeKit}/XCDYouTubeVideoPlayerViewController.m (99%) diff --git a/XCDYouTubeKit/XCDYouTubeKit.h b/XCDYouTubeKit/XCDYouTubeKit.h index c1c9b5ba..bff607ef 100644 --- a/XCDYouTubeKit/XCDYouTubeKit.h +++ b/XCDYouTubeKit/XCDYouTubeKit.h @@ -11,8 +11,7 @@ #import "XCDYouTubeVideo.h" #import "XCDYouTubeVideoOperation.h" -/* + #if TARGET_OS_IOS || (!defined(TARGET_OS_IOS) && TARGET_OS_IPHONE) #import "XCDYouTubeVideoPlayerViewController.h" #endif -*/ diff --git a/XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.h b/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h similarity index 98% rename from XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.h rename to XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h index 812ee9c1..14b53b0f 100644 --- a/XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.h +++ b/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h @@ -9,6 +9,8 @@ #define null_resettable #endif +#if TARGET_OS_IOS || (!defined(TARGET_OS_IOS) && TARGET_OS_IPHONE) + #import NS_ASSUME_NONNULL_BEGIN @@ -124,3 +126,5 @@ MP_EXTERN NSString *const XCDMetadataKeyMediumThumbnailURL DEPRECATED_MSG_ATTRIB MP_EXTERN NSString *const XCDMetadataKeyLargeThumbnailURL DEPRECATED_MSG_ATTRIBUTE("Use XCDYouTubeVideoUserInfoKey instead."); NS_ASSUME_NONNULL_END + +#endif diff --git a/XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.m b/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m similarity index 99% rename from XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.m rename to XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m index ebec6c3c..659a716b 100644 --- a/XCDYouTubeKit.Exclude/XCDYouTubeVideoPlayerViewController.m +++ b/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m @@ -1,6 +1,7 @@ // // Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. // +#if TARGET_OS_IOS || (!defined(TARGET_OS_IOS) && TARGET_OS_IPHONE) #import "XCDYouTubeVideoPlayerViewController.h" @@ -207,3 +208,4 @@ - (void) viewWillDisappear:(BOOL)animated } @end +#endif From 6ab4c2be20470a459f7d870975309526aeee0cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sone=C3=A9=20John?= Date: Thu, 17 Oct 2019 17:57:14 -0400 Subject: [PATCH 03/22] Add new `streamURL` property --- XCDYouTubeKit/XCDYouTubeVideo.h | 7 +++++++ XCDYouTubeKit/XCDYouTubeVideo.m | 2 ++ 2 files changed, 9 insertions(+) diff --git a/XCDYouTubeKit/XCDYouTubeVideo.h b/XCDYouTubeKit/XCDYouTubeVideo.h index 7668a930..4cd09143 100644 --- a/XCDYouTubeKit/XCDYouTubeVideo.h +++ b/XCDYouTubeKit/XCDYouTubeVideo.h @@ -106,6 +106,13 @@ extern NSString *const XCDYouTubeVideoQualityHTTPLiveStreaming; @property (nonatomic, readonly) NSDictionary *streamURLs; #endif +/** + +* A streamURL that is compatible on Apple devices. +* The `streamURLs` may contain both video and audio streams, some video streams do not contain any audio. This property will return a video stream that contains both audio and video with a maximum video quality of 720p in the case of videos that aren't live. Also, this properly will return the URL to a live stream in the case of live videos. +*/ +@property (nonatomic, readonly) NSURL *streamURL; + /** * A dictionary of caption URLs (XML). diff --git a/XCDYouTubeKit/XCDYouTubeVideo.m b/XCDYouTubeKit/XCDYouTubeVideo.m index 02cd7f33..8769b753 100644 --- a/XCDYouTubeKit/XCDYouTubeVideo.m +++ b/XCDYouTubeKit/XCDYouTubeVideo.m @@ -280,6 +280,8 @@ - (instancetype) initWithIdentifier:(NSString *)identifier info:(NSDictionary *) } } + _streamURL = _streamURLs[XCDYouTubeVideoQualityHTTPLiveStreaming] ?: _streamURLs[@(XCDYouTubeVideoQualityHD720)] ?: _streamURLs[@(XCDYouTubeVideoQualityMedium360)] ?: _streamURLs[@(XCDYouTubeVideoQualitySmall240)]; + return self; } else From ea207a017f03d7a1e133e6cccfa539ded1492b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sone=C3=A9=20John?= Date: Wed, 23 Oct 2019 11:59:48 -0400 Subject: [PATCH 04/22] Change table view to adapt to dark mode --- .../en.lproj/MainStoryboard.storyboard | 272 ++++++++---------- 1 file changed, 125 insertions(+), 147 deletions(-) diff --git a/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard b/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard index bdae45be..4ee3fa30 100644 --- a/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard +++ b/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -11,8 +13,8 @@ + - @@ -20,86 +22,81 @@ - + + + + + - + - - + + - + - - - - + - - + + - - - - + - - + + - - - + - - + @@ -116,108 +113,99 @@ - + - + - - + - + - + - - + - + - + - - + - + - + - - + - + - + - - + @@ -236,59 +224,57 @@ - + + + + + - + - - + + - - + - + - - - + - - - + - - + @@ -300,45 +286,45 @@ - + + + + + - + - - - + @@ -347,78 +333,74 @@ - + - + + + + + - + - - + + - - - + - - + + - - - + @@ -449,72 +431,68 @@ - + + + + + - + - - + + - - + - - + - - - + - - + @@ -525,15 +503,15 @@ - + + - @@ -541,7 +519,7 @@ - + From 64380f1283848c41523074a47e45dfa89916a886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sone=C3=A9=20John?= Date: Wed, 23 Oct 2019 12:12:57 -0400 Subject: [PATCH 05/22] Adapt view controllers to support dark mode --- .../iOS Demo/SettingsViewController.m | 3 +++ .../en.lproj/MainStoryboard.storyboard | 18 +++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/XCDYouTubeKit Demo/iOS Demo/SettingsViewController.m b/XCDYouTubeKit Demo/iOS Demo/SettingsViewController.m index a0234fe2..039d4c76 100644 --- a/XCDYouTubeKit Demo/iOS Demo/SettingsViewController.m +++ b/XCDYouTubeKit Demo/iOS Demo/SettingsViewController.m @@ -20,6 +20,9 @@ - (void) viewDidLoad { [super viewDidLoad]; + if (@available(iOS 13.0, *)) { + self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight; + } self.playVideoInBackgroundSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"PlayVideoInBackground"]; self.audioSessionCategoryLabel.text = [[AVAudioSession sharedInstance] category]; diff --git a/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard b/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard index 4ee3fa30..46b311fc 100644 --- a/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard +++ b/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard @@ -271,10 +271,10 @@ - + - + @@ -304,7 +304,7 @@ - + @@ -324,7 +324,7 @@ - + @@ -355,7 +355,7 @@ - + @@ -389,7 +389,6 @@ - @@ -400,7 +399,7 @@ - + @@ -479,6 +478,7 @@ + @@ -489,10 +489,10 @@ - + - + From b5c5bcf4328da2bd3f6b32ab3f1ba12e83f743aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sone=C3=A9=20John?= Date: Thu, 24 Oct 2019 11:01:46 -0400 Subject: [PATCH 06/22] Finish compatibility for iOS 13 --- .../project.pbxproj | 27 ++++-- .../iOS Demo/DemoAsynchronousViewController.m | 40 +++++++-- .../iOS Demo/DemoFullScreenViewController.h | 16 ---- .../iOS Demo/DemoFullScreenViewController.m | 83 ------------------- .../DemoFullScreenViewController.swift | 61 ++++++++++++++ .../iOS Demo/DemoInlineViewController.h | 2 - .../iOS Demo/DemoInlineViewController.m | 49 +++++------ .../iOS Demo/DemoThumbnailViewController.m | 68 ++++++++------- XCDYouTubeKit Demo/iOS Demo/Utilities.swift | 23 +++++ .../XCDYouTubeKit iOS Demo-Bridging-Header.h | 5 ++ .../en.lproj/MainStoryboard.storyboard | 19 +---- 11 files changed, 199 insertions(+), 194 deletions(-) delete mode 100644 XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.h delete mode 100644 XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.m create mode 100644 XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift create mode 100644 XCDYouTubeKit Demo/iOS Demo/Utilities.swift create mode 100644 XCDYouTubeKit Demo/iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h diff --git a/XCDYouTubeKit Demo/XCDYouTubeKit Demo.xcodeproj/project.pbxproj b/XCDYouTubeKit Demo/XCDYouTubeKit Demo.xcodeproj/project.pbxproj index ea93b3e9..01f0d4f4 100644 --- a/XCDYouTubeKit Demo/XCDYouTubeKit Demo.xcodeproj/project.pbxproj +++ b/XCDYouTubeKit Demo/XCDYouTubeKit Demo.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 019AFA1E2361E9CD00B03F57 /* DemoFullScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019AFA1D2361E9CC00B03F57 /* DemoFullScreenViewController.swift */; }; + 019AFA202361F10D00B03F57 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019AFA1F2361F10D00B03F57 /* Utilities.swift */; }; 0ADD88CC5EC5E9D286D8A8E0 /* libPods-XCDYouTubeKit iOS Demo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B3DDE49D0638521AD03AA01 /* libPods-XCDYouTubeKit iOS Demo.a */; }; 6941B80FC7AE87BA698FDD61 /* libPods-XCDYouTubeKit OS X Demo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CF61ADAFAE5A8C5F8C082438 /* libPods-XCDYouTubeKit OS X Demo.a */; }; C232DA831C00BCEB00E26E3A /* GradientMaskView.m in Sources */ = {isa = PBXBuildFile; fileRef = C232DA821C00BCEB00E26E3A /* GradientMaskView.m */; }; @@ -29,7 +31,6 @@ C27415A617F491230026834B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C27415A517F491230026834B /* CoreGraphics.framework */; }; C27415D217F4CDD80026834B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C27415C017F4CDD80026834B /* AppDelegate.m */; }; C27415D317F4CDD80026834B /* DemoAsynchronousViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C27415C217F4CDD80026834B /* DemoAsynchronousViewController.m */; }; - C27415D417F4CDD80026834B /* DemoFullScreenViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C27415C417F4CDD80026834B /* DemoFullScreenViewController.m */; }; C27415D517F4CDD80026834B /* DemoInlineViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C27415C617F4CDD80026834B /* DemoInlineViewController.m */; }; C27415D617F4CDD80026834B /* DemoThumbnailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C27415C817F4CDD80026834B /* DemoThumbnailViewController.m */; }; C27415D717F4CDD80026834B /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C27415C917F4CDD80026834B /* MainStoryboard.storyboard */; }; @@ -90,6 +91,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 019AFA1C2361E9CC00B03F57 /* XCDYouTubeKit iOS Demo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCDYouTubeKit iOS Demo-Bridging-Header.h"; sourceTree = ""; }; + 019AFA1D2361E9CC00B03F57 /* DemoFullScreenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoFullScreenViewController.swift; sourceTree = ""; }; + 019AFA1F2361F10D00B03F57 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; 044E71F373F947A64C774319 /* Pods-XCDYouTubeKit iOS Demo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XCDYouTubeKit iOS Demo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-XCDYouTubeKit iOS Demo/Pods-XCDYouTubeKit iOS Demo.debug.xcconfig"; sourceTree = ""; }; 4805F31947DAD8E88035A44A /* Pods-XCDYouTubeKit OS X Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XCDYouTubeKit OS X Demo.release.xcconfig"; path = "Pods/Target Support Files/Pods-XCDYouTubeKit OS X Demo/Pods-XCDYouTubeKit OS X Demo.release.xcconfig"; sourceTree = ""; }; 5B3DDE49D0638521AD03AA01 /* libPods-XCDYouTubeKit iOS Demo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-XCDYouTubeKit iOS Demo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -118,8 +122,6 @@ C27415C017F4CDD80026834B /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; C27415C117F4CDD80026834B /* DemoAsynchronousViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoAsynchronousViewController.h; sourceTree = ""; }; C27415C217F4CDD80026834B /* DemoAsynchronousViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoAsynchronousViewController.m; sourceTree = ""; }; - C27415C317F4CDD80026834B /* DemoFullScreenViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoFullScreenViewController.h; sourceTree = ""; }; - C27415C417F4CDD80026834B /* DemoFullScreenViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoFullScreenViewController.m; sourceTree = ""; }; C27415C517F4CDD80026834B /* DemoInlineViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoInlineViewController.h; sourceTree = ""; }; C27415C617F4CDD80026834B /* DemoInlineViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoInlineViewController.m; sourceTree = ""; }; C27415C717F4CDD80026834B /* DemoThumbnailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoThumbnailViewController.h; sourceTree = ""; }; @@ -274,10 +276,10 @@ C2630D3E1935C449000D3917 /* PlayerEventLogger.m */, C2570B841A02415F00127127 /* NowPlayingInfoCenterProvider.h */, C2570B851A02415F00127127 /* NowPlayingInfoCenterProvider.m */, - C27415C317F4CDD80026834B /* DemoFullScreenViewController.h */, - C27415C417F4CDD80026834B /* DemoFullScreenViewController.m */, C2C5D2961A6E5AB900F2B3F8 /* VideoPickerController.h */, C2C5D2971A6E5AB900F2B3F8 /* VideoPickerController.m */, + 019AFA1F2361F10D00B03F57 /* Utilities.swift */, + 019AFA1D2361E9CC00B03F57 /* DemoFullScreenViewController.swift */, C27415C517F4CDD80026834B /* DemoInlineViewController.h */, C27415C617F4CDD80026834B /* DemoInlineViewController.m */, C27415C717F4CDD80026834B /* DemoThumbnailViewController.h */, @@ -290,6 +292,7 @@ C2BA376C192AB32200B27FAD /* MPMoviePlayerController+BackgroundPlayback.m */, C27415C917F4CDD80026834B /* MainStoryboard.storyboard */, C27415CB17F4CDD80026834B /* Supporting Files */, + 019AFA1C2361E9CC00B03F57 /* XCDYouTubeKit iOS Demo-Bridging-Header.h */, ); path = "iOS Demo"; sourceTree = ""; @@ -422,6 +425,9 @@ LastUpgradeCheck = 0700; ORGANIZATIONNAME = "Cédric Luthi"; TargetAttributes = { + C274159D17F491230026834B = { + LastSwiftMigration = 1110; + }; C2D627581BE3C648005367FF = { CreatedOnToolsVersion = 7.1; }; @@ -432,6 +438,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -636,11 +643,12 @@ C2570B861A02415F00127127 /* NowPlayingInfoCenterProvider.m in Sources */, C27415D317F4CDD80026834B /* DemoAsynchronousViewController.m in Sources */, C2630D3F1935C449000D3917 /* PlayerEventLogger.m in Sources */, + 019AFA202361F10D00B03F57 /* Utilities.swift in Sources */, C2CF7CEE1B17737400C356EA /* ContextLogFormatter.m in Sources */, - C27415D417F4CDD80026834B /* DemoFullScreenViewController.m in Sources */, C2C5D2981A6E5AB900F2B3F8 /* VideoPickerController.m in Sources */, C27415D517F4CDD80026834B /* DemoInlineViewController.m in Sources */, C27415D617F4CDD80026834B /* DemoThumbnailViewController.m in Sources */, + 019AFA1E2361E9CD00B03F57 /* DemoFullScreenViewController.swift in Sources */, C27415DC17F4CDD80026834B /* main.m in Sources */, C2EFB48718730A2B0046B1FE /* SettingsViewController.m in Sources */, C2BA376D192AB32200B27FAD /* MPMoviePlayerController+BackgroundPlayback.m in Sources */, @@ -833,10 +841,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = "iOS Demo/Supporting Files/XCDYouTubeKit iOS Demo-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ch.pitaya.xcdyoutubekit.demo.ios; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; }; @@ -848,10 +860,13 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = "iOS Demo/Supporting Files/XCDYouTubeKit iOS Demo-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ch.pitaya.xcdyoutubekit.demo.ios; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; }; diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoAsynchronousViewController.m b/XCDYouTubeKit Demo/iOS Demo/DemoAsynchronousViewController.m index 33f2989f..e4f15ba2 100644 --- a/XCDYouTubeKit Demo/iOS Demo/DemoAsynchronousViewController.m +++ b/XCDYouTubeKit Demo/iOS Demo/DemoAsynchronousViewController.m @@ -6,7 +6,9 @@ #import -#import "MPMoviePlayerController+BackgroundPlayback.h" +#import + +#import "XCDYouTubeKit_iOS_Demo-Swift.h" @implementation DemoAsynchronousViewController @@ -21,18 +23,38 @@ - (IBAction) play:(id)sender { NSString *apiKey = self.apiKeyTextField.text; [[NSUserDefaults standardUserDefaults] setObject:apiKey forKey:@"YouTubeAPIKey"]; - - XCDYouTubeVideoPlayerViewController *videoPlayerViewController = [XCDYouTubeVideoPlayerViewController new]; - videoPlayerViewController.moviePlayer.backgroundPlaybackEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"PlayVideoInBackground"]; - [self presentMoviePlayerViewControllerAnimated:videoPlayerViewController]; - + // https://developers.google.com/youtube/v3/docs/videos/list NSURL *mostPopularURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.googleapis.com/youtube/v3/videos?key=%@&chart=mostPopular&part=id", apiKey]]; - [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:mostPopularURL] queue:[NSOperationQueue new] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) - { + + [[[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]dataTaskWithURL:mostPopularURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) + { + if (error) { + [[Utilities shared]displayError:error originViewController:self]; + return; + } + id json = [NSJSONSerialization JSONObjectWithData:data ?: [NSData new] options:0 error:NULL]; NSString *videoIdentifier = [[[json valueForKeyPath:@"items.id"] firstObject] description]; - videoPlayerViewController.videoIdentifier = videoIdentifier; + [self displayVideoIdentifier:videoIdentifier]; + }] resume]; +} + +- (void) displayVideoIdentifier:(NSString *)videoIdentifier +{ + + [[XCDYouTubeClient defaultClient]getVideoWithIdentifier:videoIdentifier completionHandler:^(XCDYouTubeVideo * _Nullable video, NSError * _Nullable error) + { + if (error) { + [[Utilities shared]displayError:error originViewController:self]; + return; + } + + AVPlayerViewController *playerViewController = [AVPlayerViewController new]; + playerViewController.player = [AVPlayer playerWithURL:video.streamURL]; + [self presentViewController:playerViewController animated:YES completion:nil]; + [playerViewController.player play]; + }]; } diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.h b/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.h deleted file mode 100644 index 2085880d..00000000 --- a/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. -// - -@import UIKit; - -#import "VideoPickerController.h" - -@interface DemoFullScreenViewController : UIViewController - -@property (nonatomic, weak) IBOutlet UITextField *videoIdentifierTextField; -@property (nonatomic, weak) IBOutlet UISwitch *lowQualitySwitch; - -- (IBAction) play:(id)sender; - -@end diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.m b/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.m deleted file mode 100644 index 92438df4..00000000 --- a/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.m +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. -// - -#import "DemoFullScreenViewController.h" - -#import - -#import "MPMoviePlayerController+BackgroundPlayback.h" - -@implementation DemoFullScreenViewController - -- (void) viewDidLoad -{ - [super viewDidLoad]; - - [self restoreVideoIdentifier]; -} - -- (void) saveVideoIdentifier -{ - [[NSUserDefaults standardUserDefaults] setObject:self.videoIdentifierTextField.text forKey:@"VideoIdentifier"]; -} - -- (void) restoreVideoIdentifier -{ - self.videoIdentifierTextField.text = [[NSUserDefaults standardUserDefaults] objectForKey:@"VideoIdentifier"]; -} - -- (IBAction) endEditing:(id)sender -{ - [self.view endEditing:YES]; -} - -- (IBAction) play:(id)sender -{ - XCDYouTubeVideoPlayerViewController *videoPlayerViewController = [[XCDYouTubeVideoPlayerViewController alloc] initWithVideoIdentifier:self.videoIdentifierTextField.text]; - videoPlayerViewController.moviePlayer.backgroundPlaybackEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"PlayVideoInBackground"]; - videoPlayerViewController.preferredVideoQualities = self.lowQualitySwitch.on ? @[ @(XCDYouTubeVideoQualitySmall240), @(XCDYouTubeVideoQualityMedium360) ] : nil; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerPlaybackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:videoPlayerViewController.moviePlayer]; - [self presentMoviePlayerViewControllerAnimated:videoPlayerViewController]; -} - -#pragma mark - Notifications - -- (void) moviePlayerPlaybackDidFinish:(NSNotification *)notification -{ - [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:notification.object]; - MPMovieFinishReason finishReason = [notification.userInfo[MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] integerValue]; - if (finishReason == MPMovieFinishReasonPlaybackError) - { - NSString *title = NSLocalizedString(@"Video Playback Error", @"Full screen video error alert - title"); - NSError *error = notification.userInfo[XCDMoviePlayerPlaybackDidFinishErrorUserInfoKey]; - NSString *message = [NSString stringWithFormat:@"%@\n%@ (%@)", error.localizedDescription, error.domain, @(error.code)]; - NSString *cancelButtonTitle = NSLocalizedString(@"OK", @"Full screen video error alert - cancel button"); - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil]; - [alertView show]; - } -} - - -#pragma mark - UITextFieldDelegate - -- (BOOL) textFieldShouldReturn:(UITextField *)textField -{ - [self play:textField]; - return YES; -} - -- (void) textFieldDidEndEditing:(UITextField *)textField -{ - [self saveVideoIdentifier]; -} - -#pragma mark - VideoPickerControllerDelegate - -- (void) videoPickerController:(VideoPickerController *)videoPickerController didSelectVideoWithIdentifier:(NSString *)videoIdentifier -{ - self.videoIdentifierTextField.text = videoIdentifier; - [self saveVideoIdentifier]; -} - -@end diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift b/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift new file mode 100644 index 00000000..c2a6a459 --- /dev/null +++ b/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift @@ -0,0 +1,61 @@ +// +// DemoFullScreenViewController.swift +// XCDYouTubeKit iOS Demo +// +// Created by Soneé John on 10/17/19. +// Copyright © 2019 Cédric Luthi. All rights reserved. +// + +import UIKit +import AVKit +import XCDYouTubeKit + +extension DemoFullScreenViewController: VideoPickerControllerDelegate { + func videoPickerController(_ videoPickerController: VideoPickerController!, didSelectVideoWithIdentifier videoIdentifier: String!) { + self.videoIdentifierTextField.text = videoIdentifier + UserDefaults.standard.set(videoIdentifier, forKey: "VideoIdentifier") + } +} + +extension DemoFullScreenViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.play(textField) + return true + } + + func textFieldDidEndEditing(_ textField: UITextField) { + UserDefaults.standard.set(self.videoIdentifierTextField.text, forKey: "VideoIdentifier") + } +} + +class DemoFullScreenViewController: UIViewController { + + @IBOutlet weak open var lowQualitySwitch: UISwitch! + @IBOutlet weak open var videoIdentifierTextField: UITextField! + + override func viewDidLoad() { + super.viewDidLoad() + self.videoIdentifierTextField.text = UserDefaults.standard.string(forKey: "VideoIdentifier") + } + + @IBAction open func endEditing(_ sender: Any!) { + self.view.endEditing(true) + } + + @IBAction open func play(_ sender: Any!) { + let playerViewController = AVPlayerViewController() + self.present(playerViewController, animated: true, completion: nil) + + XCDYouTubeClient.default().getVideoWithIdentifier(self.videoIdentifierTextField.text) { (video, error) in + guard error == nil else { + Utilities.shared.displayError(error! as NSError, originViewController: self) + return + } + + let streamURL = self.lowQualitySwitch.isOn ? video?.streamURLs[XCDYouTubeVideoQualityHTTPLiveStreaming] ?? video?.streamURLs[XCDYouTubeVideoQuality.medium360.rawValue] ?? video?.streamURLs[XCDYouTubeVideoQuality.small240.rawValue] : video?.streamURL + + playerViewController.player = AVPlayer(url: streamURL!) + playerViewController.player?.play() + } + } +} diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.h b/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.h index cc4596bc..3531a67b 100644 --- a/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.h +++ b/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.h @@ -7,10 +7,8 @@ @interface DemoInlineViewController : UIViewController @property (nonatomic, weak) IBOutlet UIView *videoContainerView; -@property (nonatomic, weak) IBOutlet UISwitch *prepareToPlaySwitch; @property (nonatomic, weak) IBOutlet UISwitch *shouldAutoplaySwitch; - (IBAction) load:(id)sender; -- (IBAction) prepareToPlay:(UISwitch *)sender; @end diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.m b/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.m index 6c2d7e03..c5d1e2fc 100644 --- a/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.m +++ b/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.m @@ -6,44 +6,35 @@ #import -#import "MPMoviePlayerController+BackgroundPlayback.h" +#import -@interface DemoInlineViewController () - -@property (nonatomic, strong) XCDYouTubeVideoPlayerViewController *videoPlayerViewController; - -@end +#import "XCDYouTubeKit_iOS_Demo-Swift.h" @implementation DemoInlineViewController -- (void) viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - // Beware, viewWillDisappear: is called when the player view enters full screen on iOS 6+ - if ([self isMovingFromParentViewController]) - [self.videoPlayerViewController.moviePlayer stop]; -} - - (IBAction) load:(id)sender { - [self.videoContainerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; - NSString *videoIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:@"VideoIdentifier"]; - self.videoPlayerViewController = [[XCDYouTubeVideoPlayerViewController alloc] initWithVideoIdentifier:videoIdentifier]; - self.videoPlayerViewController.moviePlayer.backgroundPlaybackEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"PlayVideoInBackground"]; - [self.videoPlayerViewController presentInView:self.videoContainerView]; - if (self.prepareToPlaySwitch.on) - [self.videoPlayerViewController.moviePlayer prepareToPlay]; + AVPlayerViewController *playerViewController = [AVPlayerViewController new]; + playerViewController.view.frame = self.videoContainerView.bounds; + [self addChildViewController:playerViewController]; + [self.videoContainerView addSubview:playerViewController.view]; + [playerViewController didMoveToParentViewController:self]; - self.videoPlayerViewController.moviePlayer.shouldAutoplay = self.shouldAutoplaySwitch.on; -} - -- (IBAction) prepareToPlay:(UISwitch *)sender -{ - if (sender.on) - [self.videoPlayerViewController.moviePlayer prepareToPlay]; + __weak AVPlayerViewController *weakPlayerViewController = playerViewController; + [[XCDYouTubeClient defaultClient] getVideoWithIdentifier:videoIdentifier completionHandler:^(XCDYouTubeVideo * _Nullable video, NSError * _Nullable error) { + if (video) + { + weakPlayerViewController.player = [AVPlayer playerWithURL:video.streamURL]; + if (self.shouldAutoplaySwitch.on) + [weakPlayerViewController.player play]; + } + else + { + [[Utilities shared]displayError:error originViewController:self]; + } + }]; } @end diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoThumbnailViewController.m b/XCDYouTubeKit Demo/iOS Demo/DemoThumbnailViewController.m index 588d19d9..3602bf4e 100644 --- a/XCDYouTubeKit Demo/iOS Demo/DemoThumbnailViewController.m +++ b/XCDYouTubeKit Demo/iOS Demo/DemoThumbnailViewController.m @@ -6,11 +6,13 @@ #import -#import "MPMoviePlayerController+BackgroundPlayback.h" +#import + +#import "XCDYouTubeKit_iOS_Demo-Swift.h" @interface DemoThumbnailViewController () -@property (nonatomic, strong) XCDYouTubeVideoPlayerViewController *videoPlayerViewController; +@property (nonatomic, strong) XCDYouTubeVideo *video; @end @@ -19,46 +21,48 @@ @implementation DemoThumbnailViewController - (IBAction) loadThumbnail:(id)sender { NSString *videoIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:@"VideoIdentifier"]; - self.videoPlayerViewController = [[XCDYouTubeVideoPlayerViewController alloc] initWithVideoIdentifier:videoIdentifier]; - self.videoPlayerViewController.moviePlayer.backgroundPlaybackEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"PlayVideoInBackground"]; - NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; - [defaultCenter addObserver:self selector:@selector(videoPlayerViewControllerDidReceiveVideo:) name:XCDYouTubeVideoPlayerViewControllerDidReceiveVideoNotification object:self.videoPlayerViewController]; - [defaultCenter addObserver:self selector:@selector(moviePlayerPlaybackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.videoPlayerViewController.moviePlayer]; + [[XCDYouTubeClient defaultClient]getVideoWithIdentifier:videoIdentifier completionHandler:^(XCDYouTubeVideo * _Nullable video, NSError * _Nullable error) + { + if (error) { + [[Utilities shared]displayError:error originViewController:self]; + return; + } + + [self displayThumbnailWithVideo:video]; + + }]; } - (IBAction) play:(id)sender { - [self.videoPlayerViewController presentInView:self.videoContainerView]; - [self.videoPlayerViewController.moviePlayer play]; + AVPlayerViewController *playerViewController = [AVPlayerViewController new]; + playerViewController.player = [AVPlayer playerWithURL:self.video.streamURL]; + [self presentViewController:playerViewController animated:YES completion:nil]; + [playerViewController.player play]; } -#pragma mark - Notifications - -- (void) videoPlayerViewControllerDidReceiveVideo:(NSNotification *)notification +- (void) displayThumbnailWithVideo:(XCDYouTubeVideo *)video { - XCDYouTubeVideo *video = notification.userInfo[XCDYouTubeVideoUserInfoKey]; + self.video = video; self.titleLabel.text = video.title; - - NSURL *thumbnailURL = video.mediumThumbnailURL ?: video.smallThumbnailURL; - [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:thumbnailURL] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) - { - self.thumbnailImageView.image = [UIImage imageWithData:data]; - - [self.actionButton setTitle:NSLocalizedString(@"Play Video", nil) forState:UIControlStateNormal]; - [self.actionButton removeTarget:self action:NULL forControlEvents:UIControlEventAllEvents]; - [self.actionButton addTarget:self action:@selector(play:) forControlEvents:UIControlEventTouchUpInside]; - }]; -} + NSURL *thumbnailURL = video.thumbnailURL; -- (void) moviePlayerPlaybackDidFinish:(NSNotification *)notification -{ - NSError *error = notification.userInfo[XCDMoviePlayerPlaybackDidFinishErrorUserInfoKey]; - if (error) - { - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error", nil) message:error.localizedDescription delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil]; - [alertView show]; - } + [[[NSURLSession sharedSession]dataTaskWithURL:thumbnailURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) + { + if (error) { + [[Utilities shared]displayError:error originViewController:self]; + return; + } + + [[NSOperationQueue mainQueue]addOperationWithBlock:^{ + self.thumbnailImageView.image = [UIImage imageWithData:data]; + [self.actionButton setTitle:NSLocalizedString(@"Play Video", nil) forState:UIControlStateNormal]; + [self.actionButton removeTarget:self action:NULL forControlEvents:UIControlEventAllEvents]; + [self.actionButton addTarget:self action:@selector(play:) forControlEvents:UIControlEventTouchUpInside]; + }]; + + }] resume]; } @end diff --git a/XCDYouTubeKit Demo/iOS Demo/Utilities.swift b/XCDYouTubeKit Demo/iOS Demo/Utilities.swift new file mode 100644 index 00000000..a4393929 --- /dev/null +++ b/XCDYouTubeKit Demo/iOS Demo/Utilities.swift @@ -0,0 +1,23 @@ +// +// Utilities.swift +// XCDYouTubeKit iOS Demo +// +// Created by Soneé John on 10/24/19. +// Copyright © 2019 Cédric Luthi. All rights reserved. +// + +import UIKit + +@objcMembers class Utilities: NSObject { + static let shared = Utilities() + + func displayError(_ error: NSError, originViewController: UIViewController) { + OperationQueue.main.addOperation { + originViewController.dismiss(animated: true) { + let alert = UIAlertController(title: NSLocalizedString("Error", comment: ""), message: error.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) + originViewController.present(alert, animated: true, completion: nil) + } + } + } +} diff --git a/XCDYouTubeKit Demo/iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h b/XCDYouTubeKit Demo/iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h new file mode 100644 index 00000000..5f00c933 --- /dev/null +++ b/XCDYouTubeKit Demo/iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "VideoPickerController.h" diff --git a/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard b/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard index 46b311fc..4be0463e 100644 --- a/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard +++ b/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard @@ -465,29 +465,15 @@ - + - - - - - - - - @@ -496,7 +482,6 @@ - From d1d6092eafc3cbf62b8c4c2cc4e639168b5421d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sone=C3=A9=20John?= Date: Thu, 7 Nov 2019 10:41:29 -0400 Subject: [PATCH 07/22] Support background playback in AVPlayerViewController Fixes (#427, #442) --- .../project.pbxproj | 16 +- .../AVPlayerViewControllerManager.swift | 191 ++++++++++++++++++ XCDYouTubeKit Demo/iOS Demo/AppDelegate.m | 13 +- .../iOS Demo/DemoAsynchronousViewController.m | 4 +- .../DemoFullScreenViewController.swift | 21 +- .../iOS Demo/DemoInlineViewController.m | 25 ++- .../iOS Demo/DemoThumbnailViewController.m | 4 +- ...MoviePlayerController+BackgroundPlayback.h | 18 -- ...MoviePlayerController+BackgroundPlayback.m | 119 ----------- .../XCDYouTubeKit iOS Demo-Bridging-Header.h | 1 + 10 files changed, 235 insertions(+), 177 deletions(-) create mode 100644 XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift delete mode 100644 XCDYouTubeKit Demo/iOS Demo/MPMoviePlayerController+BackgroundPlayback.h delete mode 100644 XCDYouTubeKit Demo/iOS Demo/MPMoviePlayerController+BackgroundPlayback.m diff --git a/XCDYouTubeKit Demo/XCDYouTubeKit Demo.xcodeproj/project.pbxproj b/XCDYouTubeKit Demo/XCDYouTubeKit Demo.xcodeproj/project.pbxproj index 01f0d4f4..eeef1f34 100644 --- a/XCDYouTubeKit Demo/XCDYouTubeKit Demo.xcodeproj/project.pbxproj +++ b/XCDYouTubeKit Demo/XCDYouTubeKit Demo.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 019AFA1E2361E9CD00B03F57 /* DemoFullScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019AFA1D2361E9CC00B03F57 /* DemoFullScreenViewController.swift */; }; 019AFA202361F10D00B03F57 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019AFA1F2361F10D00B03F57 /* Utilities.swift */; }; + 01E88BD523690B75002523D1 /* AVPlayerViewControllerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E88BD423690B75002523D1 /* AVPlayerViewControllerManager.swift */; }; 0ADD88CC5EC5E9D286D8A8E0 /* libPods-XCDYouTubeKit iOS Demo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B3DDE49D0638521AD03AA01 /* libPods-XCDYouTubeKit iOS Demo.a */; }; 6941B80FC7AE87BA698FDD61 /* libPods-XCDYouTubeKit OS X Demo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CF61ADAFAE5A8C5F8C082438 /* libPods-XCDYouTubeKit OS X Demo.a */; }; C232DA831C00BCEB00E26E3A /* GradientMaskView.m in Sources */ = {isa = PBXBuildFile; fileRef = C232DA821C00BCEB00E26E3A /* GradientMaskView.m */; }; @@ -19,7 +20,6 @@ C2428AEA191C3C1400065504 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C2428AE9191C3C1400065504 /* Images.xcassets */; }; C2428B07191C3DE400065504 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C2428B04191C3DE400065504 /* main.m */; }; C2428B0C191C415300065504 /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2428B0B191C415300065504 /* AVKit.framework */; }; - C2570B861A02415F00127127 /* NowPlayingInfoCenterProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = C2570B851A02415F00127127 /* NowPlayingInfoCenterProvider.m */; }; C2597EA21B0CB90C0030E9F2 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2FB52DF1918F89A00B2CBE6 /* JavaScriptCore.framework */; }; C25A0CA51C06884000C644E0 /* XCDYouTubeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2A1E7461BD1AE0F001EAC91 /* XCDYouTubeKit.framework */; }; C25A0CA61C06884100C644E0 /* XCDYouTubeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2A1E7461BD1AE0F001EAC91 /* XCDYouTubeKit.framework */; }; @@ -40,7 +40,6 @@ C27AD2A11A0791F000866050 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C27AD2A01A0791F000866050 /* Images.xcassets */; }; C2A1E7471BD1AE0F001EAC91 /* XCDYouTubeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2A1E7461BD1AE0F001EAC91 /* XCDYouTubeKit.framework */; }; C2ADD72D1BE6AC3100B182ED /* VideoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C2ADD72C1BE6AC3100B182ED /* VideoCell.m */; }; - C2BA376D192AB32200B27FAD /* MPMoviePlayerController+BackgroundPlayback.m in Sources */ = {isa = PBXBuildFile; fileRef = C2BA376C192AB32200B27FAD /* MPMoviePlayerController+BackgroundPlayback.m */; }; C2C5D2981A6E5AB900F2B3F8 /* VideoPickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = C2C5D2971A6E5AB900F2B3F8 /* VideoPickerController.m */; }; C2CF7CEE1B17737400C356EA /* ContextLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = C2CF7CED1B17737400C356EA /* ContextLogFormatter.m */; }; C2D627601BE3C648005367FF /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C2D6275F1BE3C648005367FF /* AppDelegate.m */; }; @@ -94,6 +93,7 @@ 019AFA1C2361E9CC00B03F57 /* XCDYouTubeKit iOS Demo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCDYouTubeKit iOS Demo-Bridging-Header.h"; sourceTree = ""; }; 019AFA1D2361E9CC00B03F57 /* DemoFullScreenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoFullScreenViewController.swift; sourceTree = ""; }; 019AFA1F2361F10D00B03F57 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + 01E88BD423690B75002523D1 /* AVPlayerViewControllerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerViewControllerManager.swift; sourceTree = ""; }; 044E71F373F947A64C774319 /* Pods-XCDYouTubeKit iOS Demo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XCDYouTubeKit iOS Demo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-XCDYouTubeKit iOS Demo/Pods-XCDYouTubeKit iOS Demo.debug.xcconfig"; sourceTree = ""; }; 4805F31947DAD8E88035A44A /* Pods-XCDYouTubeKit OS X Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XCDYouTubeKit OS X Demo.release.xcconfig"; path = "Pods/Target Support Files/Pods-XCDYouTubeKit OS X Demo/Pods-XCDYouTubeKit OS X Demo.release.xcconfig"; sourceTree = ""; }; 5B3DDE49D0638521AD03AA01 /* libPods-XCDYouTubeKit iOS Demo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-XCDYouTubeKit iOS Demo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -110,8 +110,6 @@ C2428B04191C3DE400065504 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; C2428B05191C3DE400065504 /* XCDYouTubeKit OS X Demo-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "XCDYouTubeKit OS X Demo-Info.plist"; sourceTree = ""; }; C2428B0B191C415300065504 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; - C2570B841A02415F00127127 /* NowPlayingInfoCenterProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NowPlayingInfoCenterProvider.h; sourceTree = ""; }; - C2570B851A02415F00127127 /* NowPlayingInfoCenterProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NowPlayingInfoCenterProvider.m; sourceTree = ""; }; C2630D3D1935C449000D3917 /* PlayerEventLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayerEventLogger.h; sourceTree = ""; }; C2630D3E1935C449000D3917 /* PlayerEventLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayerEventLogger.m; sourceTree = ""; }; C274159E17F491230026834B /* XCDYouTubeKit iOS Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "XCDYouTubeKit iOS Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -135,8 +133,6 @@ C2A1E7461BD1AE0F001EAC91 /* XCDYouTubeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = XCDYouTubeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C2ADD72B1BE6AC3100B182ED /* VideoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoCell.h; sourceTree = ""; }; C2ADD72C1BE6AC3100B182ED /* VideoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoCell.m; sourceTree = ""; }; - C2BA376B192AB32200B27FAD /* MPMoviePlayerController+BackgroundPlayback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPMoviePlayerController+BackgroundPlayback.h"; sourceTree = ""; }; - C2BA376C192AB32200B27FAD /* MPMoviePlayerController+BackgroundPlayback.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPMoviePlayerController+BackgroundPlayback.m"; sourceTree = ""; }; C2C5D2961A6E5AB900F2B3F8 /* VideoPickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoPickerController.h; sourceTree = ""; }; C2C5D2971A6E5AB900F2B3F8 /* VideoPickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoPickerController.m; sourceTree = ""; }; C2CEABA61C0EFBE80077C5CA /* Playground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Playground.playground; sourceTree = ""; }; @@ -274,11 +270,10 @@ C2CF7CED1B17737400C356EA /* ContextLogFormatter.m */, C2630D3D1935C449000D3917 /* PlayerEventLogger.h */, C2630D3E1935C449000D3917 /* PlayerEventLogger.m */, - C2570B841A02415F00127127 /* NowPlayingInfoCenterProvider.h */, - C2570B851A02415F00127127 /* NowPlayingInfoCenterProvider.m */, C2C5D2961A6E5AB900F2B3F8 /* VideoPickerController.h */, C2C5D2971A6E5AB900F2B3F8 /* VideoPickerController.m */, 019AFA1F2361F10D00B03F57 /* Utilities.swift */, + 01E88BD423690B75002523D1 /* AVPlayerViewControllerManager.swift */, 019AFA1D2361E9CC00B03F57 /* DemoFullScreenViewController.swift */, C27415C517F4CDD80026834B /* DemoInlineViewController.h */, C27415C617F4CDD80026834B /* DemoInlineViewController.m */, @@ -288,8 +283,6 @@ C27415C217F4CDD80026834B /* DemoAsynchronousViewController.m */, C2EFB48518730A2B0046B1FE /* SettingsViewController.h */, C2EFB48618730A2B0046B1FE /* SettingsViewController.m */, - C2BA376B192AB32200B27FAD /* MPMoviePlayerController+BackgroundPlayback.h */, - C2BA376C192AB32200B27FAD /* MPMoviePlayerController+BackgroundPlayback.m */, C27415C917F4CDD80026834B /* MainStoryboard.storyboard */, C27415CB17F4CDD80026834B /* Supporting Files */, 019AFA1C2361E9CC00B03F57 /* XCDYouTubeKit iOS Demo-Bridging-Header.h */, @@ -639,8 +632,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 01E88BD523690B75002523D1 /* AVPlayerViewControllerManager.swift in Sources */, C27415D217F4CDD80026834B /* AppDelegate.m in Sources */, - C2570B861A02415F00127127 /* NowPlayingInfoCenterProvider.m in Sources */, C27415D317F4CDD80026834B /* DemoAsynchronousViewController.m in Sources */, C2630D3F1935C449000D3917 /* PlayerEventLogger.m in Sources */, 019AFA202361F10D00B03F57 /* Utilities.swift in Sources */, @@ -651,7 +644,6 @@ 019AFA1E2361E9CD00B03F57 /* DemoFullScreenViewController.swift in Sources */, C27415DC17F4CDD80026834B /* main.m in Sources */, C2EFB48718730A2B0046B1FE /* SettingsViewController.m in Sources */, - C2BA376D192AB32200B27FAD /* MPMoviePlayerController+BackgroundPlayback.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift b/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift new file mode 100644 index 00000000..b7cb8b26 --- /dev/null +++ b/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift @@ -0,0 +1,191 @@ +// +// AVPlayerViewControllerManager.swift +// XCDYouTubeKit iOS Demo +// +// Created by Soneé John on 10/29/19. +// Copyright © 2019 Cédric Luthi. All rights reserved. +// + +import Foundation +import AVKit +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() + } +} + +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 + } +} + +@objcMembers class AVPlayerViewControllerManager: NSObject { + //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 + return + } + self.player = AVPlayer(url: video.streamURL) + self.controller.player = self.player + } + } + + 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() + }) + + guard let video = self.video else { return } + if let token = timeObserverToken { + oldValue?.removeTimeObserver(token) + timeObserverToken = nil + } + self.setupRemoteTransportControls() + self.updateGeneralMetadata(video: video) + self.updatePlaybackDuration() + } + } + + public lazy var controller: AVPlayerViewController = { + let controller = AVPlayerViewController() + if #available(iOS 10.0, *) { + controller.updatesNowPlayingInfoCenter = false + } + return controller + }() + + public func disconnectPlayer() { + self.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 { + guard let playerViewController = childVC as? AVPlayerViewController else { continue } + playerViewController.player = self.player + break + } + } + return + } + playerViewController.player = self.player + } + + //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 func updateGeneralMetadata(video: XCDYouTubeVideo) { + 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 + 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 + } + + 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 + 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 + nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(player.currentItem!.currentTime()) + 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 + } +} diff --git a/XCDYouTubeKit Demo/iOS Demo/AppDelegate.m b/XCDYouTubeKit Demo/iOS Demo/AppDelegate.m index 4451e322..b6620944 100644 --- a/XCDYouTubeKit Demo/iOS Demo/AppDelegate.m +++ b/XCDYouTubeKit Demo/iOS Demo/AppDelegate.m @@ -9,6 +9,7 @@ #import #import "ContextLogFormatter.h" +#import "XCDYouTubeKit_iOS_Demo-Swift.h" @implementation AppDelegate @@ -19,9 +20,6 @@ - (instancetype) init if (!(self = [super init])) return nil; - _playerEventLogger = [PlayerEventLogger new]; - _nowPlayingInfoCenterProvider = [NowPlayingInfoCenterProvider new]; - return self; } @@ -80,4 +78,13 @@ - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions: return YES; } + +- (void)applicationDidEnterBackground:(UIApplication *)application { + [[AVPlayerViewControllerManager shared]disconnectPlayer]; +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + [[AVPlayerViewControllerManager shared]reconnectPlayerWithRootViewController:self.window.rootViewController]; +} + @end diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoAsynchronousViewController.m b/XCDYouTubeKit Demo/iOS Demo/DemoAsynchronousViewController.m index e4f15ba2..405b0f06 100644 --- a/XCDYouTubeKit Demo/iOS Demo/DemoAsynchronousViewController.m +++ b/XCDYouTubeKit Demo/iOS Demo/DemoAsynchronousViewController.m @@ -50,8 +50,8 @@ - (void) displayVideoIdentifier:(NSString *)videoIdentifier return; } - AVPlayerViewController *playerViewController = [AVPlayerViewController new]; - playerViewController.player = [AVPlayer playerWithURL:video.streamURL]; + [AVPlayerViewControllerManager shared].video = video; + AVPlayerViewController *playerViewController = [AVPlayerViewControllerManager shared].controller; [self presentViewController:playerViewController animated:YES completion:nil]; [playerViewController.player play]; diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift b/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift index c2a6a459..0a42b55b 100644 --- a/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift +++ b/XCDYouTubeKit Demo/iOS Demo/DemoFullScreenViewController.swift @@ -32,7 +32,9 @@ class DemoFullScreenViewController: UIViewController { @IBOutlet weak open var lowQualitySwitch: UISwitch! @IBOutlet weak open var videoIdentifierTextField: UITextField! - + var ob: NSKeyValueObservation? + private var timeObserverToken: Any? + override func viewDidLoad() { super.viewDidLoad() self.videoIdentifierTextField.text = UserDefaults.standard.string(forKey: "VideoIdentifier") @@ -42,20 +44,17 @@ class DemoFullScreenViewController: UIViewController { self.view.endEditing(true) } - @IBAction open func play(_ sender: Any!) { - let playerViewController = AVPlayerViewController() - self.present(playerViewController, animated: true, completion: nil) - + @IBAction open func play(_ sender: Any!) { XCDYouTubeClient.default().getVideoWithIdentifier(self.videoIdentifierTextField.text) { (video, error) in guard error == nil else { Utilities.shared.displayError(error! as NSError, originViewController: self) return } - - let streamURL = self.lowQualitySwitch.isOn ? video?.streamURLs[XCDYouTubeVideoQualityHTTPLiveStreaming] ?? video?.streamURLs[XCDYouTubeVideoQuality.medium360.rawValue] ?? video?.streamURLs[XCDYouTubeVideoQuality.small240.rawValue] : video?.streamURL - - playerViewController.player = AVPlayer(url: streamURL!) - playerViewController.player?.play() + AVPlayerViewControllerManager.shared.lowQualityMode = self.lowQualitySwitch.isOn + AVPlayerViewControllerManager.shared.video = video + self.present(AVPlayerViewControllerManager.shared.controller, animated: true) { + AVPlayerViewControllerManager.shared.controller.player?.play() + } } - } + } } diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.m b/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.m index c5d1e2fc..df910489 100644 --- a/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.m +++ b/XCDYouTubeKit Demo/iOS Demo/DemoInlineViewController.m @@ -12,23 +12,28 @@ @implementation DemoInlineViewController + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [[AVPlayerViewControllerManager shared].controller.player pause]; +} - (IBAction) load:(id)sender { NSString *videoIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:@"VideoIdentifier"]; - - AVPlayerViewController *playerViewController = [AVPlayerViewController new]; - playerViewController.view.frame = self.videoContainerView.bounds; - [self addChildViewController:playerViewController]; - [self.videoContainerView addSubview:playerViewController.view]; - [playerViewController didMoveToParentViewController:self]; - - __weak AVPlayerViewController *weakPlayerViewController = playerViewController; + [[XCDYouTubeClient defaultClient] getVideoWithIdentifier:videoIdentifier completionHandler:^(XCDYouTubeVideo * _Nullable video, NSError * _Nullable error) { if (video) { - weakPlayerViewController.player = [AVPlayer playerWithURL:video.streamURL]; + [AVPlayerViewControllerManager shared].video = video; + AVPlayerViewController *playerViewController = [AVPlayerViewControllerManager shared].controller; + playerViewController.view.frame = self.videoContainerView.bounds; + [self addChildViewController:playerViewController]; + [self.videoContainerView addSubview:playerViewController.view]; + [playerViewController didMoveToParentViewController:self]; + if (self.shouldAutoplaySwitch.on) - [weakPlayerViewController.player play]; + [playerViewController.player play]; } else { diff --git a/XCDYouTubeKit Demo/iOS Demo/DemoThumbnailViewController.m b/XCDYouTubeKit Demo/iOS Demo/DemoThumbnailViewController.m index 3602bf4e..de66659e 100644 --- a/XCDYouTubeKit Demo/iOS Demo/DemoThumbnailViewController.m +++ b/XCDYouTubeKit Demo/iOS Demo/DemoThumbnailViewController.m @@ -36,8 +36,8 @@ - (IBAction) loadThumbnail:(id)sender - (IBAction) play:(id)sender { - AVPlayerViewController *playerViewController = [AVPlayerViewController new]; - playerViewController.player = [AVPlayer playerWithURL:self.video.streamURL]; + [AVPlayerViewControllerManager shared].video = self.video; + AVPlayerViewController *playerViewController = [AVPlayerViewControllerManager shared].controller; [self presentViewController:playerViewController animated:YES completion:nil]; [playerViewController.player play]; } diff --git a/XCDYouTubeKit Demo/iOS Demo/MPMoviePlayerController+BackgroundPlayback.h b/XCDYouTubeKit Demo/iOS Demo/MPMoviePlayerController+BackgroundPlayback.h deleted file mode 100644 index 30c6b242..00000000 --- a/XCDYouTubeKit Demo/iOS Demo/MPMoviePlayerController+BackgroundPlayback.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. -// - -@import MediaPlayer; - -@interface MPMoviePlayerController (BackgroundPlayback) - -/** - * When enabling background playback: - * - The `UIBackgroundModes` array (Required background modes) in the application Info.plist file must contain the `audio` element (App plays audio or streams audio/video using AirPlay). - * - The audio session category must be set to `AVAudioSessionCategoryPlayback`. - * - * @discussion On iOS < 7, the `backgroundPlaybackEnabled` property does nothing. Instead, you must set the `PlayVideoInBackground` boolean user default used by the MediaPlayer framework. The `PlayVideoInBackground` user default must be set before a `MPMoviePlayerController` object is created. - */ -@property (nonatomic, assign, getter = isBackgroundPlaybackEnabled) BOOL backgroundPlaybackEnabled; - -@end diff --git a/XCDYouTubeKit Demo/iOS Demo/MPMoviePlayerController+BackgroundPlayback.m b/XCDYouTubeKit Demo/iOS Demo/MPMoviePlayerController+BackgroundPlayback.m deleted file mode 100644 index 510d0fa3..00000000 --- a/XCDYouTubeKit Demo/iOS Demo/MPMoviePlayerController+BackgroundPlayback.m +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. -// - -#import "MPMoviePlayerController+BackgroundPlayback.h" - -@import AVFoundation; -@import ObjectiveC; - -#ifndef NSFoundationVersionNumber_iOS_7_0 -#define NSFoundationVersionNumber_iOS_7_0 1047.2 -#endif - -@implementation MPMoviePlayerController (BackgroundPlayback) - -+ (void) load -{ - // On iOS 7, working with playerLayer.player as documented in Technical Q&A QA1668 works fine. - // On iOS 5 and 6, setting playerLayer.player to nil is not enough for background playback when locking the device, the `PlayVideoInBackground` user default must be used instead. - if (NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_7_0) - return; - - dispatch_async(dispatch_get_main_queue(), ^{ - // Register for these notifications as early as possible in order to be called before -[MPAVController _applicationWillResignActive:] which calls `_pausePlaybackIfNecessary`. - NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; - [defaultCenter addObserver:self selector:@selector(backgroundPlayback_moviePlayerNowPlayingMovieDidChange:) name:MPMoviePlayerNowPlayingMovieDidChangeNotification object:nil]; - [defaultCenter addObserver:self selector:@selector(backgroundPlayback_moviePlayerPlaybackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil]; - [defaultCenter addObserver:self selector:@selector(backgroundPlayback_applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; - [defaultCenter addObserver:self selector:@selector(backgroundPlayback_applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; - }); -} - -static const void * const BackgroundPlaybackEnabledKey = &BackgroundPlaybackEnabledKey; - -- (BOOL) isBackgroundPlaybackEnabled -{ - return [objc_getAssociatedObject(self, BackgroundPlaybackEnabledKey) boolValue]; -} - -- (void) setBackgroundPlaybackEnabled:(BOOL)backgroundPlaybackEnabled -{ - if (backgroundPlaybackEnabled) - { - NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"]; - if (!backgroundModes || ([backgroundModes isKindOfClass:[NSArray class]] && ![backgroundModes containsObject:@"audio"])) - NSLog(@"ERROR: The `UIBackgroundModes` array in the application Info.plist file must contain the `audio` element for background playback."); - } - - objc_setAssociatedObject(self, BackgroundPlaybackEnabledKey, @(backgroundPlaybackEnabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -static __weak MPMoviePlayerController *currentMoviePlayerController; - -+ (void) backgroundPlayback_moviePlayerNowPlayingMovieDidChange:(NSNotification *)notification -{ - currentMoviePlayerController = notification.object; -} - -+ (void) backgroundPlayback_moviePlayerPlaybackDidFinish:(NSNotification *)notification -{ - currentMoviePlayerController = nil; -} - -__attribute__((overloadable)) -static AVPlayerLayer * PlayerLayer(void) -{ - // When an inline movie player controller goes fullscreen, its view is somehow transferred to the key window. - return PlayerLayer(currentMoviePlayerController.view) ?: PlayerLayer([[UIApplication sharedApplication] keyWindow]); -} - -// Since MPMoviePlayerController doesn't expose its AVFoundation internals, traversing its subviews is the least worst solution to access its AVPlayerLayer. -// See Technical Q&A QA1668 - Playing media while in the background using AV Foundation on iOS https://developer.apple.com/library/ios/qa/qa1668/_index.html -__attribute__((overloadable)) -static AVPlayerLayer * PlayerLayer(UIView *view) -{ - AVPlayerLayer *playerLayer = nil; - if ([view.layer isKindOfClass:[AVPlayerLayer class]]) - { - playerLayer = (AVPlayerLayer *)view.layer; - } - else - { - for (UIView *subview in view.subviews) - { - playerLayer = PlayerLayer(subview); - if (playerLayer) - break; - } - } - return playerLayer; -} - -static const void * const PlayerKey = &PlayerKey; - -+ (void) backgroundPlayback_applicationWillResignActive:(NSNotification *)notification -{ - if (!currentMoviePlayerController) - return; - - AVPlayerLayer *playerLayer = PlayerLayer(); - objc_setAssociatedObject(currentMoviePlayerController, PlayerKey, playerLayer.player, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - if (currentMoviePlayerController.isBackgroundPlaybackEnabled) - { - if (![[[AVAudioSession sharedInstance] category] isEqualToString:AVAudioSessionCategoryPlayback]) - NSLog(@"ERROR: The audio session category must be `AVAudioSessionCategoryPlayback` when background playback is enabled."); - - playerLayer.player = nil; - } -} - -+ (void) backgroundPlayback_applicationDidBecomeActive:(NSNotification *)notification -{ - AVPlayerLayer *playerLayer = PlayerLayer(); - AVPlayer *player = objc_getAssociatedObject(currentMoviePlayerController, PlayerKey); - if (player) - playerLayer.player = player; -} - -@end diff --git a/XCDYouTubeKit Demo/iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h b/XCDYouTubeKit Demo/iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h index 5f00c933..76d1cafe 100644 --- a/XCDYouTubeKit Demo/iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h +++ b/XCDYouTubeKit Demo/iOS Demo/XCDYouTubeKit iOS Demo-Bridging-Header.h @@ -3,3 +3,4 @@ // #import "VideoPickerController.h" +#import From 400c51b77c5171571319ddb8c450b6f5a13ebecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sone=C3=A9=20John?= Date: Thu, 7 Nov 2019 11:19:08 -0400 Subject: [PATCH 08/22] Handle playback interruptions --- .../AVPlayerViewControllerManager.swift | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift b/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift index b7cb8b26..041a24a1 100644 --- a/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift +++ b/XCDYouTubeKit Demo/iOS Demo/AVPlayerViewControllerManager.swift @@ -85,12 +85,35 @@ extension UIView { } public lazy var controller: AVPlayerViewController = { - let controller = AVPlayerViewController() - if #available(iOS 10.0, *) { - controller.updatesNowPlayingInfoCenter = false - } - return controller - }() + let controller = AVPlayerViewController() + if #available(iOS 10.0, *) { + controller.updatesNowPlayingInfoCenter = false + } + return controller + }() + + override init() { + super.init() + + 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 + } + + if type == .began { + self.player?.pause() + } else if type == .ended { + 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 } + self.player?.play() + } + } + } public func disconnectPlayer() { self.controller.player = nil From d81df4eebac87e17ea01290a84cdec2aba2038ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sone=C3=A9=20John?= Date: Thu, 7 Nov 2019 11:19:54 -0400 Subject: [PATCH 09/22] =?UTF-8?q?Remove=20background=20toggle=20since=20it?= =?UTF-8?q?=E2=80=99s=20no=20longer=20valid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doesn’t work with AVPlayerViewController --- .../iOS Demo/SettingsViewController.m | 20 +------- .../en.lproj/MainStoryboard.storyboard | 46 +++++-------------- 2 files changed, 14 insertions(+), 52 deletions(-) diff --git a/XCDYouTubeKit Demo/iOS Demo/SettingsViewController.m b/XCDYouTubeKit Demo/iOS Demo/SettingsViewController.m index 039d4c76..a63f2d58 100644 --- a/XCDYouTubeKit Demo/iOS Demo/SettingsViewController.m +++ b/XCDYouTubeKit Demo/iOS Demo/SettingsViewController.m @@ -8,7 +8,6 @@ @interface SettingsViewController () -@property (nonatomic, weak) IBOutlet UISwitch *playVideoInBackgroundSwitch; @property (nonatomic, weak) IBOutlet UILabel *audioSessionCategoryLabel; @property (nonatomic, weak) IBOutlet UILabel *versionLabel; @@ -22,30 +21,15 @@ - (void) viewDidLoad if (@available(iOS 13.0, *)) { self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight; - } - self.playVideoInBackgroundSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"PlayVideoInBackground"]; - self.audioSessionCategoryLabel.text = [[AVAudioSession sharedInstance] category]; + } + self.audioSessionCategoryLabel.text = [[AVAudioSession sharedInstance] category]; NSBundle *bundle = [NSBundle bundleWithIdentifier:@"ch.pitaya.xcdyoutubekit"]; self.versionLabel.text = [NSString stringWithFormat:@"Version %@ (%@)", [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"], [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]]; } #pragma mark - Actions -- (IBAction) togglePlayVideoInBackground:(UISwitch *)sender -{ - /** - * `PlayVideoInBackground` is a user default used by the MediaPlayer framework which controls whether a `MPMoviePlayerController` continues playing videos while in the background. - * - * In addition to the `PlayVideoInBackground` user default, background playback requires: - * - The `UIBackgroundModes` array (Required background modes) in the application Info.plist file must contain the `audio` element (App plays audio or streams audio/video using AirPlay). - * - The audio session category must be set to `AVAudioSessionCategoryPlayback`. - * - * On iOS 7, changing the `PlayVideoInBackground` user default has no effect. See MPMoviePlayerController+BackgroundPlayback for a solution. - */ - [[NSUserDefaults standardUserDefaults] setBool:self.playVideoInBackgroundSwitch.on forKey:@"PlayVideoInBackground"]; -} - - (IBAction) selectAudioSessionCategory:(UIButton *)sender { UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"Audio Session Category", nil) delegate:self cancelButtonTitle:NSLocalizedString(@"Cancel", nil) destructiveButtonTitle:nil otherButtonTitles:NSLocalizedString(@"Solo Ambient", nil), NSLocalizedString(@"Playback", nil), nil]; diff --git a/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard b/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard index 4be0463e..c31579cd 100644 --- a/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard +++ b/XCDYouTubeKit Demo/iOS Demo/en.lproj/MainStoryboard.storyboard @@ -1,9 +1,9 @@ - + - + @@ -36,29 +36,8 @@ - - - - - - - - - - - - - - - - +