TSBandRingTool.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. //
  2. // TSBandRingTool.swift
  3. // AIRingtone
  4. //
  5. // Created by 100Years on 2025/3/4.
  6. //
  7. import AVKit
  8. import AVFoundation
  9. class TSBandRingTool:NSObject {
  10. static var shared = TSBandRingTool()
  11. private var tutorialPlayer: AVPlayer?
  12. private var pictureController: AVPictureInPictureController?
  13. private lazy var playContentView = UIView(frame: UIScreen.main.bounds)
  14. private lazy var audioConvertTool = AudioTool()
  15. lazy var ringLoadingView: TSRingLoadingView = {
  16. let ringLoadingView = TSRingLoadingView(frame: CGRectMake(0, 0, k_ScreenWidth, k_ScreenHeight))
  17. return ringLoadingView
  18. }()
  19. weak var targetVC:UIViewController?
  20. // init(targetVC: UIViewController) {
  21. // self.targetVC = targetVC
  22. // }
  23. static func creatBandRingTool() -> TSBandRingTool{
  24. TSBandRingTool.shared = TSBandRingTool()
  25. return TSBandRingTool.shared
  26. }
  27. deinit {
  28. dePrint("TSBandRingTool deinit")
  29. }
  30. func checkGarageBandInstallation() -> Bool {
  31. // GarageBand 的 URL Scheme
  32. let garageBandScheme = "garageband://"
  33. if let url = URL(string: garageBandScheme), UIApplication.shared.canOpenURL(url) {
  34. print("GarageBand 已安装")
  35. return true
  36. } else {
  37. let ac = UIAlertController(title: nil,
  38. message: "GarageBand is not installed, you need to install it.", preferredStyle: .alert)
  39. ac.addAction(UIAlertAction(title: "Cancel", style: .cancel))
  40. ac.addAction(UIAlertAction(title: "Download", style: .default, handler: { _ in
  41. // GarageBand 在 App Store 的链接
  42. let garageBandAppStoreURL = URL(string: "https://apps.apple.com/app/id408709785")!
  43. if UIApplication.shared.canOpenURL(garageBandAppStoreURL) {
  44. UIApplication.shared.open(garageBandAppStoreURL, options: [:], completionHandler: nil)
  45. }
  46. }))
  47. targetVC?.present(ac, animated: true)
  48. return false
  49. }
  50. }
  51. func shareBandVC(vc:UIViewController,
  52. fileURL: URL,
  53. fileName:String?,
  54. completion: ((Bool) -> Void)? = nil) {
  55. if checkGarageBandInstallation() == false {
  56. completion?(false)
  57. return
  58. }
  59. if TSFileManagerTool.fileExists(at: fileURL) == false{
  60. completion?(false)
  61. return
  62. }
  63. self.targetVC = vc
  64. // if fileURLString.contains("http") {
  65. // if let window = WindowHelper.getKeyWindow() {
  66. // window.addSubview(ringLoadingView)
  67. // ringLoadingView.isRotating = true
  68. // }
  69. // _ = TSDownloadManager.downloadFile(urlString: fileURLString,missingEx: "mp3") {[weak self] url, error in
  70. // guard let self = self else { return }
  71. // ringLoadingView.removeFromSuperview()
  72. self.createBand(with: fileURL, fileName: fileName) { bandURL in
  73. if let url = bandURL {
  74. completion?(true)
  75. self.shareRing(fileUrl: url)
  76. }else{
  77. completion?(false)
  78. dePrint("Failed to set, please try another")
  79. }
  80. }
  81. // TSCommonTool.downloadAndCacheFile(from: fileURLString) { [weak self] path, error in
  82. // guard let self = self else { return }
  83. //// TSLoadingAnimation.hideLoading()
  84. // ringLoadingView.removeFromSuperview()
  85. // if let path = path,let url = URL(string: path) {
  86. // self.createBand(with: url, fileName: fileName) { bandURL in
  87. // if let url = bandURL {
  88. // completion?(true)
  89. // self.shareRing(fileUrl: url)
  90. // }else{
  91. // completion?(false)
  92. // dePrint("Failed to set, please try another")
  93. // }
  94. // }
  95. // }else{
  96. // dePrint("downloadAndCacheFile = \(error?.localizedDescription)")
  97. // completion?(false)
  98. // }
  99. // }
  100. // }else{
  101. // dePrint("ringtone no http")
  102. // completion?(false)
  103. // }
  104. }
  105. // 创建用于分享库乐队的band文件
  106. func createBand(with fileURL: URL?, fileName: String?,
  107. completion: ((URL?) -> Void)?) {
  108. let fileName = fileName ?? "Ringtone"
  109. guard let fileURL = fileURL,
  110. let bandPath = Self.bandPlacefolder,
  111. let directory = Self.ringDirectory else {
  112. completion?(nil)
  113. return
  114. }
  115. let bandURL = URL(fileURLWithPath: bandPath)
  116. let outputURL = URL(fileURLWithPath: directory).appendingPathComponent(fileName)
  117. audioConvertTool.convertToBand(from: fileURL, to: outputURL.path, bandFileURL: bandURL) { shareBandURL in
  118. completion?(shareBandURL)
  119. }
  120. }
  121. func shareRing(fileUrl: URL) {
  122. DispatchQueue.main.async {
  123. self.showTutorialVideo()
  124. let vc = UIActivityViewController(activityItems: [fileUrl], applicationActivities: nil)
  125. // 排除不需要的活动类型
  126. // vc.excludedActivityTypes = [
  127. // .postToFacebook,
  128. // .postToTwitter,
  129. // .postToWeibo,
  130. // .message,
  131. // .mail,
  132. // .print,
  133. // .copyToPasteboard,
  134. // .assignToContact,
  135. // .saveToCameraRoll,
  136. // .addToReadingList,
  137. // .postToFlickr,
  138. // .postToVimeo,
  139. // .postToTencentWeibo,
  140. // .airDrop,
  141. // .openInIBooks
  142. // ]
  143. self.targetVC?.present(vc, animated: true, completion: {
  144. self.tryStartPictureInPicture()
  145. })
  146. }
  147. }
  148. }
  149. extension TSBandRingTool : AVPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate {
  150. func tryStartPictureInPicture() {
  151. guard let player = tutorialPlayer else {
  152. return
  153. }
  154. if player.status == .readyToPlay {
  155. self.pictureController?.startPictureInPicture()
  156. tutorialPlayer?.play()
  157. return
  158. }
  159. DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
  160. self.tryStartPictureInPicture()
  161. }
  162. }
  163. func showTutorialVideo() {
  164. guard let url = Bundle.main.url(forResource: "tutorial-ring", withExtension: "mp4") else {
  165. return
  166. }
  167. guard AVPictureInPictureController.isPictureInPictureSupported() else {
  168. return
  169. }
  170. do {
  171. try AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers)
  172. } catch {
  173. }
  174. let playerItem = AVPlayerItem(asset: AVAsset(url: url))
  175. if tutorialPlayer == nil {
  176. tutorialPlayer = AVPlayer(playerItem: playerItem)
  177. } else {
  178. tutorialPlayer?.replaceCurrentItem(with: playerItem)
  179. }
  180. tutorialPlayer?.allowsExternalPlayback = true
  181. targetVC?.view.addSubview(playContentView)
  182. let layer = AVPlayerLayer(player: tutorialPlayer)
  183. layer.frame = CGRect(x: 0, y: 0, width: 1, height: 1)
  184. playContentView.layer.addSublayer(layer)
  185. let pictureVC = AVPictureInPictureController(playerLayer: layer)
  186. pictureVC?.delegate = self
  187. pictureController = pictureVC
  188. }
  189. func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
  190. playContentView.removeFromSuperview()
  191. }
  192. func playerViewControllerWillStartPictureInPicture(_ playerViewController: AVPlayerViewController) {
  193. }
  194. func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_ playerViewController: AVPlayerViewController) -> Bool {
  195. return true
  196. }
  197. func playerViewController(_ playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
  198. DispatchQueue.main.async {
  199. if playerViewController.presentingViewController == nil {
  200. self.targetVC?.present(playerViewController, animated: false)
  201. }
  202. completionHandler(true)
  203. }
  204. }
  205. }
  206. extension TSBandRingTool {
  207. static var bandPlacefolder: String? {
  208. guard let cacheDirectory = ringDirectory else {
  209. return nil
  210. }
  211. let path = NSString(string: cacheDirectory).appendingPathComponent("placeHolderBand.band")
  212. // 步骤1
  213. if !FileManager.default.fileExists(atPath: path),
  214. let localFile = Bundle.main.path(forResource: "placeholder.band", ofType: nil) {
  215. try? FileManager.default.copyItem(atPath: localFile, toPath: path)
  216. }
  217. return path
  218. }
  219. // 铃声文件夹
  220. static var ringDirectory: String? {
  221. guard let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
  222. return nil
  223. }
  224. let ringDirectory = documentUrl.appendingPathComponent("ringDirectory")
  225. // 创建文件夹
  226. return FileManager.default.getFolder(at: ringDirectory)?.path
  227. }
  228. }
  229. extension FileManager {
  230. // 获取文件夹,如果不存在,则创建, 返回空则意味着创建失败
  231. func getFolder(at url: URL) -> URL? {
  232. guard !isFolderExists(at: url) else {
  233. return url
  234. }
  235. do {
  236. try createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
  237. return url
  238. } catch {
  239. return nil
  240. }
  241. }
  242. func isFolderExists(at url: URL) -> Bool {
  243. var isDirectory: ObjCBool = false
  244. if fileExists(atPath: url.path, isDirectory: &isDirectory) && isDirectory.boolValue == true {
  245. return true // 文件夹已存在
  246. } else {
  247. return false // 文件夹不存在或者路径错误
  248. }
  249. }
  250. }