From 26a46c83a3e8a15df465cc065b659e60c84d24a4 Mon Sep 17 00:00:00 2001 From: "Mr.zhou" <1422157428@qq.com> Date: Wed, 22 May 2024 14:25:58 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=AF=B9=E7=BD=91=E7=BB=9C=E6=B3=A2?= =?UTF-8?q?=E5=8A=A8=E6=83=85=E5=86=B5=E7=9A=84=E5=88=9D=E7=BA=A7=E8=B0=83?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusicPlayer.xcodeproj/project.pbxproj | 4 + .../xcschemes/MusicPlayer.xcscheme | 12 ++ MusicPlayer/AppDelegate.swift | 1 + .../Common/Extension(扩展)/Notification.swift | 4 + .../Tool(工具封装)/MPPositive_Debouncer.swift | 33 ++++ .../Tool(工具封装)/MP_NetWorkManager.swift | 167 ++++++++---------- .../Tool(工具封装)/MP_PlayerManager.swift | 65 +++++-- .../MPPositive_SongViewModel.swift | 28 ++- .../MPPositive_PlayerLoadViewModel.swift | 62 ++++--- .../MPPositive_PlayerViewController.swift | 36 ++-- .../Player/MPPositive_PlayerCoverView.swift | 35 ++++ 11 files changed, 294 insertions(+), 153 deletions(-) create mode 100644 MusicPlayer/MP/Common/Tool(工具封装)/MPPositive_Debouncer.swift diff --git a/MusicPlayer.xcodeproj/project.pbxproj b/MusicPlayer.xcodeproj/project.pbxproj index 8b59c93..a1c040d 100644 --- a/MusicPlayer.xcodeproj/project.pbxproj +++ b/MusicPlayer.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ CBB5F1F92BFC35D000CBF73A /* MPPositive_CollectionSongModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB5F1F82BFC35D000CBF73A /* MPPositive_CollectionSongModel.swift */; }; CBB5F1FB2BFC3DB600CBF73A /* MPPositive_CollectionListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB5F1FA2BFC3DB600CBF73A /* MPPositive_CollectionListModel.swift */; }; CBB5F1FD2BFC40E400CBF73A /* MPPositive_CollectionArtistModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB5F1FC2BFC40E400CBF73A /* MPPositive_CollectionArtistModel.swift */; }; + CBB5F1FF2BFCB40000CBF73A /* MPPositive_Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB5F1FE2BFCB40000CBF73A /* MPPositive_Debouncer.swift */; }; CBB75B0B2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB75B0A2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift */; }; CBB9F9DD2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB9F9DC2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift */; }; CBB9F9DF2BEDDCC5008338DE /* MP_PlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB9F9DE2BEDDCC5008338DE /* MP_PlayerManager.swift */; }; @@ -229,6 +230,7 @@ CBB5F1F82BFC35D000CBF73A /* MPPositive_CollectionSongModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_CollectionSongModel.swift; sourceTree = ""; }; CBB5F1FA2BFC3DB600CBF73A /* MPPositive_CollectionListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_CollectionListModel.swift; sourceTree = ""; }; CBB5F1FC2BFC40E400CBF73A /* MPPositive_CollectionArtistModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_CollectionArtistModel.swift; sourceTree = ""; }; + CBB5F1FE2BFCB40000CBF73A /* MPPositive_Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_Debouncer.swift; sourceTree = ""; }; CBB75B0A2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_DownloadItemModel.swift; sourceTree = ""; }; CBB9F9DC2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonLyrics.swift; sourceTree = ""; }; CBB9F9DE2BEDDCC5008338DE /* MP_PlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_PlayerManager.swift; sourceTree = ""; }; @@ -964,6 +966,7 @@ CBCB32192BD7578500802900 /* MP_LocationManager.swift */, CBBFA9172BBA83BA00057FD5 /* MP_CoreDataHandlerManager.swift */, CBE2C4C62BC783F700F283A7 /* MP_HUD.swift */, + CBB5F1FE2BFCB40000CBF73A /* MPPositive_Debouncer.swift */, CBC687482BC2882B0023ECA6 /* MPTableManager.swift */, CBD958D12BB6600500666B0D /* MP_PlayerSlider.swift */, CB102F532BFAFA7200E967D8 /* MP_CircularProgressView.swift */, @@ -1172,6 +1175,7 @@ CB102F522BFAE73800E967D8 /* MPPositive_JsonRecommend.swift in Sources */, CBCB4FEF2BD11402009760B3 /* MPSideA_NavigationController.swift in Sources */, CBF456DD2BF1E72F00ABF761 /* MPPositive_SearchResultListViewModel.swift in Sources */, + CBB5F1FF2BFCB40000CBF73A /* MPPositive_Debouncer.swift in Sources */, CBEB01832BF5D88400D45006 /* MPPositive_ArtistShowCollectionViewCell.swift in Sources */, CBD5E80C2BF33D0200A3EBED /* MPPositive_SearchResultTypeShowView.swift in Sources */, CBCB35212BD7ACE900802900 /* MPPositive_JsonBrowse.swift in Sources */, diff --git a/MusicPlayer.xcodeproj/xcshareddata/xcschemes/MusicPlayer.xcscheme b/MusicPlayer.xcodeproj/xcshareddata/xcschemes/MusicPlayer.xcscheme index 36878cd..54aea04 100644 --- a/MusicPlayer.xcodeproj/xcshareddata/xcschemes/MusicPlayer.xcscheme +++ b/MusicPlayer.xcodeproj/xcshareddata/xcschemes/MusicPlayer.xcscheme @@ -49,6 +49,18 @@ ReferencedContainer = "container:MusicPlayer.xcodeproj"> + + + + + + Void)) { + // 取消之前的延迟调用 + timer?.invalidate() + // 设置新的延迟调用 + timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { _ in + action() + } + } +} diff --git a/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift b/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift index f1abbc0..4ef19cd 100644 --- a/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift +++ b/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift @@ -37,6 +37,30 @@ class MP_NetWorkManager: NSObject { private let search = "/search" ///YouTuBe资源键值 private let youTubeKeys:[String] = ["MUSIC_VIDEO_TYPE_ATV","MUSIC_VIDEO_TYPE_OMV","MUSIC_PAGE_TYPE_ALBUM","MUSIC_PAGE_TYPE_ARTIST","MUSIC_PAGE_TYPE_PLAYLIST","MUSIC_PAGE_TYPE_TRACK_LYRICS","MUSIC_PAGE_TYPE_TRACK_RELATED"] + ///当前网络状态 + enum NetWorkStatus: String { + case notReachable = "网络不可用" + case unknown = "网络未知" + case reachable = "网络可用" + } + private var netWorkStatu:NetWorkStatus = .reachable{ + willSet{ + DispatchQueue.main.async { + [weak self] in + guard let self = self else {return} + //旧值为网络可用,新值为网络不可用,为断网第一时间,发出通知告知播放器 + if netWorkStatu == .reachable, newValue == .notReachable { + print("网络不可用") + NotificationCenter.notificationKey.post(notificationName: .net_switch_notReachable) + } + //旧值为网络不可用,新值为网络可用,为网络回复第一时间,发出通知告知播放器 + if netWorkStatu == .notReachable, newValue == .reachable { + print("网络可用") + NotificationCenter.notificationKey.post(notificationName: .net_switch_reachable) + } + } + } + } //MARK: - 固定参数 //访问数据(首次首页预览时获得) private var visitorData:String? @@ -133,24 +157,23 @@ class MP_NetWorkManager: NSObject { monitor.start(queue: queue) } ///网络请求检测 - private func requestStatusToYouTube(_ isAilable:@escaping(Bool) -> Void) { + func requestStatusToYouTube() { //设置一个节点 let reachabilityManager = NetworkReachabilityManager(host: "https://music.youtube.com/") //通过ping节点确认是否能执行网络请求 - reachabilityManager?.startListening(onUpdatePerforming: { status in + reachabilityManager?.startListening(onQueue: .main, onUpdatePerforming: { [weak self] status in + guard let self = self else {return} switch status { case .unknown://未知状况 - isAilable(false) - print("网络情况未知") + netWorkStatu = .unknown case .notReachable://网络不可用 - isAilable(false) - print("网络不可用") + netWorkStatu = .notReachable case .reachable(.ethernetOrWiFi), .reachable(.cellular)://网络可用,且做出了分类 - //网络可用 - isAilable(true) + netWorkStatu = .reachable } }) } + } //MARK: - API请求 extension MP_NetWorkManager { @@ -189,11 +212,8 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostHomeBrowse(url, parameters: parameters) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostHomeBrowse(url, parameters: parameters) } } @@ -255,12 +275,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostAlbumOrList(url, parameters: parameters) { results in - comletion(results) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostAlbumOrList(url, parameters: parameters) { results in + comletion(results) } } //请求列表/专辑数据 @@ -315,12 +332,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostArtist(url, parameters: parameters) { result in - comletion(result) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostArtist(url, parameters: parameters) { result in + comletion(result) } } //请求艺术家信息 @@ -374,12 +388,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostArtistMore(url, parameters: parameters) { result in - comletion(result) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostArtistMore(url, parameters: parameters) { result in + comletion(result) } } ///请求艺术家更多数据 @@ -430,12 +441,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostArtistMoreContinuation(url, parameters: parameters) { result in - comletion(result) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostArtistMoreContinuation(url, parameters: parameters) { result in + comletion(result) } } ///请求艺术家更多数据继续 @@ -487,14 +495,10 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - //发送next列表请求 - self.requestPostNextList(url, parameters: parameters) { listSongs in - //成功拿到列表所有歌曲(内容尚不完善) - completion(listSongs) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostNextList(url, parameters: parameters) { listSongs in + //成功拿到列表所有歌曲(内容尚不完善) + completion(listSongs) } } //请求next列表 @@ -542,13 +546,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - //发送next列表歌词/相关内容请求 - self.requestPostNextLyricsAndRelated(url, parameters: parameters) { result in - completion(result) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostNextLyricsAndRelated(url, parameters: parameters) { result in + completion(result) } } //请求请求Next歌词/相关内容 @@ -596,12 +596,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in - completion(resourceUlrs, coverUrls) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in + completion(resourceUlrs, coverUrls) } } //请求单曲/视频 @@ -651,12 +648,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostLyric(url, parameters: parameters) { lyrics in - completion(lyrics) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostLyric(url, parameters: parameters) { lyrics in + completion(lyrics) } } //请求歌词 @@ -701,12 +695,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostRecommend(url, parameters: parameters) { results in - completion(results) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostRecommend(url, parameters: parameters) { results in + completion(results) } } ///请求相关内容 @@ -757,12 +748,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostSearchSuggestions(url, parameters: parameters) { result in - completion(result) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostSearchSuggestions(url, parameters: parameters) { result in + completion(result) } } //请求搜索建议 @@ -811,12 +799,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostSearchPreviewResults(url, parameters: parameters) { result in - completion(result) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostSearchPreviewResults(url, parameters: parameters) { result in + completion(result) } } //请求搜索预览结果 @@ -871,12 +856,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostSearchTypeResults(url, parameters: parameters) { result in - completion(result) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostSearchTypeResults(url, parameters: parameters) { result in + completion(result) } } //请求搜索分类结果 @@ -931,12 +913,9 @@ extension MP_NetWorkManager { ] ] ] - requestStatusToYouTube { isAvailable in - if isAvailable == true { - self.requestPostSearchTypeContinuation(url, parameters: parameters) { result in - completion(result) - } - } + guard netWorkStatu != .notReachable else {return} + requestPostSearchTypeContinuation(url, parameters: parameters) { result in + completion(result) } } //请求搜索分类继续结果 diff --git a/MusicPlayer/MP/Common/Tool(工具封装)/MP_PlayerManager.swift b/MusicPlayer/MP/Common/Tool(工具封装)/MP_PlayerManager.swift index 934580d..bd29342 100644 --- a/MusicPlayer/MP/Common/Tool(工具封装)/MP_PlayerManager.swift +++ b/MusicPlayer/MP/Common/Tool(工具封装)/MP_PlayerManager.swift @@ -103,7 +103,10 @@ class MP_PlayerManager:NSObject{ super.init() // 添加观察者,监听播放结束事件 NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_ :)), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem) + //监听用户切换了音乐 NotificationCenter.notificationKey.add(observer: self, selector: #selector(userSwitchCurrentVideoAction(_ :)), notificationName: .positive_player_reload) + //监听网络状态恢复可用 + NotificationCenter.notificationKey.add(observer: self, selector: #selector(netWorkReachableAction(_ :)), notificationName: .net_switch_reachable) } deinit { NotificationCenter.default.removeObserver(self) @@ -160,7 +163,23 @@ class MP_PlayerManager:NSObject{ loadPlayer.currentVideo?.resourcePlayerItem?.addObserver(self, forKeyPath: "status", options: [.old,.new], context: nil) //当前缓冲值 loadPlayer.currentVideo?.resourcePlayerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.old,.new], context: nil) + //是否具备足够播放的能力 + loadPlayer.currentVideo?.resourcePlayerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: [.old,.new], context: nil) + //启动全部预加载 + loadPlayer.listViewVideos.forEach({$0.preloadAsset($0.resourceAsset)}) } + ///网络状态恢复正常 + @objc private func netWorkReachableAction(_ sender:Notification) { + //监听到网络状态恢复,检索当前播放器是否正在播放 + if loadPlayer.currentVideo != nil { + //有音乐播放,获取当前播放进度 + let currentTime = loadPlayer.currentVideo!.resourcePlayerItem.currentTime() + //手动调整播放时间点,以此重启播放器缓存 + player.play() + playState = .Playing + } + } + //实现KVO监听 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard let keyPath = keyPath else { @@ -174,14 +193,6 @@ class MP_PlayerManager:NSObject{ if playState != .Playing { //当statuVlaue值等于playerItem准备播放的值,说明已经准备好播放 print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 已经准备好播放") - //还未播放当前音乐,启动播放 - print("开始播放音乐-\(loadPlayer.currentVideo?.title ?? "")") - player.play() - playState = .Playing - //执行开始播放闭包 - if startActionBlock != nil { - startActionBlock!() - } } }else { print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 未做好准备播放,失败原因是\(loadPlayer.currentVideo?.resourcePlayerItem.error?.localizedDescription ?? "")") @@ -211,7 +222,22 @@ class MP_PlayerManager:NSObject{ } case "playbackLikelyToKeepUp"://是否存在足够的数据开始播放 - break + if let playbackLikelyToKeepUp = change?[.newKey] as? Bool, playbackLikelyToKeepUp == true { + if playState != .Playing { + //还未播放当前音乐,启动播放 + print("播放音乐-\(loadPlayer.currentVideo?.title ?? "")") + player.play() + playState = .Playing + //执行开始播放闭包 + if startActionBlock != nil { + startActionBlock!() + } + } + }else { + //没有足够的数据支持播放 + player.pause() + playState = .Null + } default: break } @@ -324,7 +350,7 @@ class MP_PlayerManager:NSObject{ switch playType { case .random://随机,播放随机列表内容 for (index, item) in loadPlayer.randomVideos.enumerated() { - if item.videoId == loadPlayer.currentVideo.song.videoId { + if item.videoId == loadPlayer.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index - 1 } @@ -333,15 +359,15 @@ class MP_PlayerManager:NSObject{ if nextIndex < 0 { //播放列表最后一首 let last = loadPlayer.randomVideos.last - loadPlayer.improveData(last?.videoId ?? "") + loadPlayer.improveData(last?.videoId ?? "", isRandom: true) }else { //查询列表对应单曲 let song = loadPlayer.randomVideos[nextIndex] - loadPlayer.improveData(song.videoId ?? "") + loadPlayer.improveData(song.videoId ?? "", isRandom: true) } default://常规播放或者单曲播放 for (index, item) in loadPlayer.songVideos.enumerated() { - if item.videoId == loadPlayer.currentVideo.song.videoId { + if item.videoId == loadPlayer.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index - 1 } @@ -366,7 +392,7 @@ class MP_PlayerManager:NSObject{ switch playType { case .random: for (index, item) in loadPlayer.randomVideos.enumerated() { - if item.videoId == loadPlayer.currentVideo.song.videoId { + if item.videoId == loadPlayer.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index + 1 } @@ -375,15 +401,15 @@ class MP_PlayerManager:NSObject{ if nextIndex > (loadPlayer.randomVideos.count-1) { //播放列表第一首 let first = loadPlayer.randomVideos.first - loadPlayer.improveData(first?.videoId ?? "") + loadPlayer.improveData(first?.videoId ?? "", isRandom: true) }else { //存在下一首,获取下一首ID,并播放 let song = loadPlayer.randomVideos[nextIndex] - loadPlayer.improveData(song.videoId ?? "") + loadPlayer.improveData(song.videoId ?? "", isRandom: true) } default: for (index, item) in loadPlayer.songVideos.enumerated() { - if item.videoId == loadPlayer.currentVideo.song.videoId { + if item.videoId == loadPlayer.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index + 1 } @@ -411,7 +437,10 @@ class MP_PlayerManager:NSObject{ //切歌时移除KVO监听 video.resourcePlayerItem.removeObserver(self, forKeyPath: "status") video.resourcePlayerItem.removeObserver(self, forKeyPath: "loadedTimeRanges") -// video.resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp") + video.resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp") + } + if cacheValueBlock != nil { + cacheValueBlock!(0, 1) } if loadPlayer.currentVideo != nil { //开始播放 diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_SongViewModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_SongViewModel.swift index 978b2d9..ec6b6a5 100644 --- a/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_SongViewModel.swift +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_SongViewModel.swift @@ -33,13 +33,19 @@ class MPPositive_SongViewModel: NSObject { var song:MPPositive_SongItemModel! ///是否进行过预加载 var isPloading:Bool = false + // 标记为已取消 + private var isCancelled = false init(_ song:MPPositive_SongItemModel) { super.init() self.song = song configure() } deinit { - + //释放内存 + resourcePlayerItem = nil + resourceAsset = nil + isCancelled = true + print("\(title ?? "")被释放了") } //数据配置 private func configure() { @@ -76,9 +82,9 @@ class MPPositive_SongViewModel: NSObject { } reloadCollectionAndDownLoad() //执行预加载 - if isPloading == false { - preloadAsset(resourceAsset) - } +// if isPloading == false { +// preloadAsset(resourceAsset) +// } } //页面状态更新 func reloadCollectionAndDownLoad() { @@ -91,12 +97,19 @@ class MPPositive_SongViewModel: NSObject { //执行预加载 func preloadAsset(_ asset:AVURLAsset) { + guard isPloading == false else { + return + } + self.isPloading = true //执行预加载 if #available(iOS 16, *) { //ios16以上的情况 Task{ do{ let playable = try await asset.load(.isPlayable) + guard !isCancelled else { + return + } if playable == true { print("\(self.title ?? "")预加载成功") self.isPloading = true @@ -105,8 +118,9 @@ class MPPositive_SongViewModel: NSObject { switch asset.status(of: .isPlayable) { case .failed(let erro): print("\(title ?? "")预加载失败,失败原因:\(erro.localizedDescription)") + self.isPloading = false default: - break + self.isPloading = false } } }catch{ @@ -118,7 +132,7 @@ class MPPositive_SongViewModel: NSObject { let keys = ["playable"] asset.loadValuesAsynchronously(forKeys: keys) { [weak self] in - guard let self = self else {return} + guard let self = self, !isCancelled else {return} for key in keys { var error: NSError? = nil let status = asset.statusOfValue(forKey: key, error: &error) @@ -131,8 +145,10 @@ class MPPositive_SongViewModel: NSObject { } case .failed: print("\(title ?? "")预加载失败,失败原因:\(error?.localizedDescription ?? "")") + self.isPloading = false case .cancelled: print("\(title ?? "")预加载被取消了") + self.isPloading = false default: break } diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_PlayerLoadViewModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_PlayerLoadViewModel.swift index e4d0637..d44a82c 100644 --- a/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_PlayerLoadViewModel.swift +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_PlayerLoadViewModel.swift @@ -26,9 +26,8 @@ class MPPositive_PlayerLoadViewModel: NSObject { } } } - ///当前播放音乐ID - var currentVideoId:String! - ///单曲ViewModel列表 + + ///单曲播放队列ViewModel var listViewVideos:[MPPositive_SongViewModel]! ///异步请求组 var group:DispatchGroup? @@ -42,26 +41,42 @@ class MPPositive_PlayerLoadViewModel: NSObject { //根据列表生成一份随机播放列表 self.randomVideos = self.songVideos.shuffled() self.listViewVideos = [] - self.currentVideoId = currentVideoId } ///将选中Video的上下2项包括本身总计3项Video进行补全转为ViewModel,并播放这首音乐 - func improveData(_ targetVideoId:String) { - //获取targetVideoId的索引 - guard let targetIndex = self.songVideos.firstIndex(where: {$0.videoId == targetVideoId}) else { - return - } + func improveData(_ targetVideoId:String, isRandom:Bool = false) { //对于选中Video的集合 var array:[MPPositive_SongItemModel] = [] - array.append(self.songVideos[targetIndex]) - //获取上一位 - let previousIndex = targetIndex-1 - if previousIndex >= 0 { - array.append(self.songVideos[previousIndex]) - } - let nextIndex = targetIndex+1 - if nextIndex < songVideos.count { - array.append(self.songVideos[nextIndex]) + if isRandom { + //获取targetVideoId的索引 + guard let targetIndex = self.randomVideos.firstIndex(where: {$0.videoId == targetVideoId}) else { + return + } + array.append(self.randomVideos[targetIndex]) + //获取上一位 + let previousIndex = targetIndex-1 + if previousIndex >= 0 { + array.append(self.randomVideos[previousIndex]) + } + let nextIndex = targetIndex+1 + if nextIndex < randomVideos.count { + array.append(self.randomVideos[nextIndex]) + } + }else { + //获取targetVideoId的索引 + guard let targetIndex = self.songVideos.firstIndex(where: {$0.videoId == targetVideoId}) else { + return + } + array.append(self.songVideos[targetIndex]) + //获取上一位 + let previousIndex = targetIndex-1 + if previousIndex >= 0 { + array.append(self.songVideos[previousIndex]) + } + let nextIndex = targetIndex+1 + if nextIndex < songVideos.count { + array.append(self.songVideos[nextIndex]) + } } //获取完成,优先检索ViewModel,看看是否已存在补完video let videoIDs = Set(listViewVideos.map({$0.song.videoId})) @@ -89,11 +104,12 @@ class MPPositive_PlayerLoadViewModel: NSObject { } } group?.notify(queue: .main, execute: { - //补完转化完毕,重新排序 - self.listViewVideos = self.listViewVideos.sorted(by: {$0.index < $1.index}) - //排序完成,确定播放音乐 - self.currentVideo = self.listViewVideos.first(where: {$0.song.videoId == targetVideoId}) - self.group = nil + [weak self] in + //确定播放音乐 + self?.currentVideo = self?.listViewVideos.first(where: {$0.song.videoId == targetVideoId}) + //只保留最后三首 + self?.listViewVideos = self?.listViewVideos.suffix(3) + self?.group = nil }) } ///重新获取指定歌曲资源 diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift index 8298d5e..e7442b9 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift @@ -468,26 +468,38 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont } //切换播放器状态(按顺序/随机/单曲) @objc private func typeClick(_ sender:UIButton) { - //对播放器播放方式截形切换 - var value = MP_PlayerManager.shared.getPlayType().rawValue - value += 1 - if value > 2 { - value = 0 + MPPositive_Debouncer.shared.call { + [weak self] in + guard let self = self else {return} + //对播放器播放方式截形切换 + var value = MP_PlayerManager.shared.getPlayType().rawValue + value += 1 + if value > 2 { + value = 0 + } + MP_PlayerManager.shared.setPlayType(.init(rawValue: value)!) } - MP_PlayerManager.shared.setPlayType(.init(rawValue: value)!) } //下一首 @objc private func nextClick(_ sender:UIButton) { - coverView.sliderView.value = 0 - playBtn.isUserInteractionEnabled = false - MP_PlayerManager.shared.nextEvent() + MPPositive_Debouncer.shared.call { + [weak self] in + guard let self = self else {return} + coverView.sliderView.value = 0 + playBtn.isUserInteractionEnabled = false + MP_PlayerManager.shared.nextEvent() + } } //上一首 @objc private func previousClick(_ sender:UIButton) { - coverView.sliderView.value = 0 - playBtn.isUserInteractionEnabled = false - MP_PlayerManager.shared.previousEvent() + MPPositive_Debouncer.shared.call { + [weak self] in + guard let self = self else {return} + coverView.sliderView.value = 0 + playBtn.isUserInteractionEnabled = false + MP_PlayerManager.shared.previousEvent() + } } } diff --git a/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift b/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift index fa0d870..49a98dd 100644 --- a/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift +++ b/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift @@ -60,16 +60,37 @@ class MPPositive_PlayerCoverView: UIView { lazy var durationLabel:UILabel = createLabel("00:00" ,font: .systemFont(ofSize: 12*width, weight: .medium), textColor: .init(hex: "#FFFFFF", alpha: 0.85), textAlignment: .left) ///最大播放时间值Label lazy var maxTimesLabel:UILabel = createLabel("00:00" ,font: .systemFont(ofSize: 12*width, weight: .medium), textColor: .init(hex: "#FFFFFF", alpha: 0.6), textAlignment: .right) + ///断网提醒View + private lazy var maskNotReachableView:UIView = { + let maskView = UIView() + maskView.backgroundColor = .init(hex: "#000000", alpha: 0.7) + maskView.layer.masksToBounds = true + maskView.layer.cornerRadius = 16*width + //放置一个label + let noticeLabel:UILabel = createLabel("The network connection is disconnected and the player will stop loading music. Please restore the network as soon as possible!", font: .systemFont(ofSize: 18, weight: .medium), textColor: .white, textAlignment: .center, lines: 0) + maskView.addSubview(noticeLabel) + noticeLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.width.equalToSuperview().multipliedBy(0.7) + } + return maskView + }() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .clear configure() + //添加监听 + NotificationCenter.notificationKey.add(observer: self, selector: #selector(netWorkNotReachableAction(_:)), notificationName: .net_switch_notReachable) + NotificationCenter.notificationKey.add(observer: self, selector: #selector(netWorkReachableAction(_:)), notificationName: .net_switch_reachable) } required init?(coder: NSCoder) { super.init(coder: coder) } + deinit { + NotificationCenter.default.removeObserver(self) + } //视图配置 private func configure() { //配置封面图 @@ -80,6 +101,11 @@ class MPPositive_PlayerCoverView: UIView { make.centerX.equalToSuperview() make.top.equalToSuperview().offset(12*width) } + addSubview(maskNotReachableView) + maskNotReachableView.snp.makeConstraints { make in + make.left.right.top.bottom.equalTo(coverImageView) + } + maskNotReachableView.isHidden = true //添加标题 addSubview(titleLabel) titleLabel.snp.makeConstraints { make in @@ -128,6 +154,15 @@ class MPPositive_PlayerCoverView: UIView { make.top.equalTo(sliderView.snp.bottom).offset(5*width) } } + //网络不可用时触发 + @objc private func netWorkNotReachableAction(_ sender:Notification) { + maskNotReachableView.isHidden = false + } + //网络可用时触发 + @objc private func netWorkReachableAction(_ sender:Notification) { + maskNotReachableView.isHidden = true + } + //调整音乐进度 @objc private func seekProgressClick(_ sender: UISlider, forEvent event: UIEvent) { //获取touchEvent From f75ff871a25cbc227c28a3b3df6c69c28a2ba739 Mon Sep 17 00:00:00 2001 From: "Mr.zhou" <1422157428@qq.com> Date: Wed, 22 May 2024 16:41:41 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=92=AD=E6=94=BE=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E5=86=8D=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusicPlayer/AppDelegate.swift | 2 +- .../Tool(工具封装)/MP_NetWorkManager.swift | 35 +++++++++---------- .../Tool(工具封装)/MP_PlayerManager.swift | 4 +-- .../MPPositive_PlayerViewController.swift | 2 +- .../Player/MPPositive_PlayerCoverView.swift | 12 +++++-- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/MusicPlayer/AppDelegate.swift b/MusicPlayer/AppDelegate.swift index 00a9cbe..4b6f27d 100644 --- a/MusicPlayer/AppDelegate.swift +++ b/MusicPlayer/AppDelegate.swift @@ -8,6 +8,7 @@ import UIKit import CoreData import AVFoundation +import Alamofire @_exported import IQKeyboardManagerSwift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -81,7 +82,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window?.rootViewController = tabBarVC window?.makeKeyAndVisible() } - // MARK: - Core Data stack lazy var persistentContainer: NSPersistentContainer = { /* diff --git a/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift b/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift index 4ef19cd..2108886 100644 --- a/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift +++ b/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift @@ -37,30 +37,30 @@ class MP_NetWorkManager: NSObject { private let search = "/search" ///YouTuBe资源键值 private let youTubeKeys:[String] = ["MUSIC_VIDEO_TYPE_ATV","MUSIC_VIDEO_TYPE_OMV","MUSIC_PAGE_TYPE_ALBUM","MUSIC_PAGE_TYPE_ARTIST","MUSIC_PAGE_TYPE_PLAYLIST","MUSIC_PAGE_TYPE_TRACK_LYRICS","MUSIC_PAGE_TYPE_TRACK_RELATED"] - ///当前网络状态 + //网络状态 enum NetWorkStatus: String { case notReachable = "网络不可用" case unknown = "网络未知" case reachable = "网络可用" } - private var netWorkStatu:NetWorkStatus = .reachable{ + ///网络监听器 + private let reachabilityManager:NetworkReachabilityManager = NetworkReachabilityManager(host: "https://music.youtube.com/")! + ///当前网络状态 + var netWorkStatu:NetWorkStatus!{ willSet{ - DispatchQueue.main.async { - [weak self] in - guard let self = self else {return} - //旧值为网络可用,新值为网络不可用,为断网第一时间,发出通知告知播放器 - if netWorkStatu == .reachable, newValue == .notReachable { - print("网络不可用") - NotificationCenter.notificationKey.post(notificationName: .net_switch_notReachable) - } - //旧值为网络不可用,新值为网络可用,为网络回复第一时间,发出通知告知播放器 - if netWorkStatu == .notReachable, newValue == .reachable { - print("网络可用") - NotificationCenter.notificationKey.post(notificationName: .net_switch_reachable) - } + //旧值为网络可用,新值为网络不可用,为断网第一时间,发出通知告知播放器 + if netWorkStatu == .reachable, newValue == .notReachable { + print("网络不可用") + NotificationCenter.notificationKey.post(notificationName: .net_switch_notReachable) + } + //旧值为网络不可用,新值为网络可用,为网络回复第一时间,发出通知告知播放器 + if (netWorkStatu == .notReachable), newValue == .reachable { + print("网络可用") + NotificationCenter.notificationKey.post(notificationName: .net_switch_reachable) } } } + //MARK: - 固定参数 //访问数据(首次首页预览时获得) private var visitorData:String? @@ -158,10 +158,8 @@ class MP_NetWorkManager: NSObject { } ///网络请求检测 func requestStatusToYouTube() { - //设置一个节点 - let reachabilityManager = NetworkReachabilityManager(host: "https://music.youtube.com/") //通过ping节点确认是否能执行网络请求 - reachabilityManager?.startListening(onQueue: .main, onUpdatePerforming: { [weak self] status in + reachabilityManager.startListening(onQueue: .main, onUpdatePerforming: { [weak self] status in guard let self = self else {return} switch status { case .unknown://未知状况 @@ -173,7 +171,6 @@ class MP_NetWorkManager: NSObject { } }) } - } //MARK: - API请求 extension MP_NetWorkManager { diff --git a/MusicPlayer/MP/Common/Tool(工具封装)/MP_PlayerManager.swift b/MusicPlayer/MP/Common/Tool(工具封装)/MP_PlayerManager.swift index bd29342..ebafe98 100644 --- a/MusicPlayer/MP/Common/Tool(工具封装)/MP_PlayerManager.swift +++ b/MusicPlayer/MP/Common/Tool(工具封装)/MP_PlayerManager.swift @@ -171,10 +171,11 @@ class MP_PlayerManager:NSObject{ ///网络状态恢复正常 @objc private func netWorkReachableAction(_ sender:Notification) { //监听到网络状态恢复,检索当前播放器是否正在播放 - if loadPlayer.currentVideo != nil { + if loadPlayer?.currentVideo != nil { //有音乐播放,获取当前播放进度 let currentTime = loadPlayer.currentVideo!.resourcePlayerItem.currentTime() //手动调整播放时间点,以此重启播放器缓存 + player.seek(to: currentTime) player.play() playState = .Playing } @@ -225,7 +226,6 @@ class MP_PlayerManager:NSObject{ if let playbackLikelyToKeepUp = change?[.newKey] as? Bool, playbackLikelyToKeepUp == true { if playState != .Playing { //还未播放当前音乐,启动播放 - print("播放音乐-\(loadPlayer.currentVideo?.title ?? "")") player.play() playState = .Playing //执行开始播放闭包 diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift index e7442b9..d90f804 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift @@ -307,7 +307,7 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont coverView.subtitleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.subtitle lyricsView.titleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.title lyricsView.subtitleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.subtitle - lyricsView.lyricsLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.lyrics ?? "No Lyrics" + lyricsView.lyricsLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.lyrics?.isEmpty == true ? "No Lyrics":MP_PlayerManager.shared.loadPlayer.currentVideo?.lyrics coverView.loadBtn.isSelected = MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd ?? false coverView.collectionSongBtn.isSelected = MP_PlayerManager.shared.loadPlayer.currentVideo?.isCollection ?? false } diff --git a/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift b/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift index 49a98dd..42b9ca2 100644 --- a/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift +++ b/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift @@ -156,11 +156,19 @@ class MPPositive_PlayerCoverView: UIView { } //网络不可用时触发 @objc private func netWorkNotReachableAction(_ sender:Notification) { - maskNotReachableView.isHidden = false + DispatchQueue.main.async { + [weak self] in + guard let self = self else {return} + maskNotReachableView.isHidden = false + } } //网络可用时触发 @objc private func netWorkReachableAction(_ sender:Notification) { - maskNotReachableView.isHidden = true + DispatchQueue.main.async { + [weak self] in + guard let self = self else {return} + maskNotReachableView.isHidden = true + } } //调整音乐进度