diff --git a/PlayerKit.podspec b/PlayerKit.podspec index 82e337f..33250d4 100644 --- a/PlayerKit.podspec +++ b/PlayerKit.podspec @@ -4,7 +4,7 @@ Pod::Spec.new do |s| s.summary = 'A modular video player system.' s.description = <<-DESC - PlayerKit is a modular video player system for iOS and tvOS. + PlayerKit is a modular video player system for iOS, tvOS, & macOS. DESC s.homepage = 'https://github.com/vimeo/PlayerKit' @@ -14,6 +14,10 @@ Pod::Spec.new do |s| s.ios.deployment_target = '10.3' s.tvos.deployment_target = '10.0' + s.osx.deployment_target = '10.11' + + s.ios.framework = 'UIKit' + s.osx.framework = 'AppKit' s.swift_version = "5.0" diff --git a/PlayerKit.xcodeproj/project.pbxproj b/PlayerKit.xcodeproj/project.pbxproj index 70aec3e..f432107 100644 --- a/PlayerKit.xcodeproj/project.pbxproj +++ b/PlayerKit.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 8751E6B022F4C70F000610A0 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8751E67B22F49B83000610A0 /* Player.swift */; }; 8751E6B122F4C70F000610A0 /* AVPlayer+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8751E67C22F49B83000610A0 /* AVPlayer+Utilities.swift */; }; 87570FEB22F8D4FB00D740FD /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; + 87570FED22F9E15500D740FD /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87570FEC22F9E15500D740FD /* PlayerView.swift */; }; F0FFB37F820BFA1221283BF8 /* Pods_PlayerKit_iOS_PlayerKit_iOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3423AC93F7CB435E99250B3C /* Pods_PlayerKit_iOS_PlayerKit_iOSTests.framework */; }; F5E7784A924FFE1AF46D019B /* Pods_PlayerKit_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0E6AB4D2B9EC44EF46A6D7 /* Pods_PlayerKit_iOS.framework */; }; /* End PBXBuildFile section */ @@ -91,6 +92,7 @@ 8751E68C22F4B7CE000610A0 /* PlayerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8751E69422F4B7CE000610A0 /* PlayerKit-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "PlayerKit-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 87570FE922F4E01300D740FD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 87570FEC22F9E15500D740FD /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; 8A0E6AB4D2B9EC44EF46A6D7 /* Pods_PlayerKit_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PlayerKit_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9D69DB7C6B7F626E6391E3FA /* Pods-PlayerKit_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PlayerKit_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PlayerKit_Example/Pods-PlayerKit_Example.debug.xcconfig"; sourceTree = ""; }; A5F6E9489936F0976FA4E1B5 /* Pods-PlayerKit-Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PlayerKit-Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PlayerKit-Tests/Pods-PlayerKit-Tests.release.xcconfig"; sourceTree = ""; }; @@ -227,6 +229,7 @@ 8751E67822F49B83000610A0 /* Sources */ = { isa = PBXGroup; children = ( + 87570FEC22F9E15500D740FD /* PlayerView.swift */, 8751E67922F49B83000610A0 /* RegularPlayer.swift */, 8751E67A22F49B83000610A0 /* CMTime+Utilities.swift */, 8751E67B22F49B83000610A0 /* Player.swift */, @@ -494,6 +497,7 @@ 8751E6AE22F4C70F000610A0 /* RegularPlayer.swift in Sources */, 8751E6B022F4C70F000610A0 /* Player.swift in Sources */, 8751E6B122F4C70F000610A0 /* AVPlayer+Utilities.swift in Sources */, + 87570FED22F9E15500D740FD /* PlayerView.swift in Sources */, 8751E6AF22F4C70F000610A0 /* CMTime+Utilities.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/Player.swift b/Sources/Player.swift index fa89644..9df3beb 100644 --- a/Sources/Player.swift +++ b/Sources/Player.swift @@ -6,7 +6,6 @@ // // -import UIKit import AVKit /// A player error @@ -65,6 +64,8 @@ public enum PlayerError: Int { var playing: Bool { get } + var ended: Bool { get } + var error: NSError? { get } /// Seeks to the specified time @@ -83,7 +84,7 @@ public enum PlayerError: Int { /// A player that adopts the ProvidesView protocol is capable of providing a view to be added to a view hierarchy. @objc public protocol ProvidesView { - var view: UIView { get } + var view: PlayerView { get } } // MARK: Capability Protocols diff --git a/Sources/PlayerView.swift b/Sources/PlayerView.swift new file mode 100644 index 0000000..d78b1e4 --- /dev/null +++ b/Sources/PlayerView.swift @@ -0,0 +1,18 @@ +// +// PlayerView.swift +// PlayerKit +// +// Created by Balatbat, Bryant on 12/20/17. +// + +/// This file creates a `typealias` called `PlayerView` that determines which core view component to use depending on +/// which platform the binary is being compiled for. If iOS or tvOS, `UIKit` will be imported and `UIView` will be used. +/// otherwise, if macOS is used, `AppKit` will be imported and `NSView` will be used. + +#if canImport(UIKit) + import UIKit + public typealias PlayerView = UIView +#elseif canImport(AppKit) + import AppKit + public typealias PlayerView = NSView +#endif diff --git a/Sources/RegularPlayer.swift b/Sources/RegularPlayer.swift index 55ee61c..7cde465 100644 --- a/Sources/RegularPlayer.swift +++ b/Sources/RegularPlayer.swift @@ -6,7 +6,6 @@ // // -import UIKit import Foundation import AVFoundation import AVKit @@ -50,21 +49,32 @@ extension AVMediaSelectionOption: TextTrackMetadata { // MARK: ProvidesView - private class RegularPlayerView: UIView { + private class RegularPlayerView: PlayerView { var playerLayer: AVPlayerLayer { return self.layer as! AVPlayerLayer } - + + #if canImport(UIKit) override class var layerClass: AnyClass { return AVPlayerLayer.self } + #elseif canImport(AppKit) + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.layer = AVPlayerLayer() + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + #endif func configureForPlayer(player: AVPlayer) { (self.layer as! AVPlayerLayer).player = player } } - public let view: UIView = RegularPlayerView(frame: .zero) + public let view: PlayerView = RegularPlayerView(frame: .zero) private var regularPlayerView: RegularPlayerView { return self.view as! RegularPlayerView @@ -104,6 +114,10 @@ extension AVMediaSelectionOption: TextTrackMetadata { return self.player.rate > 0 } + public var ended: Bool { + return self.time >= self.duration + } + public var error: NSError? { return self.player.errorForPlayerOrItem } @@ -130,9 +144,7 @@ extension AVMediaSelectionOption: TextTrackMetadata { super.init() self.addPlayerObservers() - self.regularPlayerView.configureForPlayer(player: self.player) - self.setupAirplay() } @@ -146,7 +158,7 @@ extension AVMediaSelectionOption: TextTrackMetadata { // MARK: Setup - @available(iOS 10.0, *) + @available(iOS 10.0, tvOS 10.0, macOS 10.12, *) public var automaticallyWaitsToMinimizeStalling: Bool { get { return self.player.automaticallyWaitsToMinimizeStalling @@ -157,7 +169,9 @@ extension AVMediaSelectionOption: TextTrackMetadata { } private func setupAirplay() { - self.player.usesExternalPlaybackWhileExternalScreenIsActive = true + #if os(iOS) || os(tvOS) + self.player.usesExternalPlaybackWhileExternalScreenIsActive = true + #endif } // MARK: Observers