TSBandRingTool.swift 9.9 KB

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