diff --git a/.swift-version b/.swift-version index 389f774..8a36cd1 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.0 \ No newline at end of file +4.1 \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..b75511d --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,13 @@ +included: + - Sources + - Project + +excluded: + - Carthage + - Project/Tests + +disabled_rules: + - file_length + - type_body_length + +function_body_length: 60 diff --git a/.travis.yml b/.travis.yml index f1207c3..d4fd2f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,31 @@ language: objective-c -osx_image: xcode9.1 +osx_image: xcode9.4 +xcode_workspace: Player.xcworkspace env: global: - LC_CTYPE=en_US.UTF-8 - LANG=en_US.UTF-8 - - IOS_SDK=iphonesimulator11.1 - - TVOS_SDK=appletvsimulator11.1 -script: - - xcodebuild -showsdks - - xcodebuild -project Project/Player.xcodeproj -scheme 'Debug - iOS' -sdk $IOS_SDK build analyze -# - xcodebuild -project Project/Player.xcodeproj -scheme 'Debug - tvOS' -sdk $TVOS_SDK build analyze + - CONFIG=Release + - XCCONFIG=Project/Shared/Travis.xcconfig +matrix: + include: + - env: + ACTION=test:macos + SDK=macosx10.13 + install: + - gem install xcpretty + - env: + ACTION=test:ios + SDK=iphonesimulator11.4 + install: + - gem install xcpretty + - env: + ACTION=test:tvos + SDK=appletvsimulator11.4 + install: + - gem install xcpretty + - env: ACTION=build:carthage + - env: ACTION=build:pod + - env: ACTION=swiftlint + fast_finish: true +script: rake "$ACTION" diff --git a/Package.swift b/Package.swift deleted file mode 100644 index f3c425e..0000000 --- a/Package.swift +++ /dev/null @@ -1,5 +0,0 @@ -import PackageDescription - -let package = Package( - name: "Player" -) diff --git a/Player.gif b/Player.gif deleted file mode 100644 index 4e7f837..0000000 Binary files a/Player.gif and /dev/null differ diff --git a/Player.podspec b/Player.podspec index 2a4fa22..ee78284 100644 --- a/Player.podspec +++ b/Player.podspec @@ -1,16 +1,17 @@ Pod::Spec.new do |s| s.name = 'Player' - s.version = '0.8.4' + s.version = '0.9' s.license = 'MIT' - s.summary = 'video player in Swift, simple way to play and stream media in your iOS or tvOS app' + s.summary = '▶️ A Swift Video Player: A simple way to play and stream media on iOS/tvOS/macOS' s.homepage = 'https://github.com/piemonte/player' - s.social_media_url = 'http://twitter.com/piemonte' - s.authors = { 'patrick piemonte' => "piemonte@alumni.cmu.edu" } + s.social_media_url = 'https://twitter.com/piemonte' + s.authors = { 'patrick piemonte' => "piemonte@alumni.cmu.edu", 'chris zielinski' => "chrisz@berkeley.edu" } s.source = { :git => 'https://github.com/piemonte/player.git', :tag => s.version } s.ios.deployment_target = '9.0' s.tvos.deployment_target = '9.0' + s.osx.deployment_target = '10.10' s.source_files = 'Sources/*.swift' s.requires_arc = true - s.swift_version = '4.0' + s.swift_version = '4.1' # s.screenshot = "https://raw.github.com/piemonte/Player/master/Player.gif" end diff --git a/Player.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Player.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Player.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Player.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Player.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/Player.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/Project/Player.xcodeproj/project.pbxproj b/Project/Player.xcodeproj/project.pbxproj index 5b21329..d91825a 100644 --- a/Project/Player.xcodeproj/project.pbxproj +++ b/Project/Player.xcodeproj/project.pbxproj @@ -10,35 +10,151 @@ 04B4E6401C1E8A7B00E20778 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B4E63F1C1E8A7B00E20778 /* AppDelegate.swift */; }; 04B4E6421C1E8A7B00E20778 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B4E6411C1E8A7B00E20778 /* ViewController.swift */; }; 04B4E6471C1E8A7C00E20778 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 04B4E6461C1E8A7C00E20778 /* Assets.xcassets */; }; - 04D702CF1C1E8C8400879C91 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065A0FC51A25C94B005BA7BC /* Player.swift */; }; - 0638A5351E01BBF8009EE18B /* Player.h in Headers */ = {isa = PBXBuildFile; fileRef = 0638A5331E01BBF8009EE18B /* Player.h */; settings = {ATTRIBUTES = (Public, ); }; }; 065A0FA31A25C8E2005BA7BC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065A0FA21A25C8E2005BA7BC /* AppDelegate.swift */; }; 065A0FA51A25C8E2005BA7BC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065A0FA41A25C8E2005BA7BC /* ViewController.swift */; }; 065A0FAA1A25C8E2005BA7BC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 065A0FA91A25C8E2005BA7BC /* Assets.xcassets */; }; 065A0FAD1A25C8E2005BA7BC /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 065A0FAB1A25C8E2005BA7BC /* LaunchScreen.xib */; }; - 065A0FC61A25C94B005BA7BC /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065A0FC51A25C94B005BA7BC /* Player.swift */; }; - 06C0905F1E038F250038CA25 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065A0FC51A25C94B005BA7BC /* Player.swift */; }; + 14F9E1F620DD5259008D7CB2 /* MacPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14F9E1F520DD5259008D7CB2 /* MacPlayerTests.swift */; }; + 14F9E20120DD5A3C008D7CB2 /* test.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 14F9E20020DD5A3C008D7CB2 /* test.mp4 */; }; + 14F9E20920DD938D008D7CB2 /* iOSPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14F9E20820DD938D008D7CB2 /* iOSPlayerTests.swift */; }; + 14F9E21020DD949F008D7CB2 /* test.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 14F9E20020DD5A3C008D7CB2 /* test.mp4 */; }; + 14F9E21820DDA8B5008D7CB2 /* tvOSPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14F9E21720DDA8B5008D7CB2 /* tvOSPlayerTests.swift */; }; + 14F9E21F20DDA9D2008D7CB2 /* test.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 14F9E20020DD5A3C008D7CB2 /* test.mp4 */; }; + 848DE8D920D86AEB000807C4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848DE8D820D86AEB000807C4 /* AppDelegate.swift */; }; + 848DE8DB20D86AEF000807C4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 848DE8DA20D86AEF000807C4 /* Assets.xcassets */; }; + 848DE8E520D86C1E000807C4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848DE8E420D86C1E000807C4 /* ViewController.swift */; }; + 848DE8E720D87E01000807C4 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848DE8DC20D86AEF000807C4 /* MainMenu.xib */; }; + 848DE8EF20D8C7FB000807C4 /* Player.h in Headers */ = {isa = PBXBuildFile; fileRef = 848DE8EB20D8C7AC000807C4 /* Player.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 848DE8F120D9550A000807C4 /* Player.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0638A5311E01BBF8009EE18B /* Player.framework */; }; + 848DE8F220D9550A000807C4 /* Player.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0638A5311E01BBF8009EE18B /* Player.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 848DE90020D96741000807C4 /* Player.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0638A5311E01BBF8009EE18B /* Player.framework */; }; + 848DE90120D96741000807C4 /* Player.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0638A5311E01BBF8009EE18B /* Player.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 848DE90520D96923000807C4 /* Player.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0638A5311E01BBF8009EE18B /* Player.framework */; }; + 848DE90620D96923000807C4 /* Player.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0638A5311E01BBF8009EE18B /* Player.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 8491C2FF20DC1A570057A7C4 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8491C2FE20DC1A570057A7C4 /* Player.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 14F9E1F820DD5259008D7CB2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 065A0F951A25C8E2005BA7BC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 848DE8D520D86AEB000807C4; + remoteInfo = Player_macOS; + }; + 14F9E20B20DD938D008D7CB2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 065A0F951A25C8E2005BA7BC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 065A0F9C1A25C8E2005BA7BC; + remoteInfo = "Player iOS"; + }; + 14F9E21A20DDA8B5008D7CB2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 065A0F951A25C8E2005BA7BC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 04B4E63C1C1E8A7B00E20778; + remoteInfo = "Player tvOS"; + }; + 848DE8F320D9550A000807C4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 065A0F951A25C8E2005BA7BC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0638A5301E01BBF8009EE18B; + remoteInfo = Player; + }; + 848DE90220D96741000807C4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 065A0F951A25C8E2005BA7BC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0638A5301E01BBF8009EE18B; + remoteInfo = Player; + }; + 848DE90720D96923000807C4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 065A0F951A25C8E2005BA7BC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0638A5301E01BBF8009EE18B; + remoteInfo = Player; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 848DE8F520D9550B000807C4 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 848DE8F220D9550A000807C4 /* Player.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 848DE90420D96741000807C4 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 848DE90120D96741000807C4 /* Player.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 848DE90920D96923000807C4 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 848DE90620D96923000807C4 /* Player.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - 04B4E63D1C1E8A7B00E20778 /* Player_tv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Player_tv.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 04B4E63D1C1E8A7B00E20778 /* Player tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Player tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 04B4E63F1C1E8A7B00E20778 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 04B4E6411C1E8A7B00E20778 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 04B4E6461C1E8A7C00E20778 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 04B4E6481C1E8A7C00E20778 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0638A5311E01BBF8009EE18B /* Player.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Player.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 0638A5331E01BBF8009EE18B /* Player.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Player.h; path = ../../Sources/Player.h; sourceTree = ""; }; - 065677291A26C59B0038C208 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = SOURCE_ROOT; }; - 0656772A1A26C59B0038C208 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = SOURCE_ROOT; }; - 065A0F9D1A25C8E2005BA7BC /* Player_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Player_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 065A0F9D1A25C8E2005BA7BC /* Player iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Player iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 065A0FA11A25C8E2005BA7BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 065A0FA21A25C8E2005BA7BC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 065A0FA41A25C8E2005BA7BC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 065A0FA91A25C8E2005BA7BC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 065A0FAC1A25C8E2005BA7BC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; - 065A0FC51A25C94B005BA7BC /* Player.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Player.swift; path = ../Sources/Player.swift; sourceTree = ""; }; - 06C6A5891A89E952009DC6EA /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = SOURCE_ROOT; }; - 06DE65951E182C5A00C064DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Framework/Info.plist; sourceTree = SOURCE_ROOT; }; + 14F9E1F320DD5259008D7CB2 /* PlayerTests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "PlayerTests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 14F9E1F520DD5259008D7CB2 /* MacPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPlayerTests.swift; sourceTree = ""; }; + 14F9E1F720DD5259008D7CB2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 14F9E1FD20DD553B008D7CB2 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; name = .swiftlint.yml; path = ../.swiftlint.yml; sourceTree = ""; }; + 14F9E20020DD5A3C008D7CB2 /* test.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test.mp4; sourceTree = ""; }; + 14F9E20620DD938D008D7CB2 /* PlayerTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "PlayerTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 14F9E20820DD938D008D7CB2 /* iOSPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSPlayerTests.swift; sourceTree = ""; }; + 14F9E20A20DD938D008D7CB2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 14F9E21520DDA8B5008D7CB2 /* PlayerTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "PlayerTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 14F9E21720DDA8B5008D7CB2 /* tvOSPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSPlayerTests.swift; sourceTree = ""; }; + 14F9E21920DDA8B5008D7CB2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 848DE8D620D86AEB000807C4 /* Player macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Player macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 848DE8D820D86AEB000807C4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = ../PlayerMac/AppDelegate.swift; sourceTree = ""; }; + 848DE8DA20D86AEF000807C4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../PlayerMac/Assets.xcassets; sourceTree = ""; }; + 848DE8DD20D86AEF000807C4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 848DE8DF20D86AEF000807C4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../PlayerMac/Info.plist; sourceTree = ""; }; + 848DE8E420D86C1E000807C4 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = PlayerMac/ViewController.swift; sourceTree = SOURCE_ROOT; }; + 848DE8E820D881E6000807C4 /* Player.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Player.entitlements; path = ../PlayerMac/Player.entitlements; sourceTree = ""; }; + 848DE8EA20D8C7AC000807C4 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 848DE8EB20D8C7AC000807C4 /* Player.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Player.h; sourceTree = ""; }; + 848DE8EC20D8C7AC000807C4 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 848DE8ED20D8C7AC000807C4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 848DE8EE20D8C7AC000807C4 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; + 8491C2FE20DC1A570057A7C4 /* Player.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = ""; }; + 8491C30020DC21E50057A7C4 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; name = .travis.yml; path = ../.travis.yml; sourceTree = ""; }; + 8491C30120DC220B0057A7C4 /* Rakefile */ = {isa = PBXFileReference; lastKnownFileType = text; name = Rakefile; path = ../Rakefile; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 8491C31120DC486C0057A7C4 /* Travis.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Travis.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -46,6 +162,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 848DE90020D96741000807C4 /* Player.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -60,6 +177,36 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 848DE90520D96923000807C4 /* Player.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14F9E1F020DD5259008D7CB2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14F9E20320DD938D008D7CB2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14F9E21220DDA8B5008D7CB2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 848DE8D320D86AEB000807C4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 848DE8F120D9550A000807C4 /* Player.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -86,33 +233,18 @@ name = "Supporting Files"; sourceTree = ""; }; - 0638A5321E01BBF8009EE18B /* Player */ = { - isa = PBXGroup; - children = ( - 0638A5331E01BBF8009EE18B /* Player.h */, - 06DE65951E182C5A00C064DE /* Info.plist */, - ); - path = Player; - sourceTree = ""; - }; - 0638A5391E01C4DB009EE18B /* Common */ = { - isa = PBXGroup; - children = ( - 065677291A26C59B0038C208 /* Base.xcconfig */, - 0656772A1A26C59B0038C208 /* Debug.xcconfig */, - 06C6A5891A89E952009DC6EA /* Release.xcconfig */, - ); - name = Common; - sourceTree = ""; - }; 065A0F941A25C8E2005BA7BC = { isa = PBXGroup; children = ( - 065A0FC21A25C917005BA7BC /* Player */, - 0638A5391E01C4DB009EE18B /* Common */, + 14F9E1FD20DD553B008D7CB2 /* .swiftlint.yml */, + 8491C30120DC220B0057A7C4 /* Rakefile */, + 8491C30020DC21E50057A7C4 /* .travis.yml */, + 8491C2FD20DC1A570057A7C4 /* Sources */, 065A0F9F1A25C8E2005BA7BC /* iOS */, + 848DE8D720D86AEB000807C4 /* macOS */, 04B4E63E1C1E8A7B00E20778 /* tvOS */, - 0638A5321E01BBF8009EE18B /* Player */, + 848DE8E920D8C7AC000807C4 /* Shared */, + 14F9E1FF20DD574D008D7CB2 /* Tests */, 065A0F9E1A25C8E2005BA7BC /* Products */, ); sourceTree = ""; @@ -120,9 +252,13 @@ 065A0F9E1A25C8E2005BA7BC /* Products */ = { isa = PBXGroup; children = ( - 065A0F9D1A25C8E2005BA7BC /* Player_iOS.app */, - 04B4E63D1C1E8A7B00E20778 /* Player_tv.app */, + 065A0F9D1A25C8E2005BA7BC /* Player iOS.app */, + 04B4E63D1C1E8A7B00E20778 /* Player tvOS.app */, 0638A5311E01BBF8009EE18B /* Player.framework */, + 848DE8D620D86AEB000807C4 /* Player macOS.app */, + 14F9E1F320DD5259008D7CB2 /* PlayerTests macOS.xctest */, + 14F9E20620DD938D008D7CB2 /* PlayerTests iOS.xctest */, + 14F9E21520DDA8B5008D7CB2 /* PlayerTests tvOS.xctest */, ); name = Products; sourceTree = ""; @@ -148,12 +284,78 @@ name = "Supporting Files"; sourceTree = ""; }; - 065A0FC21A25C917005BA7BC /* Player */ = { + 14F9E1F420DD5259008D7CB2 /* macOS */ = { isa = PBXGroup; children = ( - 065A0FC51A25C94B005BA7BC /* Player.swift */, + 14F9E1F520DD5259008D7CB2 /* MacPlayerTests.swift */, + 14F9E1F720DD5259008D7CB2 /* Info.plist */, ); - name = Player; + path = macOS; + sourceTree = ""; + }; + 14F9E1FF20DD574D008D7CB2 /* Tests */ = { + isa = PBXGroup; + children = ( + 14F9E21620DDA8B5008D7CB2 /* tvOS */, + 14F9E20720DD938D008D7CB2 /* iOS */, + 14F9E1F420DD5259008D7CB2 /* macOS */, + ); + path = Tests; + sourceTree = ""; + }; + 14F9E20720DD938D008D7CB2 /* iOS */ = { + isa = PBXGroup; + children = ( + 14F9E20820DD938D008D7CB2 /* iOSPlayerTests.swift */, + 14F9E20A20DD938D008D7CB2 /* Info.plist */, + ); + path = iOS; + sourceTree = ""; + }; + 14F9E21620DDA8B5008D7CB2 /* tvOS */ = { + isa = PBXGroup; + children = ( + 14F9E21720DDA8B5008D7CB2 /* tvOSPlayerTests.swift */, + 14F9E21920DDA8B5008D7CB2 /* Info.plist */, + ); + path = tvOS; + sourceTree = ""; + }; + 848DE8D720D86AEB000807C4 /* macOS */ = { + isa = PBXGroup; + children = ( + 848DE8D820D86AEB000807C4 /* AppDelegate.swift */, + 848DE8DA20D86AEF000807C4 /* Assets.xcassets */, + 848DE8DF20D86AEF000807C4 /* Info.plist */, + 848DE8DC20D86AEF000807C4 /* MainMenu.xib */, + 848DE8E820D881E6000807C4 /* Player.entitlements */, + 848DE8E420D86C1E000807C4 /* ViewController.swift */, + ); + name = macOS; + path = PlayerMac; + sourceTree = ""; + }; + 848DE8E920D8C7AC000807C4 /* Shared */ = { + isa = PBXGroup; + children = ( + 14F9E20020DD5A3C008D7CB2 /* test.mp4 */, + 8491C31120DC486C0057A7C4 /* Travis.xcconfig */, + 848DE8EA20D8C7AC000807C4 /* Debug.xcconfig */, + 848DE8EB20D8C7AC000807C4 /* Player.h */, + 848DE8EC20D8C7AC000807C4 /* Release.xcconfig */, + 848DE8ED20D8C7AC000807C4 /* Info.plist */, + 848DE8EE20D8C7AC000807C4 /* Base.xcconfig */, + ); + path = Shared; + sourceTree = ""; + }; + 8491C2FD20DC1A570057A7C4 /* Sources */ = { + isa = PBXGroup; + children = ( + 8491C2FE20DC1A570057A7C4 /* Player.swift */, + ); + name = Sources; + path = ../Sources; sourceTree = ""; }; /* End PBXGroup section */ @@ -163,28 +365,30 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 0638A5351E01BBF8009EE18B /* Player.h in Headers */, + 848DE8EF20D8C7FB000807C4 /* Player.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - 04B4E63C1C1E8A7B00E20778 /* Player_tv */ = { + 04B4E63C1C1E8A7B00E20778 /* Player tvOS */ = { isa = PBXNativeTarget; - buildConfigurationList = 04B4E64B1C1E8A7C00E20778 /* Build configuration list for PBXNativeTarget "Player_tv" */; + buildConfigurationList = 04B4E64B1C1E8A7C00E20778 /* Build configuration list for PBXNativeTarget "Player tvOS" */; buildPhases = ( 04B4E6391C1E8A7B00E20778 /* Sources */, 04B4E63A1C1E8A7B00E20778 /* Frameworks */, 04B4E63B1C1E8A7B00E20778 /* Resources */, + 848DE90420D96741000807C4 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + 848DE90320D96741000807C4 /* PBXTargetDependency */, ); - name = Player_tv; + name = "Player tvOS"; productName = PlayerTV; - productReference = 04B4E63D1C1E8A7B00E20778 /* Player_tv.app */; + productReference = 04B4E63D1C1E8A7B00E20778 /* Player tvOS.app */; productType = "com.apple.product-type.application"; }; 0638A5301E01BBF8009EE18B /* Player */ = { @@ -195,6 +399,7 @@ 0638A52D1E01BBF8009EE18B /* Frameworks */, 0638A52E1E01BBF8009EE18B /* Headers */, 0638A52F1E01BBF8009EE18B /* Resources */, + 84F4B33720DA31140007AE86 /* Run SwiftLint */, ); buildRules = ( ); @@ -205,21 +410,96 @@ productReference = 0638A5311E01BBF8009EE18B /* Player.framework */; productType = "com.apple.product-type.framework"; }; - 065A0F9C1A25C8E2005BA7BC /* Player_iOS */ = { + 065A0F9C1A25C8E2005BA7BC /* Player iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = 065A0FBC1A25C8E2005BA7BC /* Build configuration list for PBXNativeTarget "Player_iOS" */; + buildConfigurationList = 065A0FBC1A25C8E2005BA7BC /* Build configuration list for PBXNativeTarget "Player iOS" */; buildPhases = ( 065A0F991A25C8E2005BA7BC /* Sources */, 065A0F9A1A25C8E2005BA7BC /* Frameworks */, 065A0F9B1A25C8E2005BA7BC /* Resources */, + 848DE90920D96923000807C4 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + 848DE90820D96923000807C4 /* PBXTargetDependency */, ); - name = Player_iOS; + name = "Player iOS"; productName = Player; - productReference = 065A0F9D1A25C8E2005BA7BC /* Player_iOS.app */; + productReference = 065A0F9D1A25C8E2005BA7BC /* Player iOS.app */; + productType = "com.apple.product-type.application"; + }; + 14F9E1F220DD5259008D7CB2 /* PlayerTests macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 14F9E1FC20DD5259008D7CB2 /* Build configuration list for PBXNativeTarget "PlayerTests macOS" */; + buildPhases = ( + 14F9E1EF20DD5259008D7CB2 /* Sources */, + 14F9E1F020DD5259008D7CB2 /* Frameworks */, + 14F9E1F120DD5259008D7CB2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 14F9E1F920DD5259008D7CB2 /* PBXTargetDependency */, + ); + name = "PlayerTests macOS"; + productName = PlayerTests_macOS; + productReference = 14F9E1F320DD5259008D7CB2 /* PlayerTests macOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 14F9E20520DD938D008D7CB2 /* PlayerTests iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 14F9E20D20DD938D008D7CB2 /* Build configuration list for PBXNativeTarget "PlayerTests iOS" */; + buildPhases = ( + 14F9E20220DD938D008D7CB2 /* Sources */, + 14F9E20320DD938D008D7CB2 /* Frameworks */, + 14F9E20420DD938D008D7CB2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 14F9E20C20DD938D008D7CB2 /* PBXTargetDependency */, + ); + name = "PlayerTests iOS"; + productName = "PlayerTests iOS"; + productReference = 14F9E20620DD938D008D7CB2 /* PlayerTests iOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 14F9E21420DDA8B5008D7CB2 /* PlayerTests tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 14F9E21C20DDA8B5008D7CB2 /* Build configuration list for PBXNativeTarget "PlayerTests tvOS" */; + buildPhases = ( + 14F9E21120DDA8B5008D7CB2 /* Sources */, + 14F9E21220DDA8B5008D7CB2 /* Frameworks */, + 14F9E21320DDA8B5008D7CB2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 14F9E21B20DDA8B5008D7CB2 /* PBXTargetDependency */, + ); + name = "PlayerTests tvOS"; + productName = "PlayerTests tvOS"; + productReference = 14F9E21520DDA8B5008D7CB2 /* PlayerTests tvOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 848DE8D520D86AEB000807C4 /* Player macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 848DE8E120D86AEF000807C4 /* Build configuration list for PBXNativeTarget "Player macOS" */; + buildPhases = ( + 848DE8D220D86AEB000807C4 /* Sources */, + 848DE8D320D86AEB000807C4 /* Frameworks */, + 848DE8D420D86AEB000807C4 /* Resources */, + 848DE8F520D9550B000807C4 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 848DE8F420D9550A000807C4 /* PBXTargetDependency */, + ); + name = "Player macOS"; + productName = Player_macOS; + productReference = 848DE8D620D86AEB000807C4 /* Player macOS.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -229,28 +509,59 @@ isa = PBXProject; attributes = { LastSwiftMigration = 0700; - LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0900; + LastSwiftUpdateCheck = 0940; + LastUpgradeCheck = 1240; ORGANIZATIONNAME = "Patrick Piemonte"; TargetAttributes = { 04B4E63C1C1E8A7B00E20778 = { CreatedOnToolsVersion = 7.2; + DevelopmentTeam = 9V456WSURS; LastSwiftMigration = 0900; + ProvisioningStyle = Automatic; }; 0638A5301E01BBF8009EE18B = { CreatedOnToolsVersion = 8.2; - LastSwiftMigration = 0820; - ProvisioningStyle = Automatic; + LastSwiftMigration = 1240; }; 065A0F9C1A25C8E2005BA7BC = { CreatedOnToolsVersion = 6.1; + DevelopmentTeam = 9V456WSURS; LastSwiftMigration = 0900; + ProvisioningStyle = Automatic; + }; + 14F9E1F220DD5259008D7CB2 = { + CreatedOnToolsVersion = 9.4.1; + DevelopmentTeam = 9V456WSURS; + ProvisioningStyle = Automatic; + TestTargetID = 848DE8D520D86AEB000807C4; + }; + 14F9E20520DD938D008D7CB2 = { + CreatedOnToolsVersion = 9.4.1; + DevelopmentTeam = 9V456WSURS; + ProvisioningStyle = Automatic; + TestTargetID = 065A0F9C1A25C8E2005BA7BC; + }; + 14F9E21420DDA8B5008D7CB2 = { + CreatedOnToolsVersion = 9.4.1; + DevelopmentTeam = 9V456WSURS; + ProvisioningStyle = Automatic; + TestTargetID = 04B4E63C1C1E8A7B00E20778; + }; + 848DE8D520D86AEB000807C4 = { + CreatedOnToolsVersion = 9.4.1; + DevelopmentTeam = 9V456WSURS; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; }; }; }; buildConfigurationList = 065A0F981A25C8E2005BA7BC /* Build configuration list for PBXProject "Player" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -261,9 +572,13 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 065A0F9C1A25C8E2005BA7BC /* Player_iOS */, - 04B4E63C1C1E8A7B00E20778 /* Player_tv */, + 065A0F9C1A25C8E2005BA7BC /* Player iOS */, + 04B4E63C1C1E8A7B00E20778 /* Player tvOS */, + 848DE8D520D86AEB000807C4 /* Player macOS */, 0638A5301E01BBF8009EE18B /* Player */, + 14F9E1F220DD5259008D7CB2 /* PlayerTests macOS */, + 14F9E20520DD938D008D7CB2 /* PlayerTests iOS */, + 14F9E21420DDA8B5008D7CB2 /* PlayerTests tvOS */, ); }; /* End PBXProject section */ @@ -293,15 +608,65 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 14F9E1F120DD5259008D7CB2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14F9E20120DD5A3C008D7CB2 /* test.mp4 in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14F9E20420DD938D008D7CB2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14F9E21020DD949F008D7CB2 /* test.mp4 in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14F9E21320DDA8B5008D7CB2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14F9E21F20DDA9D2008D7CB2 /* test.mp4 in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 848DE8D420D86AEB000807C4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 848DE8E720D87E01000807C4 /* MainMenu.xib in Resources */, + 848DE8DB20D86AEF000807C4 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 84F4B33720DA31140007AE86 /* Run SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run SwiftLint"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Change directory (inside 'Player/Project' right now).\ncd ..\n\nif [ \"${CI}\" != \"true\" ]; then\n if which swiftlint >/dev/null; then\n swiftlint\n else\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nelse\n echo \"warning: Skipping SwiftLint (Travis will run it)\"\nfi"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 04B4E6391C1E8A7B00E20778 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 04B4E6421C1E8A7B00E20778 /* ViewController.swift in Sources */, - 04D702CF1C1E8C8400879C91 /* Player.swift in Sources */, 04B4E6401C1E8A7B00E20778 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -310,7 +675,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 06C0905F1E038F250038CA25 /* Player.swift in Sources */, + 8491C2FF20DC1A570057A7C4 /* Player.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -319,13 +684,78 @@ buildActionMask = 2147483647; files = ( 065A0FA51A25C8E2005BA7BC /* ViewController.swift in Sources */, - 065A0FC61A25C94B005BA7BC /* Player.swift in Sources */, 065A0FA31A25C8E2005BA7BC /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 14F9E1EF20DD5259008D7CB2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14F9E1F620DD5259008D7CB2 /* MacPlayerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14F9E20220DD938D008D7CB2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14F9E20920DD938D008D7CB2 /* iOSPlayerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14F9E21120DDA8B5008D7CB2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14F9E21820DDA8B5008D7CB2 /* tvOSPlayerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 848DE8D220D86AEB000807C4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 848DE8E520D86C1E000807C4 /* ViewController.swift in Sources */, + 848DE8D920D86AEB000807C4 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 14F9E1F920DD5259008D7CB2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 848DE8D520D86AEB000807C4 /* Player macOS */; + targetProxy = 14F9E1F820DD5259008D7CB2 /* PBXContainerItemProxy */; + }; + 14F9E20C20DD938D008D7CB2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 065A0F9C1A25C8E2005BA7BC /* Player iOS */; + targetProxy = 14F9E20B20DD938D008D7CB2 /* PBXContainerItemProxy */; + }; + 14F9E21B20DDA8B5008D7CB2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 04B4E63C1C1E8A7B00E20778 /* Player tvOS */; + targetProxy = 14F9E21A20DDA8B5008D7CB2 /* PBXContainerItemProxy */; + }; + 848DE8F420D9550A000807C4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0638A5301E01BBF8009EE18B /* Player */; + targetProxy = 848DE8F320D9550A000807C4 /* PBXContainerItemProxy */; + }; + 848DE90320D96741000807C4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0638A5301E01BBF8009EE18B /* Player */; + targetProxy = 848DE90220D96741000807C4 /* PBXContainerItemProxy */; + }; + 848DE90820D96923000807C4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0638A5301E01BBF8009EE18B /* Player */; + targetProxy = 848DE90720D96923000807C4 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 065A0FAB1A25C8E2005BA7BC /* LaunchScreen.xib */ = { isa = PBXVariantGroup; @@ -335,13 +765,22 @@ name = LaunchScreen.xib; sourceTree = ""; }; + 848DE8DC20D86AEF000807C4 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 848DE8DD20D86AEF000807C4 /* Base */, + ); + name = MainMenu.xib; + path = ../PlayerMac; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 04B4E6491C1E8A7C00E20778 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -351,9 +790,11 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 9V456WSURS; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -365,16 +806,17 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.patrickpiemonte.PlayerTV; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; 04B4E64A1C1E8A7C00E20778 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -384,9 +826,11 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 9V456WSURS; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -396,9 +840,10 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.patrickpiemonte.PlayerTV; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -406,14 +851,17 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - COPY_PHASE_STRIP = NO; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; + INFOPLIST_FILE = "$(SRCROOT)/Shared/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.patrickpiemonte.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = Player; + SDKROOT = ""; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator macosx"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -421,40 +869,46 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Shared/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.patrickpiemonte.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = Player; + SDKROOT = ""; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator macosx"; + SWIFT_VERSION = 5.0; }; name = Release; }; 065A0FBA1A25C8E2005BA7BC /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0656772A1A26C59B0038C208 /* Debug.xcconfig */; buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -469,39 +923,44 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = Framework/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; 065A0FBB1A25C8E2005BA7BC /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 06C6A5891A89E952009DC6EA /* Release.xcconfig */; buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -512,9 +971,13 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = Framework/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_VERSION = 4.0; + TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -522,29 +985,297 @@ 065A0FBD1A25C8E2005BA7BC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEVELOPMENT_TEAM = 9V456WSURS; INFOPLIST_FILE = "$(SRCROOT)/Player/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.patrickpiemonte.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "Player iOS"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; 065A0FBE1A25C8E2005BA7BC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEVELOPMENT_TEAM = 9V456WSURS; INFOPLIST_FILE = "$(SRCROOT)/Player/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.patrickpiemonte.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "Player iOS"; + PROVISIONING_PROFILE_SPECIFIER = ""; + }; + name = Release; + }; + 14F9E1FA20DD5259008D7CB2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 9V456WSURS; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + INFOPLIST_FILE = Tests/macOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_BUNDLE_IDENTIFIER = "com.bigzlabs.PlayerTests-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 4.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Player macOS.app/Contents/MacOS/Player macOS"; + }; + name = Debug; + }; + 14F9E1FB20DD5259008D7CB2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 9V456WSURS; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Tests/macOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_BUNDLE_IDENTIFIER = "com.bigzlabs.PlayerTests-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 4.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Player macOS.app/Contents/MacOS/Player macOS"; + }; + name = Release; + }; + 14F9E20E20DD938D008D7CB2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 9V456WSURS; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + INFOPLIST_FILE = Tests/iOS/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.bigzlabs.PlayerTests-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Player iOS.app/Player iOS"; + }; + name = Debug; + }; + 14F9E20F20DD938D008D7CB2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 9V456WSURS; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Tests/iOS/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.bigzlabs.PlayerTests-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Player iOS.app/Player iOS"; + }; + name = Release; + }; + 14F9E21D20DDA8B5008D7CB2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 9V456WSURS; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + INFOPLIST_FILE = Tests/tvOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.bigzlabs.PlayerTests-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = 3; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Player tvOS.app/Player tvOS"; + TVOS_DEPLOYMENT_TARGET = 12.0; + }; + name = Debug; + }; + 14F9E21E20DDA8B5008D7CB2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 9V456WSURS; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Tests/tvOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.bigzlabs.PlayerTests-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = 3; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Player tvOS.app/Player tvOS"; + TVOS_DEPLOYMENT_TARGET = 12.0; + }; + name = Release; + }; + 848DE8E220D86AEF000807C4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = PlayerMac/Player.entitlements; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 9V456WSURS; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + INFOPLIST_FILE = "$(SRCROOT)/PlayerMac/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_BUNDLE_IDENTIFIER = "com.bigzlabs.Player-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + 848DE8E320D86AEF000807C4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = PlayerMac/Player.entitlements; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 9V456WSURS; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "$(SRCROOT)/PlayerMac/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_BUNDLE_IDENTIFIER = "com.bigzlabs.Player-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 04B4E64B1C1E8A7C00E20778 /* Build configuration list for PBXNativeTarget "Player_tv" */ = { + 04B4E64B1C1E8A7C00E20778 /* Build configuration list for PBXNativeTarget "Player tvOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 04B4E6491C1E8A7C00E20778 /* Debug */, @@ -571,7 +1302,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 065A0FBC1A25C8E2005BA7BC /* Build configuration list for PBXNativeTarget "Player_iOS" */ = { + 065A0FBC1A25C8E2005BA7BC /* Build configuration list for PBXNativeTarget "Player iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 065A0FBD1A25C8E2005BA7BC /* Debug */, @@ -580,6 +1311,42 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 14F9E1FC20DD5259008D7CB2 /* Build configuration list for PBXNativeTarget "PlayerTests macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14F9E1FA20DD5259008D7CB2 /* Debug */, + 14F9E1FB20DD5259008D7CB2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 14F9E20D20DD938D008D7CB2 /* Build configuration list for PBXNativeTarget "PlayerTests iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14F9E20E20DD938D008D7CB2 /* Debug */, + 14F9E20F20DD938D008D7CB2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 14F9E21C20DDA8B5008D7CB2 /* Build configuration list for PBXNativeTarget "PlayerTests tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14F9E21D20DDA8B5008D7CB2 /* Debug */, + 14F9E21E20DDA8B5008D7CB2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 848DE8E120D86AEF000807C4 /* Build configuration list for PBXNativeTarget "Player macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 848DE8E220D86AEF000807C4 /* Debug */, + 848DE8E320D86AEF000807C4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 065A0F951A25C8E2005BA7BC /* Project object */; diff --git a/Project/Player.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Project/Player.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Project/Player.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Project/Player.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Project/Player.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/Project/Player.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - iOS.xcscheme b/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - iOS.xcscheme index f3dec47..f0582d3 100644 --- a/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - iOS.xcscheme +++ b/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - iOS.xcscheme @@ -1,6 +1,6 @@ - - - - @@ -40,37 +26,33 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + - - - - - - - - diff --git a/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - macOS.xcscheme b/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - macOS.xcscheme new file mode 100644 index 0000000..3b7aad0 --- /dev/null +++ b/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - macOS.xcscheme @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - tvOS.xcscheme b/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - tvOS.xcscheme index 6d610ad..1c5b2e9 100644 --- a/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - tvOS.xcscheme +++ b/Project/Player.xcodeproj/xcshareddata/xcschemes/Debug - tvOS.xcscheme @@ -1,6 +1,6 @@ @@ -26,27 +26,33 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + + + + + - - diff --git a/Project/Player.xcodeproj/xcshareddata/xcschemes/Player.xcscheme b/Project/Player.xcodeproj/xcshareddata/xcschemes/Player.xcscheme index 8b1309f..9ca8ee3 100644 --- a/Project/Player.xcodeproj/xcshareddata/xcschemes/Player.xcscheme +++ b/Project/Player.xcodeproj/xcshareddata/xcschemes/Player.xcscheme @@ -1,6 +1,6 @@ - - - - @@ -26,27 +26,33 @@ buildConfiguration = "Release" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + + + + + - - diff --git a/Project/Player.xcodeproj/xcshareddata/xcschemes/Release - macOS.xcscheme b/Project/Player.xcodeproj/xcshareddata/xcschemes/Release - macOS.xcscheme new file mode 100644 index 0000000..a40cf2e --- /dev/null +++ b/Project/Player.xcodeproj/xcshareddata/xcschemes/Release - macOS.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Project/Player.xcodeproj/xcshareddata/xcschemes/Release - tvOS.xcscheme b/Project/Player.xcodeproj/xcshareddata/xcschemes/Release - tvOS.xcscheme index a2bdf76..4ae148c 100644 --- a/Project/Player.xcodeproj/xcshareddata/xcschemes/Release - tvOS.xcscheme +++ b/Project/Player.xcodeproj/xcshareddata/xcschemes/Release - tvOS.xcscheme @@ -1,6 +1,6 @@ @@ -26,27 +26,33 @@ buildConfiguration = "Release" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + + + + + - - diff --git a/Project/Player/AppDelegate.swift b/Project/Player/AppDelegate.swift index 5d4b912..f694464 100644 --- a/Project/Player/AppDelegate.swift +++ b/Project/Player/AppDelegate.swift @@ -28,17 +28,23 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? // MARK: UIApplicationDelegate - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { - self.window = UIWindow(frame:UIScreen.main.bounds) - self.window!.backgroundColor = UIColor.black - self.window!.rootViewController = ViewController() - self.window!.makeKeyAndVisible() - return true - } -} + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool { + + window = UIWindow(frame: UIScreen.main.bounds) + window!.backgroundColor = UIColor.black + if NSClassFromString("XCTest") == nil { + window!.rootViewController = ViewController() + } else { + window!.rootViewController = UIViewController() + } + + window!.makeKeyAndVisible() + return true + } +} diff --git a/Project/Player/Assets.xcassets/AppIcon.appiconset/Contents.json b/Project/Player/Assets.xcassets/AppIcon.appiconset/Contents.json index b8236c6..19882d5 100644 --- a/Project/Player/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Project/Player/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -39,6 +39,11 @@ "idiom" : "iphone", "size" : "60x60", "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { diff --git a/Project/Player/Info.plist b/Project/Player/Info.plist index e9dceb7..80f4716 100644 --- a/Project/Player/Info.plist +++ b/Project/Player/Info.plist @@ -33,6 +33,8 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance diff --git a/Project/Player/ViewController.swift b/Project/Player/ViewController.swift index e623a04..ebcfcac 100644 --- a/Project/Player/ViewController.swift +++ b/Project/Player/ViewController.swift @@ -24,111 +24,103 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import AVKit +import Player import UIKit -let videoUrl = URL(string: "https://v.cdn.vine.co/r/videos/AA3C120C521177175800441692160_38f2cbd1ffb.1.5.13763579289575020226.mp4")! - class ViewController: UIViewController { - fileprivate var player = Player() - - // MARK: object lifecycle + + // MARK: Object lifecycle + deinit { - self.player.willMove(toParentViewController: self) - self.player.view.removeFromSuperview() - self.player.removeFromParentViewController() + player.remove(from: self) } - // MARK: view lifecycle + // MARK: View lifecycle override func viewDidLoad() { super.viewDidLoad() - - self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - - self.player.playerDelegate = self - self.player.playbackDelegate = self - self.player.view.frame = self.view.bounds - - self.addChildViewController(self.player) - self.view.addSubview(self.player.view) - self.player.didMove(toParentViewController: self) - - self.player.url = videoUrl - - self.player.playbackLoops = true - - let tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGestureRecognizer(_:))) - tapGestureRecognizer.numberOfTapsRequired = 1 - self.player.view.addGestureRecognizer(tapGestureRecognizer) + + let uri = "https://www.apple.com/105/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7" + + "/films/meet-iphone-x/iphone-x-meet-iphone-tpl-cc-us-20171129_1280x720h.mp4" + player.url = URL(string: uri) + player.playbackLoops = true + // Need to set before calling `add(to:)` + // Note: defaults to `true`, so the following line is redundant (and unnecessary). + player.usesSystemPlaybackControls = true + + // Optional + player.playerDelegate = self + // Optional + player.playbackDelegate = self + + player.add(to: self) + + // Play audio even when the Silent switch is engaged. + try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback) + try? AVAudioSession.sharedInstance().setActive(true) + + // Uncomment for simple play/pause functionality if not using system-supplied playback controls. +// let tapGestureRecognizer = UITapGestureRecognizer(target: self, +// action: #selector(handleTapGestureRecognizer(_:))) +// tapGestureRecognizer.numberOfTapsRequired = 1 +// player.view.addGestureRecognizer(tapGestureRecognizer) + + // Uncomment to support orientation rotations if not using system-supplied playback controls. +// player.view.translatesAutoresizingMaskIntoConstraints = false +// NSLayoutConstraint.activate([ +// player.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), +// player.view.topAnchor.constraint(equalTo: view.topAnchor), +// player.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), +// player.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) +// ]) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - - self.player.playFromBeginning() + + player.playFromBeginning() } } // MARK: - UIGestureRecognizer extension ViewController { - @objc func handleTapGestureRecognizer(_ gestureRecognizer: UITapGestureRecognizer) { - switch (self.player.playbackState.rawValue) { - case PlaybackState.stopped.rawValue: - self.player.playFromBeginning() - break - case PlaybackState.paused.rawValue: - self.player.playFromCurrentTime() - break - case PlaybackState.playing.rawValue: - self.player.pause() - break - case PlaybackState.failed.rawValue: - self.player.pause() - break - default: - self.player.pause() - break + switch player.playbackState { + case .stopped: + player.playFromBeginning() + case .paused: + player.playFromCurrentTime() + case .playing: + player.pause() + case .failed: + player.pause() } } - } -// MARK: - PlayerDelegate +// MARK: - PlayerDelegate (optional) -extension ViewController:PlayerDelegate { - - func playerReady(_ player: Player) { - } - - func playerPlaybackStateDidChange(_ player: Player) { - } - - func playerBufferingStateDidChange(_ player: Player) { - } - func playerBufferTimeDidChange(_ bufferTime: Double) { - - } - -} +extension ViewController: PlayerDelegate { + func playerReady(player: Player) {} -// MARK: - PlayerPlaybackDelegate + func playerPlaybackStateDidChange(player: Player) {} -extension ViewController:PlayerPlaybackDelegate { - - func playerCurrentTimeDidChange(_ player: Player) { - } - - func playerPlaybackWillStartFromBeginning(_ player: Player) { - } - - func playerPlaybackDidEnd(_ player: Player) { - } - - func playerPlaybackWillLoop(_ player: Player) { - } - + func playerBufferingStateDidChange(player: Player) {} + + func playerBufferTimeDidChange(bufferTime: Double) {} } +// MARK: - PlayerPlaybackDelegate (optional) + +extension ViewController: PlayerPlaybackDelegate { + func playerCurrentTimeDidChange(player: Player) {} + + func playerPlaybackWillStartFromBeginning(player: Player) {} + + func playerPlaybackDidEnd(player: Player) {} + + func playerPlaybackWillLoop(player: Player) {} +} diff --git a/Project/PlayerMac/AppDelegate.swift b/Project/PlayerMac/AppDelegate.swift new file mode 100644 index 0000000..52b9546 --- /dev/null +++ b/Project/PlayerMac/AppDelegate.swift @@ -0,0 +1,48 @@ +// AppDelegate.swift +// +// Created by Chris Zielinski on 06/18/18. +// +// The MIT License (MIT) +// +// Copyright (c) 2014-present patrick piemonte (http://patrickpiemonte.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Cocoa + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + @IBOutlet var window: NSWindow! + + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Insert code here to initialize your application + + if NSClassFromString("XCTest") == nil { + window.contentViewController = ViewController() + window.makeMain() + } else { + // Using our own view controller for testing. + window.makeMain() + } + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } +} diff --git a/Project/PlayerMac/Assets.xcassets/AppIcon.appiconset/Contents.json b/Project/PlayerMac/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2db2b1c --- /dev/null +++ b/Project/PlayerMac/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Project/PlayerMac/Assets.xcassets/Contents.json b/Project/PlayerMac/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Project/PlayerMac/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Project/PlayerMac/Base.lproj/MainMenu.xib b/Project/PlayerMac/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..8078e81 --- /dev/null +++ b/Project/PlayerMac/Base.lproj/MainMenu.xib @@ -0,0 +1,693 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Project/PlayerMac/Info.plist b/Project/PlayerMac/Info.plist new file mode 100644 index 0000000..0fef1f0 --- /dev/null +++ b/Project/PlayerMac/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2018 Patrick Piemonte. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/Project/PlayerMac/Player.entitlements b/Project/PlayerMac/Player.entitlements new file mode 100644 index 0000000..625af03 --- /dev/null +++ b/Project/PlayerMac/Player.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + + diff --git a/Project/PlayerMac/ViewController.swift b/Project/PlayerMac/ViewController.swift new file mode 100644 index 0000000..2303261 --- /dev/null +++ b/Project/PlayerMac/ViewController.swift @@ -0,0 +1,98 @@ +// ViewController.swift +// +// Created by Chris Zielinski on 06/18/18. +// +// The MIT License (MIT) +// +// Copyright (c) 2014-present patrick piemonte (http://patrickpiemonte.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import AppKit +import Player + +class ViewController: NSViewController { + + var player = Player() + + // MARK: Object lifecycle + + deinit { + player.remove(from: self) + } + + override func loadView() { + view = NSView() + view.autoresizingMask = [.height, .width] + view.setFrameSize(NSSize(width: 400 * (16.0 / 9), height: 400)) + } + + // MARK: View lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + let uri = "https://www.apple.com/105/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7" + + "/films/meet-iphone-x/iphone-x-meet-iphone-tpl-cc-us-20171129_1280x720h.mp4" + player.url = URL(string: uri) + player.playbackLoops = true + player.fillMode = .resizeAspectFill + player.controlsStyle = .floating + + player.view.autoresizingMask = [.height, .width] + player.view.frame = view.bounds + + // Optional + player.playerDelegate = self + // Optional + player.playbackDelegate = self + + player.add(to: self) + } + + override func viewDidAppear() { + super.viewDidAppear() + + player.playFromBeginning() + } +} + +// MARK: - PlayerDelegate (optional) + +extension ViewController: PlayerDelegate { + func playerReady(player: Player) {} + + func playerPlaybackStateDidChange(player: Player) {} + + func playerBufferingStateDidChange(player: Player) {} + + func playerBufferTimeDidChange(bufferTime: Double) {} +} + +// MARK: - PlayerPlaybackDelegate (optional) + +extension ViewController: PlayerPlaybackDelegate { + func playerCurrentTimeDidChange(player: Player) {} + + func playerPlaybackWillStartFromBeginning(player: Player) {} + + func playerPlaybackDidEnd(player: Player) {} + + func playerPlaybackWillLoop(player: Player) {} +} diff --git a/Project/PlayerTV/AppDelegate.swift b/Project/PlayerTV/AppDelegate.swift index 8f32eb4..6c38a8b 100644 --- a/Project/PlayerTV/AppDelegate.swift +++ b/Project/PlayerTV/AppDelegate.swift @@ -28,18 +28,22 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - + // MARK: UIApplicationDelegate - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { - self.window = UIWindow(frame:UIScreen.main.bounds) - self.window!.backgroundColor = UIColor.black - self.window!.rootViewController = ViewController() - self.window!.makeKeyAndVisible() + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool { + window = UIWindow(frame: UIScreen.main.bounds) + window!.backgroundColor = UIColor.black + + if NSClassFromString("XCTest") == nil { + window!.rootViewController = ViewController() + } else { + window!.rootViewController = UIViewController() + } + + window!.makeKeyAndVisible() return true } - } - diff --git a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json index 0564959..16a370d 100644 --- a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json +++ b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -3,6 +3,10 @@ { "idiom" : "tv", "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" } ], "info" : { diff --git a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json index 0564959..16a370d 100644 --- a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json +++ b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -3,6 +3,10 @@ { "idiom" : "tv", "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" } ], "info" : { diff --git a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json index 0564959..16a370d 100644 --- a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json +++ b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -3,6 +3,10 @@ { "idiom" : "tv", "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" } ], "info" : { diff --git a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json index 0564959..16a370d 100644 --- a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json +++ b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -3,6 +3,10 @@ { "idiom" : "tv", "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" } ], "info" : { diff --git a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json index 0564959..16a370d 100644 --- a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json +++ b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -3,6 +3,10 @@ { "idiom" : "tv", "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" } ], "info" : { diff --git a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json index 0564959..16a370d 100644 --- a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json +++ b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -3,6 +3,10 @@ { "idiom" : "tv", "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" } ], "info" : { diff --git a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json index 0564959..16a370d 100644 --- a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json +++ b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json @@ -3,6 +3,10 @@ { "idiom" : "tv", "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" } ], "info" : { diff --git a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json index 0564959..16a370d 100644 --- a/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json +++ b/Project/PlayerTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json @@ -3,6 +3,10 @@ { "idiom" : "tv", "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" } ], "info" : { diff --git a/Project/PlayerTV/Assets.xcassets/LaunchImage.launchimage/Contents.json b/Project/PlayerTV/Assets.xcassets/LaunchImage.launchimage/Contents.json index 29d94c7..d746a60 100644 --- a/Project/PlayerTV/Assets.xcassets/LaunchImage.launchimage/Contents.json +++ b/Project/PlayerTV/Assets.xcassets/LaunchImage.launchimage/Contents.json @@ -1,5 +1,12 @@ { "images" : [ + { + "orientation" : "landscape", + "idiom" : "tv", + "extent" : "full-screen", + "minimum-system-version" : "11.0", + "scale" : "2x" + }, { "orientation" : "landscape", "idiom" : "tv", diff --git a/Project/PlayerTV/ViewController.swift b/Project/PlayerTV/ViewController.swift index 97bb5b1..198516b 100644 --- a/Project/PlayerTV/ViewController.swift +++ b/Project/PlayerTV/ViewController.swift @@ -25,105 +25,91 @@ // SOFTWARE. import UIKit - -let videoUrl = URL(string: "https://v.cdn.vine.co/r/videos/AA3C120C521177175800441692160_38f2cbd1ffb.1.5.13763579289575020226.mp4")! +import Player class ViewController: UIViewController { - - internal var player = Player() - - // MARK: object lifecycle + + var player = Player() + + // MARK: Object lifecycle + deinit { - self.player.willMove(toParentViewController: self) - self.player.view.removeFromSuperview() - self.player.removeFromParentViewController() + player.remove(from: self) } - - // MARK: view lifecycle - + + // MARK: View lifecycle + override func viewDidLoad() { super.viewDidLoad() - - self.view.autoresizingMask = ([.flexibleWidth, .flexibleHeight]) - - self.player.playerDelegate = self - self.player.playbackDelegate = self - self.player.view.frame = self.view.bounds - - self.addChildViewController(self.player) - self.view.addSubview(self.player.view) - self.player.didMove(toParentViewController: self) - - self.player.url = videoUrl - - self.player.playbackLoops = true - - let tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGestureRecognizer(_:))) - tapGestureRecognizer.allowedPressTypes = [NSNumber(value: UIPressType.playPause.rawValue)]; - self.view.addGestureRecognizer(tapGestureRecognizer) + + let uri = "https://www.apple.com/105/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7" + + "/films/meet-iphone-x/iphone-x-meet-iphone-tpl-cc-us-20171129_1280x720h.mp4" + player.url = URL(string: uri) + player.playbackLoops = true + // Need to set before calling `add(to:)` + // Note: Defaults to `true`, so the following line is redundant (and unnecessary). + player.usesSystemPlaybackControls = true + + player.view.frame = view.bounds + + // Optional + player.playerDelegate = self + // Optional + player.playbackDelegate = self + + player.add(to: self) + + // Uncomment for simple play/pause functionality if not using system-supplied playback controls. +// let tapGestureRecognizer = UITapGestureRecognizer(target: self, +// action: #selector(handleTapGestureRecognizer(_:))) +// tapGestureRecognizer.allowedPressTypes = [NSNumber(value: UIPressType.playPause.rawValue)] +// view.addGestureRecognizer(tapGestureRecognizer) } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - - self.player.playFromBeginning() + + player.playFromBeginning() } } // MARK: - UIGestureRecognizer extension ViewController { - @objc func handleTapGestureRecognizer(_ gestureRecognizer: UITapGestureRecognizer) { - switch (self.player.playbackState.rawValue) { - case PlaybackState.stopped.rawValue: - self.player.playFromBeginning() - case PlaybackState.paused.rawValue: - self.player.playFromCurrentTime() - case PlaybackState.playing.rawValue: - self.player.pause() - case PlaybackState.failed.rawValue: - self.player.pause() - default: - self.player.pause() + switch player.playbackState { + case .stopped: + player.playFromBeginning() + case .paused: + player.playFromCurrentTime() + case .playing: + player.pause() + case .failed: + player.pause() } } - } -// MARK: - PlayerDelegate - +// MARK: - PlayerDelegate (optional) + extension ViewController: PlayerDelegate { - - func playerReady(_ player: Player) { - } - - func playerPlaybackStateDidChange(_ player: Player) { - } - - func playerBufferingStateDidChange(_ player: Player) { - } - - func playerBufferTimeDidChange(_ bufferTime: Double) { - - } + func playerReady(player: Player) {} + + func playerPlaybackStateDidChange(player: Player) {} + + func playerBufferingStateDidChange(player: Player) {} + + func playerBufferTimeDidChange(bufferTime: Double) {} } -// MARK: - PlayerPlaybackDelegate +// MARK: - PlayerPlaybackDelegate (optional) extension ViewController: PlayerPlaybackDelegate { - - func playerCurrentTimeDidChange(_ player: Player) { - } - - func playerPlaybackWillStartFromBeginning(_ player: Player) { - } - - func playerPlaybackDidEnd(_ player: Player) { - } - - func playerPlaybackWillLoop(_ player: Player) { - } - -} + func playerCurrentTimeDidChange(player: Player) {} + func playerPlaybackWillStartFromBeginning(player: Player) {} + + func playerPlaybackDidEnd(player: Player) {} + + func playerPlaybackWillLoop(player: Player) {} +} diff --git a/Project/Base.xcconfig b/Project/Shared/Base.xcconfig similarity index 100% rename from Project/Base.xcconfig rename to Project/Shared/Base.xcconfig diff --git a/Project/Debug.xcconfig b/Project/Shared/Debug.xcconfig similarity index 95% rename from Project/Debug.xcconfig rename to Project/Shared/Debug.xcconfig index 0e29a8f..e64df2e 100644 --- a/Project/Debug.xcconfig +++ b/Project/Shared/Debug.xcconfig @@ -17,5 +17,4 @@ CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES ALWAYS_SEARCH_USER_PATHS = NO -COPY_PHASE_STRIP = NO ONLY_ACTIVE_ARCH = YES diff --git a/Project/Framework/Info.plist b/Project/Shared/Info.plist similarity index 91% rename from Project/Framework/Info.plist rename to Project/Shared/Info.plist index 51aae2e..2a44702 100644 --- a/Project/Framework/Info.plist +++ b/Project/Shared/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.patrickpiemonte.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Sources/Player.h b/Project/Shared/Player.h similarity index 97% rename from Sources/Player.h rename to Project/Shared/Player.h index a0a9236..d8525aa 100644 --- a/Sources/Player.h +++ b/Project/Shared/Player.h @@ -24,7 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#import +#import //! Project version number for Player. FOUNDATION_EXPORT double PlayerVersionNumber; diff --git a/Project/Release.xcconfig b/Project/Shared/Release.xcconfig similarity index 94% rename from Project/Release.xcconfig rename to Project/Shared/Release.xcconfig index 3226a1d..f0a68bb 100644 --- a/Project/Release.xcconfig +++ b/Project/Shared/Release.xcconfig @@ -7,7 +7,6 @@ #include "Base.xcconfig" DEAD_CODE_STRIPPING = YES -COPY_PHASE_STRIP = YES VALIDATE_PRODUCT = YES SWIFT_OPTIMIZATION_LEVEL = -Owholemodule diff --git a/Project/Shared/Travis.xcconfig b/Project/Shared/Travis.xcconfig new file mode 100644 index 0000000..58539d2 --- /dev/null +++ b/Project/Shared/Travis.xcconfig @@ -0,0 +1,11 @@ +// +// Travis.xcconfig +// +// Created by Chris Zielinski on 6/21/18. +// + +//#include "Release.xcconfig" + +CODE_SIGN_STYLE = Manual; +CODE_SIGN_IDENTITY = ; +DEVELOPMENT_TEAM = ; diff --git a/Project/Shared/test.mp4 b/Project/Shared/test.mp4 new file mode 100644 index 0000000..0142d35 Binary files /dev/null and b/Project/Shared/test.mp4 differ diff --git a/Project/Tests/iOS/Info.plist b/Project/Tests/iOS/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/Project/Tests/iOS/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Project/Tests/iOS/iOSPlayerTests.swift b/Project/Tests/iOS/iOSPlayerTests.swift new file mode 100644 index 0000000..a851291 --- /dev/null +++ b/Project/Tests/iOS/iOSPlayerTests.swift @@ -0,0 +1,99 @@ +// +// iOSPlayerTests.swift +// iOS PlayerTests +// +// Created by Chris Zielinski on 6/22/18. +// Copyright © 2018 Patrick Piemonte. All rights reserved. +// + +import XCTest +import Player +@testable import Player_iOS + +class TestViewController: UIViewController { + + let player = Player() + @objc dynamic var didLoop: Bool = false + + convenience init() { + self.init(nibName: nil, bundle: nil) + } + + override func viewDidLoad() { + player.url = Bundle(for: type(of: self)).url(forResource: "test", withExtension: "mp4")! + player.view.autoresizingMask = [.flexibleHeight, .flexibleWidth] + player.view.frame = view.bounds + + player.playbackDelegate = self + } +} + +extension TestViewController: PlayerPlaybackDelegate { + func playerPlaybackWillLoop(player: Player) { + didLoop = true + } +} + +class iOSPlayerTests: XCTestCase { + + var testViewController: TestViewController! + var player: Player { + return testViewController.player + } + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + testViewController = TestViewController() + UIApplication.shared.windows.first!.rootViewController = testViewController + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + testViewController = nil + } + + func testAutoplayEnabled() { + player.autoplay = true + player.add(to: testViewController) + + expectation(for: NSPredicate(format: "isPlaying == true"), evaluatedWith: player, handler: nil) + waitForExpectations(timeout: 5, handler: nil) + } + + func testAutoplayDisabled() { + player.autoplay = false + player.add(to: testViewController) + + let result = XCTWaiter.wait(for: [ + expectation(for: NSPredicate(format: "isPlaying == true"), evaluatedWith: player, handler: nil)], + timeout: 5) + XCTAssert(result == .timedOut) + } + + func testPlaybackLoops() { + player.playbackLoops = true + player.add(to: testViewController) + + let result = XCTWaiter.wait(for: [ + expectation(for: NSPredicate(format: "didLoop == true"), evaluatedWith: testViewController, handler: nil)], + timeout: 10) + + XCTAssert(result == .completed, "`playerPlaybackWillLoop(player:)` was not called.") + XCTAssert(player.currentTime < 0.5, "Player did not loop.") + } + + func testPlaybackFreezesAtEnd() { + player.playbackFreezesAtEnd = true + player.add(to: testViewController) + + let didNotLoop = expectation(for: NSPredicate(format: "didLoop == true"), evaluatedWith: testViewController, handler: nil) + let result = XCTWaiter.wait(for: [didNotLoop], timeout: 5) + + XCTAssert(result == .timedOut, "`playerPlaybackWillLoop(player:)` was called.") + XCTAssert(player.currentTime == player.maximumDuration, "Player did not freeze at last frame.") + XCTAssert(!player.isPlaying, "`isPlaying` is true.") + } + +} diff --git a/Project/Tests/macOS/Info.plist b/Project/Tests/macOS/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/Project/Tests/macOS/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Project/Tests/macOS/MacPlayerTests.swift b/Project/Tests/macOS/MacPlayerTests.swift new file mode 100644 index 0000000..7851d2a --- /dev/null +++ b/Project/Tests/macOS/MacPlayerTests.swift @@ -0,0 +1,106 @@ +// +// PlayerTests_macOS.swift +// PlayerTests_macOS +// +// Created by Chris Zielinski on 6/22/18. +// Copyright © 2018 Patrick Piemonte. All rights reserved. +// + +import XCTest +import Player +import AVKit +@testable import Player_macOS + +class TestViewController: NSViewController { + + let player = Player() + @objc dynamic var didLoop: Bool = false + + convenience init() { + self.init(nibName: nil, bundle: nil) + } + + override func loadView() { + view = NSView() + view.autoresizingMask = [.height, .width] + view.setFrameSize(NSSize(width: 400 * (16.0 / 9), height: 400)) + } + + override func viewDidLoad() { + player.url = Bundle(for: type(of: self)).url(forResource: "test", withExtension: "mp4")! + player.view.autoresizingMask = [.height, .width] + player.view.frame = view.bounds + + player.playbackDelegate = self + } +} + +extension TestViewController: PlayerPlaybackDelegate { + func playerPlaybackWillLoop(player: Player) { + didLoop = true + } +} + +class MacPlayerTests: XCTestCase { + + var testViewController: TestViewController! + var player: Player { + return testViewController.player + } + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + testViewController = TestViewController() + NSApp.windows.first!.contentViewController = testViewController + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + testViewController = nil + } + + func testAutoplayEnabled() { + player.autoplay = true + player.add(to: testViewController) + + expectation(for: NSPredicate(format: "isPlaying == true"), evaluatedWith: player, handler: nil) + waitForExpectations(timeout: 5, handler: nil) + } + + func testAutoplayDisabled() { + player.autoplay = false + player.add(to: testViewController) + + let result = XCTWaiter.wait(for: [ + expectation(for: NSPredicate(format: "isPlaying == true"), evaluatedWith: player, handler: nil)], + timeout: 5) + XCTAssert(result == .timedOut) + } + + func testPlaybackLoops() { + player.playbackLoops = true + player.add(to: testViewController) + + let result = XCTWaiter.wait(for: [ + expectation(for: NSPredicate(format: "didLoop == true"), evaluatedWith: testViewController, handler: nil)], + timeout: 12) + + XCTAssert(result == .completed, "`playerPlaybackWillLoop(player:)` was not called.") + XCTAssert(player.currentTime < 0.5, "Player did not loop.") + } + + func testPlaybackFreezesAtEnd() { + player.playbackFreezesAtEnd = true + player.add(to: testViewController) + + let didNotLoop = expectation(for: NSPredicate(format: "didLoop == true"), evaluatedWith: testViewController, handler: nil) + let result = XCTWaiter.wait(for: [didNotLoop], timeout: 5) + + XCTAssert(result == .timedOut, "`playerPlaybackWillLoop(player:)` was called.") + XCTAssert(player.currentTime == player.maximumDuration, "Player did not freeze at last frame.") + XCTAssert(!player.isPlaying, "`isPlaying` is true.") + } + +} diff --git a/Project/Tests/tvOS/Info.plist b/Project/Tests/tvOS/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/Project/Tests/tvOS/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Project/Tests/tvOS/tvOSPlayerTests.swift b/Project/Tests/tvOS/tvOSPlayerTests.swift new file mode 100644 index 0000000..113a679 --- /dev/null +++ b/Project/Tests/tvOS/tvOSPlayerTests.swift @@ -0,0 +1,99 @@ +// +// tvOSPlayerTests.swift +// tvOS PlayerTests +// +// Created by Chris Zielinski on 6/22/18. +// Copyright © 2018 Patrick Piemonte. All rights reserved. +// + +import XCTest +import Player +@testable import Player_tvOS + +class TestViewController: UIViewController { + + let player = Player() + @objc dynamic var didLoop: Bool = false + + convenience init() { + self.init(nibName: nil, bundle: nil) + } + + override func viewDidLoad() { + player.url = Bundle(for: type(of: self)).url(forResource: "test", withExtension: "mp4")! + player.view.frame = view.bounds + + player.playbackDelegate = self + } +} + +extension TestViewController: PlayerPlaybackDelegate { + func playerPlaybackWillLoop(player: Player) { + didLoop = true + } +} + + +class tvOSPlayerTests: XCTestCase { + + var testViewController: TestViewController! + var player: Player { + return testViewController.player + } + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + testViewController = TestViewController() + UIApplication.shared.windows.first!.rootViewController = testViewController + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + testViewController = nil + } + + func testAutoplayEnabled() { + player.autoplay = true + player.add(to: testViewController) + + expectation(for: NSPredicate(format: "isPlaying == true"), evaluatedWith: player, handler: nil) + waitForExpectations(timeout: 5, handler: nil) + } + + func testAutoplayDisabled() { + player.autoplay = false + player.add(to: testViewController) + + let result = XCTWaiter.wait(for: [ + expectation(for: NSPredicate(format: "isPlaying == true"), evaluatedWith: player, handler: nil)], + timeout: 5) + XCTAssert(result == .timedOut) + } + + func testPlaybackLoops() { + player.playbackLoops = true + player.add(to: testViewController) + + let result = XCTWaiter.wait(for: [ + expectation(for: NSPredicate(format: "didLoop == true"), evaluatedWith: testViewController, handler: nil)], + timeout: 10) + + XCTAssert(result == .completed, "`playerPlaybackWillLoop(player:)` was not called.") + XCTAssert(player.currentTime < 0.5, "Player did not loop.") + } + + func testPlaybackFreezesAtEnd() { + player.playbackFreezesAtEnd = true + player.add(to: testViewController) + + let didNotLoop = expectation(for: NSPredicate(format: "didLoop == true"), evaluatedWith: testViewController, handler: nil) + let result = XCTWaiter.wait(for: [didNotLoop], timeout: 5) + + XCTAssert(result == .timedOut, "`playerPlaybackWillLoop(player:)` was called.") + XCTAssert(player.currentTime == player.maximumDuration, "Player did not freeze at last frame.") + XCTAssert(!player.isPlaying, "`isPlaying` is true.") + } + +} diff --git a/README.md b/README.md index ae1a6e0..d60b7ae 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,164 @@ -![Player](https://github.com/piemonte/Player/raw/master/Player.gif) +# Player -## Player +[![Build Status](https://travis-ci.org/piemonte/Player.svg?branch=master)](https://travis-ci.org/piemonte/Player) +[![Platform](https://img.shields.io/cocoapods/p/Player.svg?style=flat)](http://cocoadocs.org/docsets/Player) +[![Pod Version](https://img.shields.io/cocoapods/v/Player.svg?style=flat)](http://cocoadocs.org/docsets/Player/) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Swift Version](https://img.shields.io/badge/language-swift%204.1-brightgreen.svg)](https://developer.apple.com/swift) +[![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://github.com/piemonte/Player/blob/master/LICENSE) -`Player` is a simple iOS video player library written in [Swift](https://developer.apple.com/swift/). +![Overview](https://github.com/chriszielinski/Player/raw/master/readme-assets/player.gif) -[![Build Status](https://travis-ci.org/piemonte/Player.svg?branch=master)](https://travis-ci.org/piemonte/Player) [![Pod Version](https://img.shields.io/cocoapods/v/Player.svg?style=flat)](http://cocoadocs.org/docsets/Player/) [![Swift Version](https://img.shields.io/badge/language-swift%204.0-brightgreen.svg)](https://developer.apple.com/swift) [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://github.com/piemonte/Player/blob/master/LICENSE) +

