VLCRendererDiscovererManager.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /*****************************************************************************
  2. * VLCRendererDiscovererManager.swift
  3. *
  4. * Copyright © 2018 VLC authors and VideoLAN
  5. * Copyright © 2018 Videolabs
  6. *
  7. * Authors: Soomin Lee <bubu@mikan.io>
  8. *
  9. * Refer to the COPYING file of the official project for license.
  10. *****************************************************************************/
  11. @objc protocol VLCRendererDiscovererManagerDelegate {
  12. @objc optional func removedCurrentRendererItem(_ item: VLCRendererItem)
  13. }
  14. class VLCRendererDiscovererManager: NSObject {
  15. // Array of RendererDiscoverers(Chromecast, UPnP, ...)
  16. @objc var discoverers: [VLCRendererDiscoverer] = [VLCRendererDiscoverer]()
  17. @objc weak var delegate: VLCRendererDiscovererManagerDelegate?
  18. @objc lazy var actionSheet: VLCActionSheet = {
  19. let actionSheet = VLCActionSheet()
  20. actionSheet.delegate = self
  21. actionSheet.dataSource = self
  22. actionSheet.modalPresentationStyle = .custom
  23. actionSheet.setAction { [weak self] (item) in
  24. if let rendererItem = item as? VLCRendererItem {
  25. self?.setRendererItem(rendererItem: rendererItem)
  26. }
  27. }
  28. return actionSheet
  29. }()
  30. @objc var presentingViewController: UIViewController?
  31. @objc var rendererButtons: [UIButton] = [UIButton]()
  32. @objc init(presentingViewController: UIViewController?) {
  33. self.presentingViewController = presentingViewController
  34. super.init()
  35. }
  36. // Returns renderers of *all* discoverers
  37. @objc func getAllRenderers() -> [VLCRendererItem] {
  38. return discoverers.flatMap { $0.renderers }
  39. }
  40. fileprivate func isDuplicateDiscoverer(with description: VLCRendererDiscovererDescription) -> Bool {
  41. for discoverer in discoverers where discoverer.name == description.name {
  42. return true
  43. }
  44. return false
  45. }
  46. @objc func start() {
  47. // Gather potential renderer discoverers
  48. guard let tmpDiscoverersDescription: [VLCRendererDiscovererDescription] = VLCRendererDiscoverer.list() else {
  49. print("VLCRendererDiscovererManager: Unable to retrieve list of VLCRendererDiscovererDescription")
  50. return
  51. }
  52. for discovererDescription in tmpDiscoverersDescription where !isDuplicateDiscoverer(with: discovererDescription) {
  53. guard let rendererDiscoverer = VLCRendererDiscoverer(name: discovererDescription.name) else {
  54. print("VLCRendererDiscovererManager: Unable to instanciate renderer discoverer with name: \(discovererDescription.name)")
  55. continue
  56. }
  57. guard rendererDiscoverer.start() else {
  58. print("VLCRendererDiscovererManager: Unable to start renderer discoverer with name: \(rendererDiscoverer.name)")
  59. continue
  60. }
  61. rendererDiscoverer.delegate = self
  62. discoverers.append(rendererDiscoverer)
  63. }
  64. }
  65. @objc func stop() {
  66. for discoverer in discoverers {
  67. discoverer.stop()
  68. }
  69. discoverers.removeAll()
  70. }
  71. // MARK: VLCActionSheet
  72. @objc fileprivate func displayActionSheet() {
  73. guard let presentingViewController = presentingViewController else {
  74. assertionFailure("VLCRendererDiscovererManager: Cannot display actionSheet, no viewController setted")
  75. return
  76. }
  77. // If only one renderer, choose it automatically
  78. if getAllRenderers().count == 1, let rendererItem = getAllRenderers().first {
  79. let indexPath = IndexPath(row: 0, section: 0)
  80. actionSheet.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
  81. actionSheet(collectionView: actionSheet.collectionView, cellForItemAt: indexPath)
  82. actionSheet.action?(rendererItem)
  83. } else {
  84. presentingViewController.present(actionSheet, animated: false, completion: nil)
  85. }
  86. }
  87. fileprivate func setRendererItem(rendererItem: VLCRendererItem) {
  88. let vpcRenderer = VLCPlaybackController.sharedInstance().renderer
  89. var finalRendererItem: VLCRendererItem?
  90. var isSelected: Bool = false
  91. if vpcRenderer != rendererItem {
  92. finalRendererItem = rendererItem
  93. isSelected = true
  94. }
  95. VLCPlaybackController.sharedInstance().renderer = finalRendererItem
  96. for button in rendererButtons {
  97. button.isSelected = isSelected
  98. }
  99. }
  100. @objc func addSelectionHandler(_ selectionHandler: ((_ rendererItem: VLCRendererItem?) -> Void)?) {
  101. actionSheet.setAction { [weak self] (item) in
  102. if let rendererItem = item as? VLCRendererItem {
  103. //if we select the same renderer we want to disconnect
  104. let oldRenderer = VLCPlaybackController.sharedInstance().renderer
  105. self?.setRendererItem(rendererItem: rendererItem)
  106. if let handler = selectionHandler {
  107. handler(oldRenderer == rendererItem ? nil : rendererItem)
  108. }
  109. }
  110. }
  111. }
  112. /// Add the given button to VLCRendererDiscovererManager.
  113. /// The button state will be handled by the manager.
  114. ///
  115. /// - Returns: New `UIButton`
  116. @objc func setupRendererButton() -> UIButton {
  117. let button = UIButton()
  118. button.isHidden = getAllRenderers().isEmpty
  119. button.tintColor = PresentationTheme.current.colors.orangeUI
  120. button.setImage(UIImage(named: "renderer"), for: .normal)
  121. button.setImage(UIImage(named: "rendererFull"), for: .selected)
  122. button.addTarget(self, action: #selector(displayActionSheet), for: .touchUpInside)
  123. button.accessibilityLabel = NSLocalizedString("BUTTON_RENDERER", comment: "")
  124. button.accessibilityHint = NSLocalizedString("BUTTON_RENDERER_HINT", comment: "")
  125. rendererButtons.append(button)
  126. return button
  127. }
  128. }
  129. // MARK: VLCRendererDiscovererDelegate
  130. extension VLCRendererDiscovererManager: VLCRendererDiscovererDelegate {
  131. func rendererDiscovererItemAdded(_ rendererDiscoverer: VLCRendererDiscoverer, item: VLCRendererItem) {
  132. for button in rendererButtons {
  133. UIView.animate(withDuration: 0.1) {
  134. button.isHidden = false
  135. }
  136. }
  137. if actionSheet.viewIfLoaded?.window != nil {
  138. actionSheet.collectionView.reloadData()
  139. actionSheet.updateViewConstraints()
  140. }
  141. }
  142. func rendererDiscovererItemDeleted(_ rendererDiscoverer: VLCRendererDiscoverer, item: VLCRendererItem) {
  143. let playbackController = VLCPlaybackController.sharedInstance()
  144. // Current renderer has been removed
  145. if playbackController.renderer == item {
  146. playbackController.renderer = nil
  147. delegate?.removedCurrentRendererItem?(item)
  148. // Reset buttons state
  149. for button in rendererButtons {
  150. button.isSelected = false
  151. }
  152. }
  153. if actionSheet.viewIfLoaded?.window != nil {
  154. actionSheet.collectionView.reloadData()
  155. actionSheet.updateViewConstraints()
  156. }
  157. // No more renderers to show
  158. if getAllRenderers().isEmpty {
  159. for button in rendererButtons {
  160. UIView.animate(withDuration: 0.1) {
  161. button.isHidden = true
  162. }
  163. }
  164. actionSheet.removeActionSheet()
  165. }
  166. }
  167. fileprivate func updateCollectionViewCellApparence(cell: VLCActionSheetCell, highlighted: Bool) {
  168. var image = UIImage(named: "renderer")
  169. var textColor = PresentationTheme.current.colors.cellTextColor
  170. var tintColor = PresentationTheme.current.colors.cellDetailTextColor
  171. if highlighted {
  172. image = UIImage(named: "rendererFull")
  173. textColor = PresentationTheme.current.colors.orangeUI
  174. tintColor = PresentationTheme.current.colors.orangeUI
  175. }
  176. cell.tintColor = tintColor
  177. cell.icon.image = image
  178. cell.name.textColor = textColor
  179. }
  180. }
  181. // MARK: VLCActionSheetDelegate
  182. extension VLCRendererDiscovererManager: VLCActionSheetDelegate {
  183. func headerViewTitle() -> String? {
  184. return NSLocalizedString("HEADER_TITLE_RENDERER", comment: "")
  185. }
  186. func itemAtIndexPath(_ indexPath: IndexPath) -> Any? {
  187. let renderers = getAllRenderers()
  188. if indexPath.row < renderers.count {
  189. return renderers[indexPath.row]
  190. }
  191. assertionFailure("VLCRendererDiscovererManager: VLCActionSheetDelegate: IndexPath out of range")
  192. return nil
  193. }
  194. func actionSheet(collectionView: UICollectionView, didSelectItem item: Any, At indexPath: IndexPath) {
  195. guard let renderer = item as? VLCRendererItem,
  196. let cell = collectionView.cellForItem(at: indexPath) as? VLCActionSheetCell else {
  197. assertionFailure("VLCRendererDiscovererManager: VLCActionSheetDelegate: Cell is not a VLCActionSheetCell")
  198. return
  199. }
  200. let isCurrentlySelectedRenderer = renderer == VLCPlaybackController.sharedInstance().renderer
  201. if !isCurrentlySelectedRenderer {
  202. collectionView.reloadData()
  203. } else {
  204. delegate?.removedCurrentRendererItem?(renderer)
  205. }
  206. updateCollectionViewCellApparence(cell: cell, highlighted: isCurrentlySelectedRenderer)
  207. }
  208. }
  209. // MARK: VLCActionSheetDataSource
  210. extension VLCRendererDiscovererManager: VLCActionSheetDataSource {
  211. func numberOfRows() -> Int {
  212. return getAllRenderers().count
  213. }
  214. @discardableResult
  215. func actionSheet(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  216. guard let cell = collectionView.dequeueReusableCell(
  217. withReuseIdentifier: VLCActionSheetCell.identifier, for: indexPath) as? VLCActionSheetCell else {
  218. assertionFailure("VLCRendererDiscovererManager: VLCActionSheetDataSource: Unable to dequeue reusable cell")
  219. return UICollectionViewCell()
  220. }
  221. let renderers = getAllRenderers()
  222. if indexPath.row < renderers.count {
  223. cell.name.text = renderers[indexPath.row].name
  224. let isSelectedRenderer = renderers[indexPath.row] == VLCPlaybackController.sharedInstance().renderer ? true : false
  225. updateCollectionViewCellApparence(cell: cell, highlighted: isSelectedRenderer)
  226. } else {
  227. assertionFailure("VLCRendererDiscovererManager: VLCActionSheetDataSource: IndexPath out of range")
  228. }
  229. return cell
  230. }
  231. }