From a33eabedad1081643ea08903a79b9492b7cd49d1 Mon Sep 17 00:00:00 2001 From: Pablo Bernardi Date: Fri, 10 Mar 2017 17:55:24 -0300 Subject: [PATCH] Added Airplay Support --- MobilePlayer.xcodeproj/project.pbxproj | 22 ++++++++ MobilePlayer/Config/AirplayConfig.swift | 50 +++++++++++++++++ MobilePlayer/Config/BarConfig.swift | 2 + MobilePlayer/Config/ElementConfig.swift | 5 ++ MobilePlayer/MobilePlayerViewController.swift | 1 + MobilePlayer/Views/Airplay.swift | 48 +++++++++++++++++ MobilePlayer/Views/Bar.swift | 3 ++ ...edConfigAirplayExampleViewController.swift | 37 +++++++++++++ .../ExamplesTableViewController.swift | 1 + MobilePlayerExamples/Resources/Airplay.json | 54 +++++++++++++++++++ 10 files changed, 223 insertions(+) create mode 100644 MobilePlayer/Config/AirplayConfig.swift create mode 100644 MobilePlayer/Views/Airplay.swift create mode 100644 MobilePlayerExamples/AdvancedConfigAirplayExampleViewController.swift create mode 100644 MobilePlayerExamples/Resources/Airplay.json diff --git a/MobilePlayer.xcodeproj/project.pbxproj b/MobilePlayer.xcodeproj/project.pbxproj index 0d30901..b4b8c38 100644 --- a/MobilePlayer.xcodeproj/project.pbxproj +++ b/MobilePlayer.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 049568191E733437006E4937 /* Airplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049568181E733437006E4937 /* Airplay.swift */; }; + 0495681B1E733563006E4937 /* AirplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0495681A1E733563006E4937 /* AirplayConfig.swift */; }; + 0495681D1E733904006E4937 /* Airplay.json in Resources */ = {isa = PBXBuildFile; fileRef = 0495681C1E733904006E4937 /* Airplay.json */; }; + 0495681F1E73484A006E4937 /* AdvancedConfigAirplayExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0495681E1E73484A006E4937 /* AdvancedConfigAirplayExampleViewController.swift */; }; 5F31B7B91BBAE39900EF50C4 /* MobilePlayerNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F31B7B81BBAE39900EF50C4 /* MobilePlayerNotification.swift */; }; 5F3F03C31C18E73200EF50C4 /* PrerollExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F3F03C21C18E73200EF50C4 /* PrerollExampleViewController.swift */; }; 5F3F03C51C18E74000EF50C4 /* PauseOverlayExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F3F03C41C18E74000EF50C4 /* PauseOverlayExampleViewController.swift */; }; @@ -136,6 +140,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 049568181E733437006E4937 /* Airplay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Airplay.swift; path = Views/Airplay.swift; sourceTree = ""; }; + 0495681A1E733563006E4937 /* AirplayConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirplayConfig.swift; sourceTree = ""; }; + 0495681C1E733904006E4937 /* Airplay.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = Airplay.json; path = Resources/Airplay.json; sourceTree = ""; }; + 0495681E1E73484A006E4937 /* AdvancedConfigAirplayExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedConfigAirplayExampleViewController.swift; sourceTree = ""; }; 0A3D625F8D46F7B3823A9927 /* Pods-MobilePlayerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MobilePlayerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MobilePlayerTests/Pods-MobilePlayerTests.debug.xcconfig"; sourceTree = ""; }; 460022C3A2D4FC80A1D57D18 /* Pods-MobilePlayerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MobilePlayerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MobilePlayerTests/Pods-MobilePlayerTests.release.xcconfig"; sourceTree = ""; }; 505CCD80FF878F8F17EC4627 /* Pods_MobilePlayerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MobilePlayerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -281,6 +289,7 @@ children = ( 5F4CED421C054A5200EF50C4 /* WatermarkedPlayer.json */, 5FF829681C16924400EF50C4 /* MovielalaPlayer.json */, + 0495681C1E733904006E4937 /* Airplay.json */, ); name = Resources; sourceTree = ""; @@ -298,6 +307,7 @@ 5F3F03C21C18E73200EF50C4 /* PrerollExampleViewController.swift */, 5F3F03C41C18E74000EF50C4 /* PauseOverlayExampleViewController.swift */, 5F3F03C61C18E75300EF50C4 /* PostrollExampleViewController.swift */, + 0495681E1E73484A006E4937 /* AdvancedConfigAirplayExampleViewController.swift */, ); name = Examples; sourceTree = ""; @@ -362,6 +372,7 @@ 5FA92EA21BA9896300EF50C4 /* ToggleButton.swift */, 5FA92EA61BA9919D00EF50C4 /* Slider.swift */, 5F7CDF4A1BBEB6F900EF50C4 /* VolumeView.swift */, + 049568181E733437006E4937 /* Airplay.swift */, ); name = Views; sourceTree = ""; @@ -413,6 +424,7 @@ 5FA92EA01BA9839300EF50C4 /* ButtonConfig.swift */, 5FBFEA331BA8ACDE00EF50C4 /* ToggleButtonConfig.swift */, 5FBFEA351BA8AD0200EF50C4 /* SliderConfig.swift */, + 0495681A1E733563006E4937 /* AirplayConfig.swift */, ); path = Config; sourceTree = ""; @@ -721,6 +733,7 @@ 5F56CB901C040E8D00EF50C4 /* LaunchScreen.storyboard in Resources */, 5F3F03D51C1903EF00EF50C4 /* PostrollOverlayViewController.xib in Resources */, 5FF829691C16924400EF50C4 /* MovielalaPlayer.json in Resources */, + 0495681D1E733904006E4937 /* Airplay.json in Resources */, 5F56CB8D1C040E8D00EF50C4 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -788,6 +801,7 @@ 5F79747C1C17ED7800EF50C4 /* TimedOverlayExampleViewController.swift in Sources */, 5F3F03CF1C18F3DA00EF50C4 /* PauseOverlayViewController.swift in Sources */, 5FF829671C168E4700EF50C4 /* ProgConfigExampleViewController.swift in Sources */, + 0495681F1E73484A006E4937 /* AdvancedConfigAirplayExampleViewController.swift in Sources */, 5F3F03D31C1903E200EF50C4 /* PostrollOverlayViewController.swift in Sources */, 5F7974781C17B02200EF50C4 /* OverlayExampleViewController.swift in Sources */, 5F56CBB71C041DAE00EF50C4 /* RemoteConfigExampleViewController.swift in Sources */, @@ -817,11 +831,13 @@ 5FA92EA51BA98BE700EF50C4 /* Bar.swift in Sources */, 5FBFEA361BA8AD0200EF50C4 /* SliderConfig.swift in Sources */, 5FBFEA2E1BA8AC1B00EF50C4 /* BarConfig.swift in Sources */, + 0495681B1E733563006E4937 /* AirplayConfig.swift in Sources */, C308B3C01B150F7600CB1515 /* MobilePlayerConfig.swift in Sources */, 5F8272741BFA9F0600EF50C4 /* UIImage+CocoaPods.swift in Sources */, 5F7CDF471BBEB6C400EF50C4 /* YoutubeParser.swift in Sources */, 5FD756961BAC4AE700EF50C4 /* WatermarkViewController.swift in Sources */, 5FBFEA321BA8ACC500EF50C4 /* LabelConfig.swift in Sources */, + 049568191E733437006E4937 /* Airplay.swift in Sources */, 5F6AF3701BA96EF000EF50C4 /* Button.swift in Sources */, 5FBFEA341BA8ACDE00EF50C4 /* ToggleButtonConfig.swift in Sources */, CE554D731AFE6EBB00DAC7E9 /* MobilePlayerOverlayViewController.swift in Sources */, @@ -935,6 +951,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = MobilePlayerExamples/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -950,6 +967,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = MobilePlayerExamples/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -963,6 +981,7 @@ isa = XCBuildConfiguration; buildSettings = { DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = MobilePlayerExamplesUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -978,6 +997,7 @@ isa = XCBuildConfiguration; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = MobilePlayerExamplesUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1129,6 +1149,7 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", @@ -1150,6 +1171,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; "BUNDLE_LOADER[arch=*]" = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", diff --git a/MobilePlayer/Config/AirplayConfig.swift b/MobilePlayer/Config/AirplayConfig.swift new file mode 100644 index 0000000..f6230a6 --- /dev/null +++ b/MobilePlayer/Config/AirplayConfig.swift @@ -0,0 +1,50 @@ +// +// AirplayConfig.swift +// MobilePlayer +// +// Created by Maca on 3/10/17. +// Copyright © 2017 MovieLaLa. All rights reserved. +// + +import UIKit + +/// Holds button configuration values. +public class AirplayConfig: ElementConfig { + + /// Airplay background color + public let backgroundColor: UIColor + + /// Airplay tint color. Default value is white. + public let tintColor: UIColor + + /// Initializes using default values. + public convenience init() { + self.init(dictionary: [String: Any]()) + } + + /// Initializes using a dictionary. + /// + /// * Key for `backgroundColor` is `"backgroundColor"` and its value should be a color hex string. + /// * Key for `tintColor` is `"tintColor"` and its value should be a color hex string. + /// + /// - parameters: + /// - dictionary: Button configuration dictionary. + public override init(dictionary: [String: Any]) { + // Values need to be AnyObject for type conversions to work correctly. + let dictionary = dictionary as [String: AnyObject] + + if let backgroundColorHex = dictionary["backgroundColor"] as? String { + backgroundColor = UIColor(hex: backgroundColorHex) + } else { + backgroundColor = UIColor.clear + } + + if let tintColorHex = dictionary["tintColor"] as? String { + tintColor = UIColor(hex: tintColorHex) + } else { + tintColor = UIColor.white + } + + super.init(dictionary: dictionary) + } +} diff --git a/MobilePlayer/Config/BarConfig.swift b/MobilePlayer/Config/BarConfig.swift index a207e87..b47bb08 100644 --- a/MobilePlayer/Config/BarConfig.swift +++ b/MobilePlayer/Config/BarConfig.swift @@ -96,6 +96,8 @@ public class BarConfig { validElements.append(LabelConfig(dictionary: elementDictionary)) case "slider": validElements.append(SliderConfig(dictionary: elementDictionary)) + case "airplay": + validElements.append(AirplayConfig(dictionary: elementDictionary)) default: break } diff --git a/MobilePlayer/Config/ElementConfig.swift b/MobilePlayer/Config/ElementConfig.swift index 474325d..d77cdbb 100644 --- a/MobilePlayer/Config/ElementConfig.swift +++ b/MobilePlayer/Config/ElementConfig.swift @@ -25,6 +25,9 @@ public enum ElementType: String { /// Element is a slider. case Slider = "slider" + + /// Element is airplay button + case Airplay = "airplay" } /// Determines how an element's width will be calculated. @@ -61,6 +64,8 @@ public class ElementConfig { /// * "play" /// * Sliders /// * "playback" + /// * Airplay + /// * "router" public let identifier: String? /// How the width of the element will be calculated. Default value is `.Fill` for title label and playback slider, diff --git a/MobilePlayer/MobilePlayerViewController.swift b/MobilePlayer/MobilePlayerViewController.swift index 533b47d..3ca2cbc 100644 --- a/MobilePlayer/MobilePlayerViewController.swift +++ b/MobilePlayer/MobilePlayerViewController.swift @@ -120,6 +120,7 @@ open class MobilePlayerViewController: MPMoviePlayerViewController { edgesForExtendedLayout = [] moviePlayer.scalingMode = .aspectFit moviePlayer.controlStyle = .none + moviePlayer.allowsAirPlay = true initializeNotificationObservers() initializeControlsView() parseContentURLIfNeeded() diff --git a/MobilePlayer/Views/Airplay.swift b/MobilePlayer/Views/Airplay.swift new file mode 100644 index 0000000..b14c7e0 --- /dev/null +++ b/MobilePlayer/Views/Airplay.swift @@ -0,0 +1,48 @@ +// +// Airplay.swift +// MobilePlayer +// +// Created by Maca on 3/10/17. +// Copyright © 2017 MovieLaLa. All rights reserved. +// + +import UIKit +import MediaPlayer + +class Airplay: MPVolumeView { + let config: AirplayConfig + + init(config: AirplayConfig = AirplayConfig()) { + self.config = config + super.init(frame: .zero) + accessibilityLabel = accessibilityLabel ?? config.identifier + tintColor = config.tintColor + backgroundColor = config.backgroundColor + showsRouteButton = true + showsVolumeSlider = false + translatesAutoresizingMaskIntoConstraints = false + self.sizeToFit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +// override func sizeThatFits(_ size: CGSize) -> CGSize { +// let superSize = super.sizeThatFits(size) +// return CGSize( +// width: 40.0, +// height: superSize.height) +// } +} + +// MARK: - Element +extension Airplay: Element { + var type: ElementType { return config.type } + var identifier: String? { return config.identifier } + var widthCalculation: ElementWidthCalculation { return config.widthCalculation } + var width: CGFloat { return config.width } + var marginLeft: CGFloat { return config.marginLeft } + var marginRight: CGFloat { return config.marginRight } + var view: UIView { return self } +} diff --git a/MobilePlayer/Views/Bar.swift b/MobilePlayer/Views/Bar.swift index 3107326..4b75397 100644 --- a/MobilePlayer/Views/Bar.swift +++ b/MobilePlayer/Views/Bar.swift @@ -60,6 +60,9 @@ class Bar: UIView { case .Slider: guard let sliderConfig = config as? SliderConfig else { return } elementView = Slider(config: sliderConfig) + case .Airplay: + guard let airplayConfig = config as? AirplayConfig else { return } + elementView = Airplay(config: airplayConfig) case .Unknown: elementView = nil } diff --git a/MobilePlayerExamples/AdvancedConfigAirplayExampleViewController.swift b/MobilePlayerExamples/AdvancedConfigAirplayExampleViewController.swift new file mode 100644 index 0000000..ac3f774 --- /dev/null +++ b/MobilePlayerExamples/AdvancedConfigAirplayExampleViewController.swift @@ -0,0 +1,37 @@ +// +// AdvancedConfigAirplayExampleViewController.swift +// MobilePlayer +// +// Created by Maca on 3/10/17. +// Copyright © 2017 MovieLaLa. All rights reserved. +// + +import UIKit +import MobilePlayer + +class AdvancedConfigAirplayExampleViewController: ExampleViewController { + + override init() { + super.init() + title = "Advanced w/Airplay Configuration" + codeImageView.image = UIImage(named: "AdvancedConfigExampleCode") + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func showButtonDidGetTapped() { + let bundle = Bundle.main + let config = MobilePlayerConfig(fileURL: bundle.url( + forResource: "Airplay", + withExtension: "json")!) + let playerVC = MobilePlayerViewController( + contentURL: videoURL, + config: config) + playerVC.title = videoTitle + playerVC.activityItems = [videoURL] + presentMoviePlayerViewControllerAnimated(playerVC) + } +} + diff --git a/MobilePlayerExamples/ExamplesTableViewController.swift b/MobilePlayerExamples/ExamplesTableViewController.swift index 5e57a1f..3f71b9c 100644 --- a/MobilePlayerExamples/ExamplesTableViewController.swift +++ b/MobilePlayerExamples/ExamplesTableViewController.swift @@ -16,6 +16,7 @@ class ExamplesTableViewController: UITableViewController { RemoteConfigExampleViewController(), ProgConfigExampleViewController(), AdvancedConfigExampleViewController(), + AdvancedConfigAirplayExampleViewController(), OverlayExampleViewController(), TimedOverlayExampleViewController(), PrerollExampleViewController(), diff --git a/MobilePlayerExamples/Resources/Airplay.json b/MobilePlayerExamples/Resources/Airplay.json new file mode 100644 index 0000000..da3eecc --- /dev/null +++ b/MobilePlayerExamples/Resources/Airplay.json @@ -0,0 +1,54 @@ +{ + "topBar": { + "backgroundColor": ["#a60500b0", "#a60500a0"], + "elements": [ + { + "type": "button", + "identifier": "close" + }, + { + "type": "slider", + "identifier": "playback", + "trackHeight": 6, + "trackCornerRadius": 3, + "minimumTrackTintColor": "#eee", + "availableTrackTintColor": "#9e9b9a", + "maximumTrackTintColor": "#cccccc", + "thumbTintColor": "#f9f9f9", + "thumbBorderWidth": 1, + "thumbBorderColor": "#fff", + "marginRight": 4 + } + ] + }, + "bottomBar": { + "backgroundColor": ["#a60500a0", "#a60500b0"], + "elements": [ + { + "type": "label", + "text": "Now Watching", + "font": "Baskerville", + "size": 12, + "marginLeft": 8, + "marginRight": 8 + }, + { + "type": "label", + "identifier": "title", + "size": 14 + }, + { + "type": "button", + "identifier": "action" + }, + { + "type": "toggleButton", + "identifier": "play" + }, + { + "type": "airplay", + "identifier": "router" + } + ] + } +}