Player is a simple cross-platform video player library written in Swift. +
+
+⚠️ Warning: version 0.9 has breaking API changes. ⚠️

-- Looking for an obj-c video player? Check out [PBJVideoPlayer (obj-c)](https://github.com/piemonte/PBJVideoPlayer). -- Looking for a Swift camera library? Check out [Next Level](https://github.com/NextLevel/NextLevel). +### Looking for... +- An obj-c video player? Check out [PBJVideoPlayer (obj-c)](https://github.com/piemonte/PBJVideoPlayer). +- A Swift camera library? Check out [Next Level](https://github.com/NextLevel/NextLevel). ### Features - [x] plays local media or streams remote media over HTTP - [x] customizable UI and user interaction +- [x] optional system-supplied playback controls - [x] no size restrictions - [x] orientation change support - [x] simple API +- [x] AirPlay & PIP support (see [`AVPlayerController`](https://developer.apple.com/documentation/avkit/avplayerviewcontroller) documentation) -# Quick Start +### I'm a ~~Rapper~~ Wrapper +- uses [`AVPlayerViewController`](https://developer.apple.com/documentation/avkit/avplayerviewcontroller) on iOS/tvOS platforms for system-supplied playback controls (See `usesSystemPlaybackControls`). Otherwise, an [`AVPlayerLayer`](https://developer.apple.com/documentation/avfoundation/avplayerlayer). +- uses [`AVPlayerView`](https://developer.apple.com/documentation/avkit/avplayerview) on the macOS platform. -`Player` is available for installation using the Cocoa dependency manager [CocoaPods](http://cocoapods.org/). Alternatively, you can simply copy the `Player.swift` file into your Xcode project. +## Installation +`Player` is available for installation using CocoaPods or Carthage. Alternatively, you can simply copy the `Player.swift` file into your Xcode project. + +### Using [CocoaPods](http://cocoapods.org/) ```ruby -# CocoaPods -swift_version = "4.0" -pod "Player", "~> 0.8.0" +pod "Player" +``` + +Need Swift 3? Use release `0.7.0`. **Note**: macOS and system-supplied playback controls not supported. -# Carthage -github "piemonte/Player" ~> 0.8.0 +```ruby +pod "Player", "~> 0.7.0" ``` -Need Swift 3? Use release `0.7.0` +### Using [Carthage](https://github.com/Carthage/Carthage) + +```ruby +github "piemonte/Player" +``` -## Usage +## Quick Start -The sample project provides an example of how to integrate `Player`, otherwise you can follow these steps. +The sample projects provide an example of how to integrate `Player`, otherwise you can follow these steps. -Allocate and add the `Player` controller to your view hierarchy. +Create and add the `Player` to your view controller. -``` Swift - self.player = Player() - self.player.playerDelegate = self - self.player.playbackDelegate = self - self.player.view.frame = self.view.bounds - - self.addChildViewController(self.player) - self.view.addSubview(self.player.view) - self.player.didMove(toParentViewController: self) +```swift +let player = Player() +// Optional +player.playerDelegate = self +// Optional +player.playbackDelegate = self +player.view.frame = view.bounds +player.add(to: self) ``` Provide the file path to the resource you would like to play locally or stream. Ensure you're including the file extension. -``` Swift -let videoUrl: URL = // file or http url -self.player.url = videoUrl +```swift +player.url = URL(string: "https://www.apple.com/105/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/films/meet-iphone-x/iphone-x-meet-iphone-tpl-cc-us-20171129_1280x720h.mp4") ``` -play/pause/chill +play/pause/chill 🏖️ -``` Swift - self.player.playFromBeginning() +```swift +player.playFromBeginning() +player.pause() ``` -Adjust the fill mode for the video, if needed. +Adjust the fill mode for the video, if needed. Note: On iOS, this property is ignored if using system-supplied playback controls. -``` Swift - self.player.fillMode = PlayerFillMode.resizeAspectFit.avFoundationType +```swift +player.fillMode = .resizeAspectFit ``` -Display video playback progress, if needed. +The fill mode can be set to the following values: -``` Swift -extension ViewController: PlayerPlaybackDelegate { +`.resizeAspectFit` (default) - public func playerPlaybackWillStartFromBeginning(_ player: Player) { - } +![.resizeAspectFit](https://github.com/chriszielinski/Player/raw/master/readme-assets/aspectFit.png) + +`.resizeAspectFill` + +![.resizeAspectFill](https://github.com/chriszielinski/Player/raw/master/readme-assets/aspectFill.png) + +`.resizeStretch` (aka please don't. I mean look at that poor thing) + +![.resizeStretch](https://github.com/chriszielinski/Player/raw/master/readme-assets/stretch.png) + +Display video playback progress, if desired. Note, all delegate methods are optional. + +```swift +extension ViewController: PlayerPlaybackDelegate { + public func playerPlaybackWillStartFromBeginning(player: Player) {} - public func playerPlaybackDidEnd(_ player: Player) { - } + public func playerPlaybackDidEnd(player: Player) {} - public func playerCurrentTimeDidChange(_ player: Player) { - let fraction = Double(player.currentTime) / Double(player.maximumDuration) - self._playbackViewController?.setProgress(progress: CGFloat(fraction), animated: true) + public func playerCurrentTimeDidChange(player: Player) { + let currentProgress = Float(player.currentTime / player.maximumDuration) + progressView.setProgress(currentProgress, animated: true) } - public func playerPlaybackWillLoop(_ player: Player) { - self. _playbackViewController?.reset() + public func playerPlaybackWillLoop(player: Player) { + progressView.setProgress(0.0, animated: false) } - } ``` +## iOS & tvOS +On iOS/tvOS platforms, the player displays system-supplied playback controls by default. + +![iOS system-supplied controls](https://github.com/chriszielinski/Player/raw/master/readme-assets/ios-controls.png) + +![tvOS system-supplied controls](https://github.com/chriszielinski/Player/raw/master/readme-assets/tvos-controls.png) + +These are optional and can be disabled as follows: + +```swift +... +// Need to set before calling `add(to:)` +player.usesSystemPlaybackControls = false +player.add(to: self) +``` + +## macOS +On the macOS platform, the player can display media controls as well. + +```swift +player.controlsStyle = .floating +``` + +The controls' style can be set to the following: + +`.none` + +`.inline` (default) + +![Player](https://github.com/chriszielinski/Player/raw/master/readme-assets/inline.png) + +`.minimal` + +![Player](https://github.com/chriszielinski/Player/raw/master/readme-assets/minimal.png) + +`.floating` + +![Player](https://github.com/chriszielinski/Player/raw/master/readme-assets/floating.png) + ## Documentation You can find [the docs here](http://piemonte.github.io/Player/). Documentation is generated with [jazzy](https://github.com/realm/jazzy) and hosted on [GitHub-Pages](https://pages.github.com). @@ -99,9 +168,13 @@ You can find [the docs here](http://piemonte.github.io/Player/). Documentation i - Need help? Use [Stack Overflow](http://stackoverflow.com/questions/tagged/player-swift) with the tag 'player-swift'. - Questions? Use [Stack Overflow](http://stackoverflow.com/questions/tagged/player-swift) with the tag 'player-swift'. - Found a bug? Open an [issue](https://github.com/piemonte/player/issues). -- Feature idea? Open an [issue](https://github.com/piemonte/player/issues). +- Feature idea? ~~Open an [issue](https://github.com/piemonte/player/issues).~~ Do it yourself & PR when done 😅 (or you can open an issue). - Want to contribute? Submit a [pull request](https://github.com/piemonte/player/pulls). +## Used In + +- [Cards](https://github.com/PaoloCuscela/Cards) — Awesome iOS 11 appstore cards written in Swift. + ## Resources * [Swift Evolution](https://github.com/apple/swift-evolution) @@ -110,6 +183,12 @@ You can find [the docs here](http://piemonte.github.io/Player/). Documentation i * [PBJVision](https://github.com/piemonte/PBJVision), iOS camera engine, features touch-to-record video, slow motion video, and photo capture * [PBJVideoPlayer](https://github.com/piemonte/PBJVideoPlayer), a simple iOS video player library, written in obj-c +## Contributors + +- [Patrick Piemonte](https://github.com/piemonte) — Original author, iOS/tvOS platforms. +- [Addison Brickey](https://github.com/addbrick) — `AVPlayerController` for iOS/tvOS platforms. +- [Chris Zielinski](https://github.com/chriszielinski) — macOS platform. + ## License Player is available under the MIT license, see the [LICENSE](https://github.com/piemonte/player/blob/master/LICENSE) file for more information. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..64753bb --- /dev/null +++ b/Rakefile @@ -0,0 +1,73 @@ +# Based on the Regex Rakefile (https://github.com/sharplet/Regex) + +namespace :build do + desc "Build and validate the podspec" + task :pod do + sh "pod lib lint *.podspec --no-clean" + end + + desc "Build the Carthage frameworks for all platforms" + task :carthage do + sh "carthage build --no-skip-current" + end + + namespace :xcodebuild do + def pretty(scheme) + sh("/bin/sh", "-o", "pipefail", "-c", "env NSUnbufferedIO=YES xcodebuild -workspace Player.xcworkspace -scheme '#{scheme}' -xcconfig $XCCONFIG -configuration $CONFIG -sdk $SDK build analyze | xcpretty") + end + + desc "Build for macOS" + task :macos do + pretty "Release - macOS" + end + + desc "Build for iOS " + task :ios do + pretty "Release - iOS" + end + + desc "Build for tvOS" + task :tvos do + pretty "Release - tvOS" + end + end +end + +namespace :test do + def prettyTest(cmd) + sh("/bin/sh", "-o", "pipefail", "-c", "env NSUnbufferedIO=YES xcodebuild build-for-testing test-without-building -workspace Player.xcworkspace -scheme #{cmd} -xcconfig $XCCONFIG -sdk $SDK | xcpretty") + end + + desc "Run tests on macOS" + task :macos do + prettyTest "'Debug - macOS'" + end + + desc "Run tests on iOS" + task :ios do + prettyTest "'Debug - iOS' -destination 'platform=iOS Simulator,name=iPhone X'" + end + + desc "Run tests on tvOS" + task :tvos do + prettyTest "'Debug - tvOS' -destination 'platform=tvOS Simulator,name=Apple TV'" + end +end + +desc "Run swiftlint if available" +task :swiftlint do + return unless system "which -s swiftlint" + exec "swiftlint lint --reporter emoji" +end + +desc "Clean built products" +task :clean do + Dir["build/", "Carthage/Build/*/Player.framework*"].each do |f| + rm_rf(f) + end +end + +desc "Build all platforms and run SwiftLint" +task :everything => ["build:xcodebuild:macos", "build:xcodebuild:ios", "build:xcodebuild:tvos", "swiftlint"] + +task :default => :everything diff --git a/Sources/Player.swift b/Sources/Player.swift old mode 100644 new mode 100755 index 738ea4b..18c2741 --- a/Sources/Player.swift +++ b/Sources/Player.swift @@ -24,115 +24,133 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import UIKit -import Foundation +#if canImport(AppKit) + import AppKit +#else + import UIKit +#endif import AVFoundation +import AVKit import CoreGraphics -// MARK: - types - -/// Video fill mode options for `Player.fillMode`. -/// -/// - resize: Stretch to fill. -/// - resizeAspectFill: Preserve aspect ratio, filling bounds. -/// - resizeAspectFit: Preserve aspect ratio, fill within bounds. -public enum PlayerFillMode { - case resize - case resizeAspectFill - case resizeAspectFit // default - - public var avFoundationType: String { - get { - switch self { - case .resize: - return AVLayerVideoGravity.resize.rawValue - case .resizeAspectFill: - return AVLayerVideoGravity.resizeAspectFill.rawValue - case .resizeAspectFit: - return AVLayerVideoGravity.resizeAspect.rawValue - } - } - } -} - -/// Asset playback states. -public enum PlaybackState: Int, CustomStringConvertible { - case stopped = 0 - case playing - case paused - case failed - - public var description: String { - get { - switch self { - case .stopped: - return "Stopped" - case .playing: - return "Playing" - case .failed: - return "Failed" - case .paused: - return "Paused" - } - } - } -} - -/// Asset buffering states. -public enum BufferingState: Int, CustomStringConvertible { - case unknown = 0 - case ready - case delayed - - public var description: String { - get { - switch self { - case .unknown: - return "Unknown" - case .ready: - return "Ready" - case .delayed: - return "Delayed" - } - } - } -} - // MARK: - PlayerDelegate /// Player delegate protocol +@objc public protocol PlayerDelegate: NSObjectProtocol { - func playerReady(_ player: Player) - func playerPlaybackStateDidChange(_ player: Player) - func playerBufferingStateDidChange(_ player: Player) - + @objc optional func playerReady(player: Player) + @objc optional func playerPlaybackError(player: Player, error: NSError?) + @objc optional func playerPlaybackStateDidChange(player: Player) + @objc optional func playerBufferingStateDidChange(player: Player) + // This is the time in seconds that the video has been buffered. - // If implementing a UIProgressView, user this value / player.maximumDuration to set progress. - func playerBufferTimeDidChange(_ bufferTime: Double) + // If implementing a UIProgressView, use this value / player.maximumDuration to set progress. + @objc optional func playerBufferTimeDidChange(bufferTime: Double) } +// MARK: - PlayerPlaybackDelegate /// Player playback protocol +@objc public protocol PlayerPlaybackDelegate: NSObjectProtocol { - func playerCurrentTimeDidChange(_ player: Player) - func playerPlaybackWillStartFromBeginning(_ player: Player) - func playerPlaybackDidEnd(_ player: Player) - func playerPlaybackWillLoop(_ player: Player) + @objc optional func playerCurrentTimeDidChange(player: Player) + @objc optional func playerPlaybackWillStartFromBeginning(player: Player) + @objc optional func playerPlaybackDidEnd(player: Player) + @objc optional func playerPlaybackWillLoop(player: Player) +} + +extension String { + var withSentenceCasing: String { + return prefix(1).capitalized + dropFirst() + } } +// MARK: - Type Aliases + +#if canImport(AppKit) +public typealias ViewController = NSViewController +public typealias View = NSView +private typealias PlayerView = AVPlayerView +public typealias Image = NSImage +public typealias Color = NSColor +public typealias SnapshotResult = Image? +public typealias NibName = NSNib.Name? +#else +public typealias ViewController = UIViewController +public typealias View = UIView +private typealias PlayerView = SuperSecretPlayerView? +public typealias Image = UIImage +public typealias Color = UIColor +public typealias SnapshotResult = Image +public typealias NibName = String? +#endif + // MARK: - Player /// ▶️ Player, simple way to play and stream media -open class Player: UIViewController { +open class Player: ViewController { + + // MARK: - Types + + /// Asset playback states. + public enum PlaybackState: String, CustomStringConvertible { + case stopped + case playing + case paused + case failed + + public var description: String { + return rawValue.withSentenceCasing + } + } + + /// Asset buffering states. + public enum BufferingState: String, CustomStringConvertible { + case unknown + case ready + case delayed + + public var description: String { + return rawValue.withSentenceCasing + } + } + + /// Video fill mode options for the `fillMode` property. + public enum FillMode: String, CustomStringConvertible { + /// Specifies that the video should be stretched to fill the layer’s bounds. + case resizeStretch = "AVLayerVideoGravityResize" + /// Specifies that the player should preserve the video’s aspect ratio and fill the layer’s bounds. + case resizeAspectFill = "AVLayerVideoGravityResizeAspectFill" + /// Specifies that the player should preserve the video’s aspect ratio and fit the video within + /// the layer’s bounds. + case resizeAspectFit = "AVLayerVideoGravityResizeAspect" + + private var avLayerVideoGravityValue: AVLayerVideoGravity { + return AVLayerVideoGravity(rawValue: rawValue) + } + + public var description: String { + switch self { + case .resizeStretch: + return "resizeStretch" + case .resizeAspectFill: + return "resizeAspectFill" + case .resizeAspectFit: + return "resizeAspectFit" + } + } + } + + // MARK: - Properties /// Player delegate. open weak var playerDelegate: PlayerDelegate? - + /// Playback delegate. open weak var playbackDelegate: PlayerPlaybackDelegate? - // configuration - + // MARK: Configuration + /// Local or remote URL for the file asset to be played. /// /// - Parameter url: URL of the asset. @@ -141,297 +159,549 @@ open class Player: UIViewController { setup(url: url) } } - + /// Determines if the video should autoplay when a url is set /// /// - Parameter bool: defaults to true - open var autoplay: Bool = true + open var autoplay: Bool = true /// For setting up with AVAsset instead of URL + /// /// Note: Resets URL (cannot set both) open var asset: AVAsset? { - get { return _asset } + get { return avAsset } set { _ = newValue.map { setupAsset($0) } } } - + /// Mutes audio playback when true. open var muted: Bool { get { - return self._avplayer.isMuted + return avPlayer.isMuted } set { - self._avplayer.isMuted = newValue + avPlayer.isMuted = newValue } } - + /// Volume for the player, ranging from 0.0 to 1.0 on a linear scale. open var volume: Float { get { - return self._avplayer.volume + return avPlayer.volume } set { - self._avplayer.volume = newValue + avPlayer.volume = newValue } } /// Specifies how the video is displayed within a player layer’s bounds. - /// The default value is `AVLayerVideoGravityResizeAspect`. See `FillMode` enum. - open var fillMode: String { + /// The default value is `.resizeAspectFit`. See the `FillMode` enum. + /// + /// Note: On iOS, this property is ignored if using system-supplied playback controls. + /// + open var fillMode: FillMode { get { - return self._playerView.fillMode + #if canImport(AppKit) + return FillMode(rawValue: playerView.videoGravity.rawValue)! + #else + if let playerViewController = avPlayerViewController { + return FillMode(rawValue: convertFromAVLayerVideoGravity(playerViewController.videoGravity))! + } + + return FillMode(rawValue: playerView!.fillMode.rawValue)! + #endif + } + set { + #if canImport(AppKit) + playerView.videoGravity = AVLayerVideoGravity(rawValue: newValue.rawValue) + #else + if let playerViewController = avPlayerViewController { + playerViewController.videoGravity = convertToAVLayerVideoGravity(newValue.rawValue) + } else { + playerView!.fillMode = newValue.avLayerVideoGravityValue + } + #endif + } + } + + /// Player view's initial background color. + open var layerBackgroundColor: Color? { + get { + var color: Color? + #if canImport(AppKit) + if let backgroundColor = playerView.layer?.backgroundColor { + color = Color(cgColor: backgroundColor) + } + #else + if let avPlayerViewController = avPlayerViewController { + color = avPlayerViewController.view.backgroundColor + } else { + color = playerView!.playerBackgroundColor + } + #endif + + return color } set { - self._playerView.fillMode = newValue + #if canImport(AppKit) + playerView.layer?.backgroundColor = newValue?.cgColor + #else + if let playerViewController = avPlayerViewController { + playerViewController.view.backgroundColor = newValue + } else { + playerView!.playerBackgroundColor = newValue + } + #endif } } + #if canImport(AppKit) + /// The player view’s controls style. + /// + /// The player view supports a number of different control styles that you can use to customize the player + /// view’s appearance and behavior. See `AVPlayerViewControlsStyle` for the possible values. + /// The default value of this property is `.default` + /// + /// - Important: Only available on the macOS platform. + /// + open var controlsStyle: AVPlayerViewControlsStyle { + get { + return playerView.controlsStyle + } + set { + playerView.controlsStyle = newValue + } + } + #else + /// A Boolean value that indicates whether the player shows playback controls. + /// This property has a default value of `true`. + /// + /// - Note: Only available on iOS/tvOS platforms. For macOS, see `controlsStyle`. + /// + /// - Important: Set this property **before** calling `add(to:)`. Setting it after will have no effect. + /// + open var usesSystemPlaybackControls: Bool = true + #endif + /// Pauses playback automatically when resigning active. + /// + /// The default value of this property is `true`. open var playbackPausesWhenResigningActive: Bool = true - - /// Pauses playback automatically when backgrounded. + + /// Pauses playback automatically when backgrounded (on macOS, when hidden). + /// + /// The default value of this property is `true`. open var playbackPausesWhenBackgrounded: Bool = true - + /// Resumes playback when became active. + /// + /// The default value of this property is `true`. open var playbackResumesWhenBecameActive: Bool = true - /// Resumes playback when entering foreground. + /// Resumes playback when entering foreground. (on macOS, when unhidden) + /// + /// The default value of this property is `true`. open var playbackResumesWhenEnteringForeground: Bool = true - - // state + + // MARK: State + + /// Whether the player is currently playing. + /// Returns `true` if the `playbackState` is `.playing`. + /// This property is key-value observable. + @objc + open dynamic var isPlaying: Bool { + return playbackState == .playing + } + + class func keyPathsForValuesAffectingIsPlaying() -> Set { + return ["playbackState"] + } /// Playback automatically loops continuously when true. open var playbackLoops: Bool { get { - return self._avplayer.actionAtItemEnd == .none + return avPlayer.actionAtItemEnd == .none } set { if newValue { - self._avplayer.actionAtItemEnd = .none + avPlayer.actionAtItemEnd = .none } else { - self._avplayer.actionAtItemEnd = .pause + avPlayer.actionAtItemEnd = .pause } } } /// Playback freezes on last frame frame at end when true. - open var playbackFreezesAtEnd: Bool = false + /// + /// The default value of this property is `false`. + open var playbackFreezesAtEnd: Bool = false { + didSet { + if playbackFreezesAtEnd { + avPlayer.actionAtItemEnd = .pause + } + } + } /// Current playback state of the Player. open var playbackState: PlaybackState = .stopped { didSet { if playbackState != oldValue || !playbackEdgeTriggered { - self.playerDelegate?.playerPlaybackStateDidChange(self) + playerDelegate?.playerPlaybackStateDidChange?(player: self) } } } - + /// Current buffering state of the Player. open var bufferingState: BufferingState = .unknown { - didSet { + didSet { if bufferingState != oldValue || !playbackEdgeTriggered { - self.playerDelegate?.playerBufferingStateDidChange(self) + playerDelegate?.playerBufferingStateDidChange?(player: self) } } } /// Playback buffering size in seconds. open var bufferSize: Double = 10 - + /// Playback is not automatically triggered from state changes when true. open var playbackEdgeTriggered: Bool = true /// Maximum duration of playback. open var maximumDuration: TimeInterval { - get { - if let playerItem = self._playerItem { - return CMTimeGetSeconds(playerItem.duration) - } else { - return CMTimeGetSeconds(kCMTimeIndefinite) - } + if let playerItem = avPlayerItem { + return CMTimeGetSeconds(playerItem.duration) + } else { + return CMTimeGetSeconds(CMTime.indefinite) } } /// Media playback's current time. open var currentTime: TimeInterval { - get { - if let playerItem = self._playerItem { - return CMTimeGetSeconds(playerItem.currentTime()) - } else { - return CMTimeGetSeconds(kCMTimeIndefinite) - } + if let playerItem = avPlayerItem { + return CMTimeGetSeconds(playerItem.currentTime()) + } else { + return CMTimeGetSeconds(CMTime.indefinite) } } /// The natural dimensions of the media. - open var naturalSize: CGSize { - get { - if let playerItem = self._playerItem, - let track = playerItem.asset.tracks(withMediaType: .video).first { - - let size = track.naturalSize.applying(track.preferredTransform) - return CGSize(width: fabs(size.width), height: fabs(size.height)) - } else { - return CGSize.zero - } + /// + /// - Note: The `avPlayerItem` must exist and have had its tracks loaded. + /// + open var naturalSize: CGSize? { + if let playerItem = avPlayerItem, + let track = playerItem.asset.tracks(withMediaType: .video).first { + let size = track.naturalSize.applying(track.preferredTransform) + return CGSize(width: abs(size.width), height: abs(size.height)) } + return nil } - /// Player view's initial background color. - open var layerBackgroundColor: UIColor? { - get { - guard let backgroundColor = self._playerView.playerLayer.backgroundColor - else { - return nil - } - return UIColor(cgColor: backgroundColor) - } - set { - self._playerView.playerLayer.backgroundColor = newValue?.cgColor + // MARK: Public Objects + + public var avPlayer: AVPlayer + public var avPlayerItem: AVPlayerItem? + public var avPlayerLayer: AVPlayerLayer? { + #if canImport(AppKit) + return macPlayerLayer + #else + if let playerViewController = avPlayerViewController { + return playerViewController.view.layer.sublayers?.first(where: { $0 is AVPlayerLayer }) as? AVPlayerLayer + } else { + return playerView!.playerLayer } + #endif } - - // MARK: - private instance vars - - internal var _asset: AVAsset? { + #if canImport(UIKit) + public var avPlayerViewController: AVPlayerViewController? + #endif + + // MARK: Private Objects + + private var avAsset: AVAsset? { didSet { - if let _ = self._asset { - self.setupPlayerItem(nil) + if avAsset != nil { + setupPlayerItem(nil) } } } - internal var _avplayer: AVPlayer - internal var _playerItem: AVPlayerItem? - internal var _timeObserver: Any? - - internal var _playerView: PlayerView = PlayerView(frame: .zero) - internal var _seekTimeRequested: CMTime? - - internal var _lastBufferTime: Double = 0 - - //Boolean that determines if the user or calling coded has trigged autoplay manually. - internal var _hasAutoplayActivated: Bool = true - - // MARK: - object lifecycle + + private var timeObserver: Any? + private var playerView: PlayerView + private var seekTimeRequested: CMTime? + private var lastBufferTime: Double = 0 + + // Boolean that determines if the user or calling coded has trigged autoplay manually. + private var hasAutoplayActivated: Bool = true + + #if canImport(AppKit) + private weak var macPlayerLayer: AVPlayerLayer? + private var playerViewObservation: NSKeyValueObservation! + #endif + + // MARK: - Object Lifecycle public convenience init() { self.init(nibName: nil, bundle: nil) } public required init?(coder aDecoder: NSCoder) { - self._avplayer = AVPlayer() - self._avplayer.actionAtItemEnd = .pause - self._timeObserver = nil - + avPlayer = AVPlayer() + #if canImport(AppKit) + playerView = PlayerView() + #else + playerView = SuperSecretPlayerView() + #endif + super.init(coder: aDecoder) + + sharedInit() } - public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - self._avplayer = AVPlayer() - self._avplayer.actionAtItemEnd = .pause - self._timeObserver = nil - + public override init(nibName nibNameOrNil: NibName, bundle nibBundleOrNil: Bundle?) { + avPlayer = AVPlayer() + #if canImport(AppKit) + playerView = PlayerView() + #else + playerView = SuperSecretPlayerView() + #endif + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + sharedInit() + } + + private func sharedInit() { + avPlayer.actionAtItemEnd = .pause + timeObserver = nil + fillMode = .resizeAspectFit + + #if canImport(AppKit) + playerView.player = avPlayer + playerView.controlsStyle = .default + #endif } deinit { - self._avplayer.pause() - self.setupPlayerItem(nil) + avPlayer.pause() + setupPlayerItem(nil) + + removePlayerObservers() + + playerDelegate = nil + removeApplicationObservers() - self.removePlayerObservers() + playbackDelegate = nil + removePlayerLayerObservers() - self.playerDelegate = nil - self.removeApplicationObservers() - - self.playbackDelegate = nil - self.removePlayerLayerObservers() - self._playerView.player = nil + #if canImport(AppKit) + playerView.player = nil + #else + if let playerViewController = avPlayerViewController { + playerViewController.player = nil + avPlayerViewController = nil + } else { + playerView!.player = nil + playerView = nil + } + #endif + } + + /// Adds a player to the given view controller. + /// The player will be added to `viewController`'s `childViewControllers` array and its view hierarchy. + /// + /// - Important: On iOS/tvOS platforms, `usesSystemPlaybackControls` must be set prior to calling this method. + /// + /// - Parameters: + /// - viewController: The parent view controller that the player will be added to. + open func add(to viewController: ViewController) { + add(to: viewController, view: viewController.view) + } + + /// Adds a player to the given view controller. + /// The player will be added to `viewController`'s `childViewControllers` array. + /// If `view` is provided, the player will be added to it as a subview. + /// + /// - Important: On iOS/tvOS platforms, `usesSystemPlaybackControls` must be set prior to calling this method. + /// + /// - Parameters: + /// - viewController: The parent view controller that the player will be added to. + /// - view: The view that the player will be added to. If `nil`, you are + /// responsible for adding it to the view hierarchy yourself. + open func add(to viewController: ViewController, view: View? = nil) { + viewController.addChild(self) + + #if canImport(UIKit) + if usesSystemPlaybackControls { + let playerViewController = AVPlayerViewController() + + addChild(playerViewController) + playerViewController.didMove(toParent: self) + + playerViewController.view.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(playerViewController.view) + + NSLayoutConstraint.activate([ + playerViewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + playerViewController.view.topAnchor.constraint(equalTo: self.view.topAnchor), + playerViewController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), + playerViewController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) + ]) + + avPlayerViewController = playerViewController + } else { + playerView!.playerIsHidden = false + playerView!.frame = self.view.frame + self.view = playerView + } + + didMove(toParent: viewController) + #endif + + addPlayerLayerObservers() + + if let parentView = view { + parentView.addSubview(self.view) + } } - // MARK: - view lifecycle + /// Removes the player from the given view controller. + /// The player will be removed from `viewController`'s `childViewControllers` array and its view hierarchy. + /// + /// - Parameter viewController: The parent view controller that the player will be removed from. + open func remove(from viewController: ViewController) { + #if canImport(UIKit) + willMove(toParent: self) + #endif + + view.removeFromSuperview() + removeFromParent() + } + + // MARK: - View Lifecycle open override func loadView() { - self._playerView.playerLayer.isHidden = true - self.view = self._playerView + #if canImport(AppKit) + view = playerView + #else + super.loadView() + #endif } - + open override func viewDidLoad() { super.viewDidLoad() - + if let url = url { setup(url: url) } else if let asset = asset { setupAsset(asset) } - - self.addPlayerLayerObservers() - self.addPlayerObservers() - self.addApplicationObservers() + + addPlayerObservers() + addApplicationObservers() } - open override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) + #if canImport(AppKit) + open override func viewDidDisappear() { + super.viewDidDisappear() - if self.playbackState == .playing { - self.pause() + if playbackState == .playing { + pause() + } } - } - - // MARK: - Playback funcs + + #else + open override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + if playbackState == .playing { + pause() + } + } + #endif + + // MARK: - Playback Methods + + #if canImport(UIKit) + open func playerViewSet(player: AVPlayer) { + if let playerViewController = avPlayerViewController { + playerViewController.player = player + playerViewController.view.isHidden = false + } else { + playerView!.player = player + playerView!.playerIsHidden = false + } + } + #endif /// Begins playback of the media from the beginning. open func playFromBeginning() { - self.playbackDelegate?.playerPlaybackWillStartFromBeginning(self) - self._avplayer.seek(to: kCMTimeZero) - self.playFromCurrentTime() + playbackDelegate?.playerPlaybackWillStartFromBeginning?(player: self) + avPlayer.seek(to: CMTime.zero) + playFromCurrentTime() } /// Begins playback of the media from the current time. open func playFromCurrentTime() { if !autoplay { - //external call to this method with auto play off. activate it before calling play - _hasAutoplayActivated = true + // External call to this method with auto play off. Activate it before calling play + hasAutoplayActivated = true } play() } - - fileprivate func play() { - if autoplay || _hasAutoplayActivated { - self.playbackState = .playing - self._avplayer.play() + + private func play() { + if autoplay || hasAutoplayActivated { + playbackState = .playing + avPlayer.play() } } /// Pauses playback of the media. open func pause() { - if self.playbackState != .playing { + if playbackState != .playing { return } - self._avplayer.pause() - self.playbackState = .paused + avPlayer.pause() + playbackState = .paused } /// Stops playback of the media. open func stop() { - if self.playbackState == .stopped { + if playbackState == .stopped { return } - self._avplayer.pause() - self.playbackState = .stopped - self.playbackDelegate?.playerPlaybackDidEnd(self) + avPlayer.pause() + playbackState = .stopped + playbackDelegate?.playerPlaybackDidEnd?(player: self) } - + /// Updates playback to the specified time. /// /// - Parameters: /// - time: The time to switch to move the playback. /// - completionHandler: Call block handler after seeking/ open func seek(to time: CMTime, completionHandler: ((Bool) -> Swift.Void)? = nil) { - if let playerItem = self._playerItem { + if let playerItem = avPlayerItem { return playerItem.seek(to: time, completionHandler: completionHandler) } else { - _seekTimeRequested = time + seekTimeRequested = time + } + } + + /// Sets the current playback time to the specified second mark and executes the specified block when the seek + /// operation completes or is interrupted. + /// + /// - Parameters: + /// - time: The time (in seconds) to seek to. + /// - completionHandler: Call block handler after seeking. + open func seek(toSecond second: Int, completionHandler: ((Bool) -> Swift.Void)? = nil) { + let cmTime = CMTimeMake(value: Int64(second), timescale: 1) + if let completionHandler = completionHandler { + avPlayer.seek(to: cmTime, completionHandler: completionHandler) + } else { + avPlayer.seek(to: cmTime) } } @@ -442,424 +712,613 @@ open class Player: UIViewController { /// - toleranceBefore: The tolerance allowed before time. /// - toleranceAfter: The tolerance allowed after time. /// - completionHandler: call block handler after seeking - open func seekToTime(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime, completionHandler: ((Bool) -> Swift.Void)? = nil) { - if let playerItem = self._playerItem { - return playerItem.seek(to: time, toleranceBefore: toleranceBefore, toleranceAfter: toleranceAfter, completionHandler: completionHandler) + open func seekToTime(to time: CMTime, + toleranceBefore: CMTime, + toleranceAfter: CMTime, + completionHandler: ((Bool) -> Swift.Void)? = nil) { + if let playerItem = avPlayerItem { + return playerItem.seek(to: time, + toleranceBefore: toleranceBefore, + toleranceAfter: toleranceAfter, + completionHandler: completionHandler) } } - - /// Captures a snapshot of the current Player view. + + /// Captures a snapshot of the current player view. /// - /// - Returns: A UIImage of the player view. - open func takeSnapshot() -> UIImage { - UIGraphicsBeginImageContextWithOptions(self._playerView.frame.size, false, UIScreen.main.scale) - self._playerView.drawHierarchy(in: self._playerView.bounds, afterScreenUpdates: true) - let image = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return image! - } + /// - Returns: A image of the player view. + open func takeSnapshot() -> SnapshotResult { + var image: SnapshotResult + #if canImport(AppKit) + image = nil + + if let playerItem = avPlayerItem { + let imageGenerator = AVAssetImageGenerator(asset: playerItem.asset) + if let cgImage = try? imageGenerator.copyCGImage(at: playerItem.currentTime(), actualTime: nil) { + image = NSImage(cgImage: cgImage, size: playerView.visibleRect.size) + } + } + #else + if let playerViewController = avPlayerViewController { + UIGraphicsBeginImageContextWithOptions(playerViewController.view.frame.size, false, UIScreen.main.scale) + playerViewController.view.drawHierarchy(in: playerViewController.view.bounds, afterScreenUpdates: true) + } else { + UIGraphicsBeginImageContextWithOptions(playerView!.frame.size, false, UIScreen.main.scale) + playerView!.drawHierarchy(in: playerView!.bounds, afterScreenUpdates: true) + } + image = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + #endif - /// Return the av player layer for consumption by - /// things such as Picture in Picture - open func playerLayer() -> AVPlayerLayer? { - return self._playerView.playerLayer + return image } } -// MARK: - loading funcs +// MARK: - Setup Methods -extension Player { - - fileprivate func setup(url: URL?) { +private extension Player { + func setup(url: URL?) { guard isViewLoaded else { return } - + // ensure everything is reset beforehand - if self.playbackState == .playing { - self.pause() + if playbackState == .playing { + pause() } - - //Reset autoplay flag since a new url is set. - _hasAutoplayActivated = false + + // Reset autoplay flag since a new url is set. + hasAutoplayActivated = false + if autoplay { playbackState = .playing } else { playbackState = .stopped } - - self.setupPlayerItem(nil) - + + setupPlayerItem(nil) + if let url = url { let asset = AVURLAsset(url: url, options: .none) - self.setupAsset(asset) + setupAsset(asset) } } - fileprivate func setupAsset(_ asset: AVAsset) { + func setupAsset(_ asset: AVAsset) { guard isViewLoaded else { return } - - if self.playbackState == .playing { - self.pause() - } - self.bufferingState = .unknown + if playbackState == .playing { + pause() + } - self._asset = asset + bufferingState = .unknown + avAsset = asset - let keys = [PlayerTracksKey, PlayerPlayableKey, PlayerDurationKey] - self._asset?.loadValuesAsynchronously(forKeys: keys, completionHandler: { () -> Void in + let keys = [AssetTracksKey, AssetPlayableKey, AssetDurationKey, AssetRateKey] + avAsset?.loadValuesAsynchronously(forKeys: keys) { () -> Void in for key in keys { - var error: NSError? = nil - let status = self._asset?.statusOfValue(forKey: key, error:&error) + var error: NSError? + let status = self.avAsset?.statusOfValue(forKey: key, error: &error) if status == .failed { + self.playerDelegate?.playerPlaybackError?(player: self, error: error) self.playbackState = .failed return } } - if let asset = self._asset { + if let asset = self.avAsset { if !asset.isPlayable { self.playbackState = .failed return } - - let playerItem = AVPlayerItem(asset:asset) + + let playerItem = AVPlayerItem(asset: asset) self.setupPlayerItem(playerItem) } - }) + } } - fileprivate func setupPlayerItem(_ playerItem: AVPlayerItem?) { - self._playerItem?.removeObserver(self, forKeyPath: PlayerEmptyBufferKey, context: &PlayerItemObserverContext) - self._playerItem?.removeObserver(self, forKeyPath: PlayerKeepUpKey, context: &PlayerItemObserverContext) - self._playerItem?.removeObserver(self, forKeyPath: PlayerStatusKey, context: &PlayerItemObserverContext) - self._playerItem?.removeObserver(self, forKeyPath: PlayerLoadedTimeRangesKey, context: &PlayerItemObserverContext) - - if let currentPlayerItem = self._playerItem { - NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: currentPlayerItem) - NotificationCenter.default.removeObserver(self, name: .AVPlayerItemFailedToPlayToEndTime, object: currentPlayerItem) + func setupPlayerItem(_ playerItem: AVPlayerItem?) { + avPlayerItem?.removeObserver(self, + forKeyPath: PlayerItemEmptyBufferKey, + context: &PlayerItemObserverContext) + avPlayerItem?.removeObserver(self, + forKeyPath: PlayerItemKeepUpKey, + context: &PlayerItemObserverContext) + avPlayerItem?.removeObserver(self, + forKeyPath: PlayerItemStatusKey, + context: &PlayerItemObserverContext) + avPlayerItem?.removeObserver(self, + forKeyPath: PlayerItemLoadedTimeRangesKey, + context: &PlayerItemObserverContext) + + if let currentPlayerItem = avPlayerItem { + NotificationCenter.default.removeObserver(self, + name: .AVPlayerItemDidPlayToEndTime, + object: currentPlayerItem) + NotificationCenter.default.removeObserver(self, + name: .AVPlayerItemFailedToPlayToEndTime, + object: currentPlayerItem) } - self._playerItem = playerItem + avPlayerItem = playerItem - if let seek = _seekTimeRequested, self._playerItem != nil { - _seekTimeRequested = nil - self.seek(to: seek) + if let requestedSeekTime = seekTimeRequested, avPlayerItem != nil { + seekTimeRequested = nil + seek(to: requestedSeekTime) } - self._playerItem?.addObserver(self, forKeyPath: PlayerEmptyBufferKey, options: [.new, .old], context: &PlayerItemObserverContext) - self._playerItem?.addObserver(self, forKeyPath: PlayerKeepUpKey, options: [.new, .old], context: &PlayerItemObserverContext) - self._playerItem?.addObserver(self, forKeyPath: PlayerStatusKey, options: [.new, .old], context: &PlayerItemObserverContext) - self._playerItem?.addObserver(self, forKeyPath: PlayerLoadedTimeRangesKey, options: [.new, .old], context: &PlayerItemObserverContext) - - if let updatedPlayerItem = self._playerItem { - NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidPlayToEndTime(_:)), name: .AVPlayerItemDidPlayToEndTime, object: updatedPlayerItem) - NotificationCenter.default.addObserver(self, selector: #selector(playerItemFailedToPlayToEndTime(_:)), name: .AVPlayerItemFailedToPlayToEndTime, object: updatedPlayerItem) + avPlayerItem?.addObserver(self, + forKeyPath: PlayerItemEmptyBufferKey, + options: [.new, .old], + context: &PlayerItemObserverContext) + avPlayerItem?.addObserver(self, + forKeyPath: PlayerItemKeepUpKey, + options: [.new, .old], + context: &PlayerItemObserverContext) + avPlayerItem?.addObserver(self, + forKeyPath: PlayerItemStatusKey, + options: [.new, .old], + context: &PlayerItemObserverContext) + avPlayerItem?.addObserver(self, + forKeyPath: PlayerItemLoadedTimeRangesKey, + options: [.new, .old], + context: &PlayerItemObserverContext) + + if let updatedPlayerItem = avPlayerItem { + NotificationCenter.default.addObserver(self, + selector: #selector(playerItemDidPlayToEndTime(_:)), + name: .AVPlayerItemDidPlayToEndTime, + object: updatedPlayerItem) + NotificationCenter.default.addObserver(self, + selector: #selector(playerItemFailedToPlayToEndTime(_:)), + name: .AVPlayerItemFailedToPlayToEndTime, + object: updatedPlayerItem) } - self._avplayer.replaceCurrentItem(with: self._playerItem) - + avPlayer.replaceCurrentItem(with: avPlayerItem) + // update new playerItem settings - if self.playbackLoops { - self._avplayer.actionAtItemEnd = .none + if playbackLoops { + avPlayer.actionAtItemEnd = .none } else { - self._avplayer.actionAtItemEnd = .pause + avPlayer.actionAtItemEnd = .pause } } - } -// MARK: - NSNotifications +// MARK: - Notifications -extension Player { - - // MARK: - AVPlayerItem - - @objc internal func playerItemDidPlayToEndTime(_ aNotification: Notification) { - if self.playbackLoops { - self.playbackDelegate?.playerPlaybackWillLoop(self) - self._avplayer.seek(to: kCMTimeZero) +private extension Player { + + // MARK: AVPlayerItem + + @objc + func playerItemDidPlayToEndTime(_ aNotification: Notification) { + if playbackLoops { + playbackDelegate?.playerPlaybackWillLoop?(player: self) + avPlayer.seek(to: CMTime.zero) } else { - if self.playbackFreezesAtEnd { - self.stop() + if playbackFreezesAtEnd { + stop() } else { - self._avplayer.seek(to: kCMTimeZero, completionHandler: { _ in + avPlayer.seek(to: CMTime.zero) { _ in self.stop() - }) + } } } } - @objc internal func playerItemFailedToPlayToEndTime(_ aNotification: Notification) { - self.playbackState = .failed + @objc + func playerItemFailedToPlayToEndTime(_ aNotification: Notification) { + playbackState = .failed } - - // MARK: - UIApplication - - internal func addApplicationObservers() { - NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationWillResignActive(_:)), name: .UIApplicationWillResignActive, object: UIApplication.shared) - NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidBecomeActive(_:)), name: .UIApplicationDidBecomeActive, object: UIApplication.shared) - NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidEnterBackground(_:)), name: .UIApplicationDidEnterBackground, object: UIApplication.shared) - NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationWillEnterForeground(_:)), name: .UIApplicationWillEnterForeground, object: UIApplication.shared) + + // MARK: UIApplication/NSApplication + + func addApplicationObservers() { + #if canImport(AppKit) + NotificationCenter.default.addObserver(self, + selector: #selector(handleApplicationWillResignActive(_:)), + name: NSApplication.willResignActiveNotification, + object: NSApp) + NotificationCenter.default.addObserver(self, + selector: #selector(handleApplicationDidBecomeActive(_:)), + name: NSApplication.didBecomeActiveNotification, + object: NSApp) + NotificationCenter.default.addObserver(self, + selector: #selector(handleApplicationDidEnterBackground(_:)), + name: NSApplication.didHideNotification, + object: NSApp) + NotificationCenter.default.addObserver(self, + selector: #selector(handleApplicationWillEnterForeground(_:)), + name: NSApplication.willUnhideNotification, + object: NSApp) + #else + NotificationCenter.default.addObserver(self, + selector: #selector(handleApplicationWillResignActive(_:)), + name: UIApplication.willResignActiveNotification, + object: UIApplication.shared) + NotificationCenter.default.addObserver(self, + selector: #selector(handleApplicationDidBecomeActive(_:)), + name: UIApplication.didBecomeActiveNotification, + object: UIApplication.shared) + NotificationCenter.default.addObserver(self, + selector: #selector(handleApplicationDidEnterBackground(_:)), + name: UIApplication.didEnterBackgroundNotification, + object: UIApplication.shared) + NotificationCenter.default.addObserver(self, + selector: #selector(handleApplicationWillEnterForeground(_:)), + name: UIApplication.willEnterForegroundNotification, + object: UIApplication.shared) + #endif } - - internal func removeApplicationObservers() { + + func removeApplicationObservers() { NotificationCenter.default.removeObserver(self) } - - // MARK: - handlers - - @objc internal func handleApplicationWillResignActive(_ aNotification: Notification) { - if self.playbackState == .playing && self.playbackPausesWhenResigningActive { - self.pause() + + // MARK: Notification Handlers + + @objc + func handleApplicationWillResignActive(_ aNotification: Notification) { + if playbackState == .playing && playbackPausesWhenResigningActive { + pause() } } - @objc internal func handleApplicationDidBecomeActive(_ aNotification: Notification) { - if self.playbackState != .playing && self.playbackResumesWhenBecameActive { - self.play() + @objc + func handleApplicationDidBecomeActive(_ aNotification: Notification) { + if playbackState != .playing && playbackResumesWhenBecameActive { + play() } } - @objc internal func handleApplicationDidEnterBackground(_ aNotification: Notification) { - if self.playbackState == .playing && self.playbackPausesWhenBackgrounded { - self.pause() + @objc + func handleApplicationDidEnterBackground(_ aNotification: Notification) { + if playbackState == .playing && playbackPausesWhenBackgrounded { + pause() } } - - @objc internal func handleApplicationWillEnterForeground(_ aNoticiation: Notification) { - if self.playbackState != .playing && self.playbackResumesWhenEnteringForeground { - self.play() + + @objc + func handleApplicationWillEnterForeground(_ aNoticiation: Notification) { + if playbackState != .playing && playbackResumesWhenEnteringForeground { + play() } } - } // MARK: - KVO +// swiftlint:disable identifier_name + // KVO contexts +private var PlayerObserverContext: Int? +private var PlayerItemObserverContext: Int? +private var PlayerLayerObserverContext: Int? -private var PlayerObserverContext = 0 -private var PlayerItemObserverContext = 0 -private var PlayerLayerObserverContext = 0 +// KVO asset keys +private let AssetTracksKey = #keyPath(AVAsset.tracks) +private let AssetPlayableKey = #keyPath(AVAsset.isPlayable) +private let AssetDurationKey = #keyPath(AVAsset.duration) +private let AssetRateKey = #keyPath(AVAsset.preferredRate) + +// KVO player item keys +private let PlayerItemStatusKey = #keyPath(AVPlayerItem.status) +private let PlayerItemEmptyBufferKey = #keyPath(AVPlayerItem.playbackBufferEmpty) +private let PlayerItemKeepUpKey = #keyPath(AVPlayerItem.playbackLikelyToKeepUp) +private let PlayerItemLoadedTimeRangesKey = #keyPath(AVPlayerItem.loadedTimeRanges) // KVO player keys +private let PlayerRateKey = #keyPath(AVPlayer.rate) -private let PlayerTracksKey = "tracks" -private let PlayerPlayableKey = "playable" -private let PlayerDurationKey = "duration" -private let PlayerRateKey = "rate" +// KVO player layer keys +private let PlayerLayerReadyForDisplayKey = #keyPath(AVPlayerLayer.isReadyForDisplay) -// KVO player item keys +// swiftlint:enable identifier_name -private let PlayerStatusKey = "status" -private let PlayerEmptyBufferKey = "playbackBufferEmpty" -private let PlayerKeepUpKey = "playbackLikelyToKeepUp" -private let PlayerLoadedTimeRangesKey = "loadedTimeRanges" +private extension Player { -// KVO player layer keys + // MARK: AVPlayerViewObservers + + func addPlayerLayerObservers() { + #if canImport(AppKit) + // Should work, but doesn't... radar://41298723 +// playerView.addObserver(self, +// forKeyPath: #keyPath(AVPlayerView.isReadyForDisplay), +// context: &PlayerLayerObserverContext) + + // Current workaround. + playerViewObservation = playerView.observe(\.layer) { [weak self] playerView, _ in + if let macPlayerLayer = playerView.layer?.sublayers? + .first(where: { $0 is AVPlayerLayer }) as? AVPlayerLayer, + let strongSelf = self { + + strongSelf.playerViewObservation.invalidate() + macPlayerLayer.addObserver(strongSelf, + forKeyPath: PlayerLayerReadyForDisplayKey, + context: &PlayerLayerObserverContext) + strongSelf.macPlayerLayer = macPlayerLayer + } + } + #else + if let playerViewController = avPlayerViewController { + playerViewController.addObserver(self, + forKeyPath: PlayerLayerReadyForDisplayKey, + options: [.new, .old], + context: &PlayerLayerObserverContext) + } else { + playerView!.layer.addObserver(self, + forKeyPath: PlayerLayerReadyForDisplayKey, + options: [.new, .old], + context: &PlayerLayerObserverContext) + } + #endif + } + + func removePlayerLayerObservers() { + #if canImport(AppKit) + macPlayerLayer?.removeObserver(self, + forKeyPath: PlayerLayerReadyForDisplayKey, + context: &PlayerLayerObserverContext) + #else + if let playerViewController = avPlayerViewController { + playerViewController.removeObserver(self, + forKeyPath: PlayerLayerReadyForDisplayKey, + context: &PlayerLayerObserverContext) + } else { + playerView!.layer.removeObserver(self, + forKeyPath: PlayerLayerReadyForDisplayKey, + context: &PlayerLayerObserverContext) + } + #endif + } + + // MARK: AVPlayerObservers + + func addPlayerObservers() { + timeObserver = avPlayer.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 100), + queue: DispatchQueue.main) { [weak self] _ in + guard let strongSelf = self else { return } + strongSelf.playbackDelegate?.playerCurrentTimeDidChange?(player: strongSelf) + } + avPlayer.addObserver(self, + forKeyPath: PlayerRateKey, + options: [.new, .old], + context: &PlayerObserverContext) + } -private let PlayerReadyForDisplayKey = "readyForDisplay" + func removePlayerObservers() { + if let observer = timeObserver { + avPlayer.removeTimeObserver(observer) + } + avPlayer.removeObserver(self, forKeyPath: PlayerRateKey, context: &PlayerObserverContext) + } +} extension Player { - - // MARK: - AVPlayerLayerObservers - - internal func addPlayerLayerObservers() { - self._playerView.layer.addObserver(self, forKeyPath: PlayerReadyForDisplayKey, options: [.new, .old], context: &PlayerLayerObserverContext) - } - - internal func removePlayerLayerObservers() { - self._playerView.layer.removeObserver(self, forKeyPath: PlayerReadyForDisplayKey, context: &PlayerLayerObserverContext) - } - - // MARK: - AVPlayerObservers - - internal func addPlayerObservers() { - self._timeObserver = self._avplayer.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 100), queue: DispatchQueue.main, using: { [weak self] timeInterval in - guard let strongSelf = self - else { - return - } - strongSelf.playbackDelegate?.playerCurrentTimeDidChange(strongSelf) - }) - self._avplayer.addObserver(self, forKeyPath: PlayerRateKey, options: [.new, .old], context: &PlayerObserverContext) - } - - internal func removePlayerObservers() { - if let observer = self._timeObserver { - self._avplayer.removeTimeObserver(observer) - } - self._avplayer.removeObserver(self, forKeyPath: PlayerRateKey, context: &PlayerObserverContext) - } - - // MARK: - - - override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { - - // PlayerRateKey, PlayerObserverContext - + private func observeStatus(change: [NSKeyValueChangeKey: Any]?) { + if let status = change?[NSKeyValueChangeKey.newKey] as? NSNumber { + switch status.intValue as AVPlayer.Status.RawValue { + #if canImport(UIKit) + case AVPlayer.Status.readyToPlay.rawValue: + playerViewSet(player: avPlayer) + #endif + case AVPlayer.Status.failed.rawValue: + playbackState = PlaybackState.failed + default: + break + } + } + } + + // TODO:👇 Refactor to block based KVO (will also fix the cyclomatic complexity). + // swiftlint:disable next block_based_kvo cyclomatic_complexity + open override func observeValue(forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey: Any]?, + context: UnsafeMutableRawPointer?) { + // AssetRateKey, PlayerObserverContext if context == &PlayerItemObserverContext { - - // PlayerStatusKey - - if keyPath == PlayerKeepUpKey { - - // PlayerKeepUpKey - - if let item = self._playerItem { - - if item.isPlaybackLikelyToKeepUp { - self.bufferingState = .ready - if self.playbackState == .playing { - self.playFromCurrentTime() + // PlayerItemStatusKey + if keyPath == PlayerItemKeepUpKey { + // PlayerItemKeepUpKey + if avPlayerItem?.isPlaybackLikelyToKeepUp ?? false { + bufferingState = .ready + + // Don't want this on macOS (not only does `AVPlayerView` handle it for us, this causes + // unwanted interaction with keyboard shortcuts for controlling playback). + #if canImport(UIKit) + if playbackState == .playing { + playFromCurrentTime() } - } - } - - if let status = change?[NSKeyValueChangeKey.newKey] as? NSNumber { - switch status.intValue as AVPlayerStatus.RawValue { - case AVPlayerStatus.readyToPlay.rawValue: - self._playerView.playerLayer.player = self._avplayer - self._playerView.playerLayer.isHidden = false - case AVPlayerStatus.failed.rawValue: - self.playbackState = PlaybackState.failed - default: - break - } + #endif } - - } else if keyPath == PlayerEmptyBufferKey { - - // PlayerEmptyBufferKey - - if let item = self._playerItem { - if item.isPlaybackBufferEmpty { - self.bufferingState = .delayed - } - } - - if let status = change?[NSKeyValueChangeKey.newKey] as? NSNumber { - switch status.intValue as AVPlayerStatus.RawValue { - case AVPlayerStatus.readyToPlay.rawValue: - self._playerView.playerLayer.player = self._avplayer - self._playerView.playerLayer.isHidden = false - case AVPlayerStatus.failed.rawValue: - self.playbackState = PlaybackState.failed - default: - break - } + + observeStatus(change: change) + } else if keyPath == PlayerItemEmptyBufferKey { + // PlayerItemEmptyBufferKey + if avPlayerItem?.isPlaybackBufferEmpty ?? false { + bufferingState = .delayed } - - } else if keyPath == PlayerLoadedTimeRangesKey { - - // PlayerLoadedTimeRangesKey - - if let item = self._playerItem { - self.bufferingState = .ready - + + observeStatus(change: change) + } else if keyPath == PlayerItemLoadedTimeRangesKey { + // PlayerItemLoadedTimeRangesKey + if let item = avPlayerItem { + bufferingState = .ready + let timeRanges = item.loadedTimeRanges if let timeRange = timeRanges.first?.timeRangeValue { let bufferedTime = CMTimeGetSeconds(CMTimeAdd(timeRange.start, timeRange.duration)) - if _lastBufferTime != bufferedTime { - self.executeClosureOnMainQueueIfNecessary { - self.playerDelegate?.playerBufferTimeDidChange(bufferedTime) + if lastBufferTime != bufferedTime { + executeClosureOnMainQueueIfNecessary { + self.playerDelegate?.playerBufferTimeDidChange?(bufferTime: bufferedTime) } - _lastBufferTime = bufferedTime + lastBufferTime = bufferedTime } } - - let currentTime = CMTimeGetSeconds(item.currentTime()) - if ((_lastBufferTime - currentTime) >= self.bufferSize || - _lastBufferTime == maximumDuration || - timeRanges.first == nil) - && self.playbackState == .playing - { - self.play() - } - + + // Don't want this on macOS (not only does `AVPlayerView` handle it for us, this causes + // unwanted interaction with keyboard shortcuts for controlling playback). + #if canImport(UIKit) + let currentTime = CMTimeGetSeconds(item.currentTime()) + if ((lastBufferTime - currentTime) >= bufferSize || + lastBufferTime == maximumDuration || + timeRanges.first == nil) + && playbackState == .playing { + play() + } + #endif } - } - } else if context == &PlayerLayerObserverContext { - if self._playerView.playerLayer.isReadyForDisplay { - self.executeClosureOnMainQueueIfNecessary { - self.playerDelegate?.playerReady(self) + #if canImport(AppKit) + let isReadyForDisplay = playerView.isReadyForDisplay + #else + let isReadyForDisplay = avPlayerViewController?.isReadyForDisplay + ?? playerView!.playerLayer.isReadyForDisplay + #endif + + if isReadyForDisplay { + if autoplay { + play() + } + + executeClosureOnMainQueueIfNecessary { + self.playerDelegate?.playerReady?(player: self) + } + } + } else if context == &PlayerObserverContext { + // Currently, only observed on the macOS platform. + // Needed for interaction with controls or keyboard shortcuts. + if keyPath == PlayerRateKey { + if avPlayer.rate == 0 { + playbackState = .paused + } else { + playbackState = .playing } } } - } - } -// MARK: - queues +// MARK: - Dispatch extension Player { - - internal func executeClosureOnMainQueueIfNecessary(withClosure closure: @escaping () -> Void) { + public func executeClosureOnMainQueueIfNecessary(withClosure closure: @escaping () -> Void) { if Thread.isMainThread { closure() } else { DispatchQueue.main.async(execute: closure) } } - } -// MARK: - PlayerView +// MARK: - PlayerView (UIKit) -internal class PlayerView: UIView { +#if canImport(UIKit) + internal class SuperSecretPlayerView: UIView { - // MARK: - properties - - override class var layerClass: AnyClass { - get { + // MARK: - Properties + + override class var layerClass: AnyClass { return AVPlayerLayer.self } - } - var playerLayer: AVPlayerLayer { - get { - return self.layer as! AVPlayerLayer + var playerLayer: AVPlayerLayer { + // swiftlint:disable:next force_cast + return layer as! AVPlayerLayer } - } - var player: AVPlayer? { - get { - return self.playerLayer.player + var player: AVPlayer? { + get { + return playerLayer.player + } + set { + playerLayer.player = newValue + } } - set { - self.playerLayer.player = newValue + + var fillMode: AVLayerVideoGravity { + get { + return playerLayer.videoGravity + } + set { + playerLayer.videoGravity = newValue + } } - } - var fillMode: String { - get { - return self.playerLayer.videoGravity.rawValue + var playerIsReadyForDisplay: Bool { + return playerLayer.isReadyForDisplay } - set { - self.playerLayer.videoGravity = AVLayerVideoGravity(rawValue: newValue) + + var playerIsHidden: Bool { + get { + return playerLayer.isHidden + } + set { + playerLayer.isHidden = newValue + } } - } - - // MARK: - object lifecycle - override init(frame: CGRect) { - super.init(frame: frame) - self.playerLayer.backgroundColor = UIColor.black.cgColor - self.playerLayer.fillMode = PlayerFillMode.resizeAspectFit.avFoundationType - } + var playerBackgroundColor: UIColor? { + get { + if let cgColor = playerLayer.backgroundColor { + return UIColor(cgColor: cgColor) + } + return nil + } + set { + playerLayer.backgroundColor = newValue?.cgColor + } + } - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - self.playerLayer.backgroundColor = UIColor.black.cgColor - self.playerLayer.fillMode = PlayerFillMode.resizeAspectFit.avFoundationType - } + var playerFillMode: String { + get { + return convertFromCAMediaTimingFillMode(playerLayer.fillMode) + } + set { + playerLayer.fillMode = convertToCAMediaTimingFillMode(newValue) + } + } - deinit { - self.player?.pause() - self.player = nil + // MARK: - Object Lifecycle + + override init(frame: CGRect) { + super.init(frame: frame) + playerBackgroundColor = .black + playerFillMode = Player.FillMode.resizeAspectFit.rawValue + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + playerBackgroundColor = .black + playerFillMode = Player.FillMode.resizeAspectFit.rawValue + } + + deinit { + player?.pause() + player = nil + } } - +#endif + +// Helper function inserted by Swift 4.2 migrator. +private func convertFromAVLayerVideoGravity(_ input: AVLayerVideoGravity) -> String { + return input.rawValue +} + +// Helper function inserted by Swift 4.2 migrator. +private func convertToAVLayerVideoGravity(_ input: String) -> AVLayerVideoGravity { + return AVLayerVideoGravity(rawValue: input) +} + +// Helper function inserted by Swift 4.2 migrator. +private func convertFromCAMediaTimingFillMode(_ input: CAMediaTimingFillMode) -> String { + return input.rawValue +} + +// Helper function inserted by Swift 4.2 migrator. +private func convertToCAMediaTimingFillMode(_ input: String) -> CAMediaTimingFillMode { + return CAMediaTimingFillMode(rawValue: input) } diff --git a/docs/Classes.html b/docs/Classes.html index 632d140..b8914cb 100644 --- a/docs/Classes.html +++ b/docs/Classes.html @@ -13,7 +13,7 @@
-

Player Docs (60% documented)

+

Player Docs (61% documented)

View on GitHub

@@ -33,19 +33,14 @@ - - - @@ -100,7 +95,7 @@

Player

Declaration

Swift

-
open class Player: UIViewController
+
open class Player: Player.ViewController
@@ -112,8 +107,8 @@

Declaration

diff --git a/docs/Classes/Player.html b/docs/Classes/Player.html index 4e3769f..6587849 100644 --- a/docs/Classes/Player.html +++ b/docs/Classes/Player.html @@ -14,7 +14,7 @@
-

Player Docs (60% documented)

+

Player Docs (61% documented)

View on GitHub

@@ -34,19 +34,14 @@ - - - @@ -69,7 +64,7 @@

Player

-
open class Player: UIViewController
+
open class Player: Player.ViewController
@@ -78,13 +73,351 @@

Player

+
  • - + + + ViewController + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public typealias ViewController = NSViewController
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + Image + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public typealias Image = NSImage
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + Color + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public typealias Color = NSColor
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + SnapshotResult + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public typealias SnapshotResult = Image?
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + NibName + +
    +
    +
    +
    +
    +
    +

    Undocumented

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public typealias NibName = NSNib.Name?
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + ViewController + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
  • +
  • +
    + + + + Image + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
  • +
  • +
    + + + + Color + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
  • +
  • +
    + + + + SnapshotResult + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
  • +
  • +
    + + + + NibName + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
  • +
+
+
+
+ + + +

Types

+
+
+
    +
  • +
    + + + + PlaybackState + +
    +
    +
    +
    +
    +
    +

    Asset playback states.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public enum PlaybackState: String, CustomStringConvertible
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + BufferingState + +
    +
    +
    +
    +
    +
    +

    Asset buffering states.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public enum BufferingState: String, CustomStringConvertible
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + FillMode + +
    +
    +
    +
    +
    +
    +

    Video fill mode options for the fillMode property.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public enum FillMode: String, CustomStringConvertible
    + +
    +
    +
    +
    +
  • +
+
+
+ +
+ + +
+ +
  • - + - url + url
    @@ -153,7 +497,7 @@

    Declaration

    Declaration

    Swift

    -
    open var url: URL?
    +
    open var url: URL?
    @@ -182,9 +526,9 @@

    Parameters

  • - + - autoplay + autoplay
    @@ -199,7 +543,7 @@

    Parameters

    Declaration

    Swift

    -
    open var autoplay: Bool = true
    +
    open var autoplay: Bool = true
    @@ -228,9 +572,9 @@

    Parameters

  • - + - asset + asset
    @@ -238,15 +582,16 @@

    Parameters

    -

    For setting up with AVAsset instead of URL -Note: Resets URL (cannot set both)

    +

    For setting up with AVAsset instead of URL

    + +

    Note: Resets URL (cannot set both)

    Declaration

    Swift

    -
    open var asset: AVAsset?
    +
    open var asset: AVAsset?
    @@ -256,9 +601,9 @@

    Declaration

  • - + - muted + muted
    @@ -273,7 +618,7 @@

    Declaration

    Declaration

    Swift

    -
    open var muted: Bool
    +
    open var muted: Bool
    @@ -283,9 +628,9 @@

    Declaration

  • - + - volume + volume
    @@ -300,7 +645,188 @@

    Declaration

    Declaration

    Swift

    -
    open var volume: Float
    +
    open var volume: Float
    + +
    +
    +
  • +
    +
  • +
  • +
    + + + + fillMode + +
    +
    +
    +
    +
    +
    +

    Specifies how the video is displayed within a player layer’s bounds. + The default value is .resizeAspectFit. See the FillMode enum.

    + +

    Note: On iOS, this property is ignored if using system-supplied playback controls.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open var fillMode: FillMode
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + layerBackgroundColor + +
    +
    +
    +
    +
    +
    +

    Player view’s initial background color.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open var layerBackgroundColor: Color?
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + controlsStyle + +
    +
    +
    +
    +
    +
    +

    The player view’s controls style.

    + +

    The player view supports a number of different control styles that you can use to customize the player +view’s appearance and behavior. See AVPlayerViewControlsStyle for the possible values. +The default value of this property is .default

    +
    +

    Important

    + Only available on the macOS platform. + +
    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open var controlsStyle: AVPlayerViewControlsStyle
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    A Boolean value that indicates whether the player shows playback controls. +This property has a default value of true.

    +
    +

    Note

    +

    Only available on iOS/tvOS platforms. For macOS, see controlsStyle.

    + +
    +
    +

    Important

    +

    Set this property before calling add(to:). Setting it after will have no effect.

    + +
    + +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Pauses playback automatically when resigning active.

    + +

    The default value of this property is true.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open var playbackPausesWhenResigningActive: Bool = true
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Pauses playback automatically when backgrounded (on macOS, when hidden).

    + +

    The default value of this property is true.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open var playbackPausesWhenBackgrounded: Bool = true
    @@ -310,9 +836,78 @@

    Declaration

  • +
    +
    +
    +
    +
    +

    Resumes playback when became active.

    + +

    The default value of this property is true.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open var playbackResumesWhenBecameActive: Bool = true
    + +
    +
    +
    +
    +
  • +
  • + +
    +
    +
    +
    +
    +

    Resumes playback when entering foreground. (on macOS, when unhidden)

    + +

    The default value of this property is true.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open var playbackResumesWhenEnteringForeground: Bool = true
    + +
    +
    +
    +
    +
  • +
+
+
+
+ + + +

State

+
+
+
    +
  • +
    + + + + isPlaying
    @@ -320,15 +915,15 @@

    Declaration

    -

    Specifies how the video is displayed within a player layer’s bounds. -The default value is AVLayerVideoGravityResizeAspect. See FillMode enum.

    +

    Whether the player is currently playing. +Returns true if the playbackState is .playing.

    Declaration

    Swift

    -
    open var fillMode: String
    +
    open var isPlaying: Bool
    @@ -338,9 +933,9 @@

    Declaration

  • @@ -348,14 +943,14 @@

    Declaration

    -

    Pauses playback automatically when resigning active.

    +

    Playback automatically loops continuously when true.

    Declaration

    Swift

    -
    open var playbackPausesWhenResigningActive: Bool = true
    +
    open var playbackLoops: Bool
    @@ -365,9 +960,9 @@

    Declaration

  • @@ -375,14 +970,16 @@

    Declaration

    -

    Pauses playback automatically when backgrounded.

    +

    Playback freezes on last frame frame at end when true.

    + +

    The default value of this property is false.

    Declaration

    Swift

    -
    open var playbackPausesWhenBackgrounded: Bool = true
    +
    open var playbackFreezesAtEnd: Bool = false
    @@ -392,9 +989,9 @@

    Declaration

  • @@ -402,14 +999,14 @@

    Declaration

    -

    Resumes playback when became active.

    +

    Current playback state of the Player.

    Declaration

    Swift

    -
    open var playbackResumesWhenBecameActive: Bool = true
    +
    open var playbackState: PlaybackState = .stopped
    @@ -419,9 +1016,9 @@

    Declaration

  • @@ -429,14 +1026,14 @@

    Declaration

    -

    Resumes playback when entering foreground.

    +

    Current buffering state of the Player.

    Declaration

    Swift

    -
    open var playbackResumesWhenEnteringForeground: Bool = true
    +
    open var bufferingState: BufferingState = .unknown
    @@ -446,9 +1043,9 @@

    Declaration

  • @@ -456,14 +1053,14 @@

    Declaration

    -

    Playback automatically loops continuously when true.

    +

    Playback buffering size in seconds.

    Declaration

    Swift

    -
    open var playbackLoops: Bool
    +
    open var bufferSize: Double = 10
    @@ -473,9 +1070,9 @@

    Declaration

  • @@ -483,14 +1080,14 @@

    Declaration

    -

    Playback freezes on last frame frame at end when true.

    +

    Playback is not automatically triggered from state changes when true.

    Declaration

    Swift

    -
    open var playbackFreezesAtEnd: Bool = false
    +
    open var playbackEdgeTriggered: Bool = true
    @@ -500,9 +1097,9 @@

    Declaration

  • @@ -510,14 +1107,14 @@

    Declaration

    -

    Current playback state of the Player.

    +

    Maximum duration of playback.

    Declaration

    Swift

    -
    open var playbackState: PlaybackState = .stopped
    +
    open var maximumDuration: TimeInterval
    @@ -527,9 +1124,9 @@

    Declaration

  • @@ -537,14 +1134,14 @@

    Declaration

    -

    Current buffering state of the Player.

    +

    Media playback’s current time.

    Declaration

    Swift

    -
    open var bufferingState: BufferingState = .unknown
    +
    open var currentTime: TimeInterval
    @@ -554,9 +1151,9 @@

    Declaration

  • - - - bufferSize + + + naturalSize
    @@ -564,26 +1161,42 @@

    Declaration

    -

    Playback buffering size in seconds.

    +

    The natural dimensions of the media.

    +
    +

    Note

    + The avPlayerItem must exist and have had its tracks loaded. + +

    Declaration

    Swift

    -
    open var bufferSize: Double = 10
    +
    open var naturalSize: CGSize?
  • +
+
+
+ +
  • @@ -591,14 +1204,14 @@

    Declaration

    -

    Playback is not automatically triggered from state changes when true.

    +

    Undocumented

    Declaration

    Swift

    -
    open var playbackEdgeTriggered: Bool = true
    +
    public var avPlayer: AVPlayer
    @@ -608,9 +1221,9 @@

    Declaration

  • @@ -618,14 +1231,14 @@

    Declaration

    -

    Maximum duration of playback.

    +

    Undocumented

    Declaration

    Swift

    -
    open var maximumDuration: TimeInterval
    +
    public var avPlayerItem: AVPlayerItem?
    @@ -635,9 +1248,9 @@

    Declaration

  • @@ -645,26 +1258,28 @@

    Declaration

    -

    Media playback’s current time.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    open var currentTime: TimeInterval
    - -
    +
  • +
+
+
+ +
  • - - - naturalSize + + + init()
    @@ -672,14 +1287,14 @@

    Declaration

    -

    The natural dimensions of the media.

    +

    Undocumented

    Declaration

    Swift

    -
    open var naturalSize: CGSize
    +
    public convenience init()
    @@ -689,9 +1304,9 @@

    Declaration

  • @@ -699,37 +1314,26 @@

    Declaration

    -

    Player view’s initial background color.

    +

    Undocumented

    Declaration

    Swift

    -
    open var layerBackgroundColor: UIColor?
    +
    public required init?(coder aDecoder: NSCoder)
  • -
-
-
- -
  • @@ -744,7 +1348,7 @@

    object lifecycle

    Declaration

    Swift

    -
    open class Player: UIViewController
    +
    public override init(nibName nibNameOrNil: NibName, bundle nibBundleOrNil: Bundle?)
    @@ -754,9 +1358,9 @@

    Declaration

  • - - - init(coder:) + + + add(to:)
    @@ -764,26 +1368,51 @@

    Declaration

    -

    Undocumented

    +

    Adds a player to the given view controller. +The player will be added to viewController‘s childViewControllers array and its view hierarchy.

    +
    +

    Important

    +

    On iOS/tvOS platforms, usesSystemPlaybackControls must be set prior to calling this method.

    + +

    Declaration

    Swift

    -
    open class Player: UIViewController
    +
    open func add(to viewController: ViewController)
    +
    +

    Parameters

    + + + + + + + +
    + + viewController + + +
    +

    The parent view controller that the player will be added to.

    +
    +
    +
  • @@ -791,17 +1420,37 @@

    Declaration

    -

    Undocumented

    +

    Removes the player from the given view controller. +The player will be removed from viewController‘s childViewControllers array and its view hierarchy.

    Declaration

    Swift

    -
    open class Player: UIViewController
    +
    open func remove(from viewController: ViewController)
    +
    +

    Parameters

    + + + + + + + +
    + + viewController + + +
    +

    The parent view controller that the player will be removed from.

    +
    +
    +
  • @@ -809,10 +1458,10 @@

    Declaration

    @@ -836,7 +1485,7 @@

    view lifecycle

    Declaration

    Swift

    -
    open class Player: UIViewController
    +
    open override func loadView()
@@ -863,7 +1512,7 @@

Declaration

Declaration

Swift

-
open class Player: UIViewController
+
open override func viewDidLoad()
@@ -873,9 +1522,9 @@

Declaration

  • @@ -890,24 +1539,60 @@

    Declaration

    Declaration

    Swift

    -
    open class Player: UIViewController
    +
    open override func viewDidDisappear()
  • +
  • +
    + + + + viewDidDisappear(_:) + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    • +
    • + +
      +
      +
      +
      +
      + +
      +
      +
      +
    • @@ -928,7 +1613,7 @@

      Playback funcs

      Declaration

      Swift

      -
      open func playFromBeginning()
      +
      open func playFromBeginning()
      @@ -955,7 +1640,7 @@

      Declaration

      Declaration

      Swift

      -
      open func playFromCurrentTime()
      +
      open func playFromCurrentTime()
    @@ -982,7 +1667,7 @@

    Declaration

    Declaration

    Swift

    -
    open func pause()
    +
    open func pause()
    @@ -1009,7 +1694,7 @@

    Declaration

    Declaration

    Swift

    -
    open func stop()
    +
    open func stop()
    @@ -1036,7 +1721,7 @@

    Declaration

    Declaration

    Swift

    -
    open func seek(to time: CMTime, completionHandler: ((Bool) -> Swift.Void)? = nil)
    +
    open func seek(to time: CMTime, completionHandler: ((Bool) -> Swift.Void)? = nil)
    @@ -1074,6 +1759,65 @@

    Parameters

    +
  • + +
    +
    +
    +
    +
    +

    Sets the current playback time to the specified second mark and executes the specified block when the seek +operation completes or is interrupted.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    open func seek(toSecond second: Int, completionHandler: ((Bool) -> Swift.Void)? = nil)
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + time + + +
    +

    The time (in seconds) to seek to.

    +
    +
    + + completionHandler + + +
    +

    Call block handler after seeking.

    +
    +
    +
    +
    +
    +
  • @@ -1094,7 +1838,10 @@

    Parameters

    Declaration

    Swift

    -
    open func seekToTime(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime, completionHandler: ((Bool) -> Swift.Void)? = nil)
    +
    open func seekToTime(to time: CMTime,
    +                     toleranceBefore: CMTime,
    +                     toleranceAfter: CMTime,
    +                     completionHandler: ((Bool) -> Swift.Void)? = nil)
    @@ -1159,9 +1906,9 @@

    Parameters

  • @@ -1169,20 +1916,20 @@

    Parameters

    -

    Captures a snapshot of the current Player view.

    +

    Captures a snapshot of the current player view.

    Declaration

    Swift

    -
    open func takeSnapshot() -> UIImage
    +
    open func takeSnapshot() -> SnapshotResult

    Return Value

    -

    A UIImage of the player view.

    +

    A image of the player view.

    @@ -1200,15 +1947,19 @@

    Return Value

    -

    Return the av player layer for consumption by -things such as Picture in Picture

    +

    Return the AVPlayerLayer for consumption by things such as Picture in Picture.

    +
    +

    Note

    + Player must be loaded. + +

    Declaration

    Swift

    -
    open func playerLayer() -> AVPlayerLayer?
    +
    open func playerLayer() -> AVPlayerLayer?
    @@ -1218,6 +1969,13 @@

    Declaration

    +
    • @@ -1239,7 +1997,41 @@

      Declaration

      Declaration

      Swift

      -
      open class Player: UIViewController
      +
      open override func observeValue(forKeyPath keyPath: String?,
      +                                of object: Any?,
      +                                change: [NSKeyValueChangeKey: Any]?,
      +                                context: UnsafeMutableRawPointer?)
      + +
      +
      +
    + +
  • + + +
    +
    diff --git a/docs/docsets/Player.docset/Contents/Resources/Documents/Enums/PlayerFillMode.html b/docs/Classes/Player/FillMode.html similarity index 54% rename from docs/docsets/Player.docset/Contents/Resources/Documents/Enums/PlayerFillMode.html rename to docs/Classes/Player/FillMode.html index 4aeca8c..42e10b5 100644 --- a/docs/docsets/Player.docset/Contents/Resources/Documents/Enums/PlayerFillMode.html +++ b/docs/Classes/Player/FillMode.html @@ -1,63 +1,58 @@ - PlayerFillMode Enumeration Reference - - + FillMode Enumeration Reference + + - - + + - - + +
    -

    Player Docs (60% documented)

    -

    View on GitHub

    +

    Player Docs (61% documented)

    +

    View on GitHub

    diff --git a/docs/Enums/PlaybackState.html b/docs/Classes/Player/PlaybackState.html similarity index 64% rename from docs/Enums/PlaybackState.html rename to docs/Classes/Player/PlaybackState.html index 2785e88..4df1734 100644 --- a/docs/Enums/PlaybackState.html +++ b/docs/Classes/Player/PlaybackState.html @@ -2,11 +2,11 @@ PlaybackState Enumeration Reference - - + + - - + + @@ -14,14 +14,14 @@
    -

    Player Docs (60% documented)

    -

    View on GitHub

    +

    Player Docs (61% documented)

    +

    View on GitHub

    @@ -29,35 +29,30 @@