From 4f68305942dc99ac5c345b6fc69600eb0a39d9c4 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 30 Mar 2023 17:06:09 +0800 Subject: [PATCH] fixed background playback issue, added systemwide playback control, bug fixes --- MusicPlayer Watch App/ContentView.swift | 113 ++++++--- MusicPlayer Watch App/PlaybackView.swift | 50 ++-- MusicPlayer-Watch-App-Info.plist | 12 + MusicPlayer.xcodeproj/project.pbxproj | 8 +- .../xcdebugger/Breakpoints_v2.xcbkptlist | 231 +++++++++++++++++- 5 files changed, 351 insertions(+), 63 deletions(-) diff --git a/MusicPlayer Watch App/ContentView.swift b/MusicPlayer Watch App/ContentView.swift index dd2b5c1..7dbfb6e 100644 --- a/MusicPlayer Watch App/ContentView.swift +++ b/MusicPlayer Watch App/ContentView.swift @@ -9,13 +9,16 @@ import SwiftUI import Network import AVFoundation import UIKit +import WatchKit +import MediaPlayer class TrackInfo : NSObject, Identifiable, ObservableObject { @Published var s : String = "" - @Published var art : Image? = nil + @Published var art : UIImage? = nil @Published var m : AVPlayerItem? = nil @Published var changed = false var cv : ContentView? = nil + var background = false override init() { super.init() } @@ -37,10 +40,30 @@ class TrackInfo : NSObject, Identifiable, ObservableObject { self.changed = !self.changed return } + if (context == nil) { + if let change = change, + let keyPath = keyPath, + let cv = self.cv, + keyPath == "timeControlStatus" && self.background + { + let old = change[.oldKey] as! Int + let new = change[.newKey] as! Int + if (new == 0 && old != 0) { + cv.player.playImmediately(atRate: 1) + } + } + } if let cv = self.cv { if let idx = cv.music.music.firstIndex(where: { s in change![.newKey] as? AVPlayerItem == s.m }){ + var nowPlayingInfo = [String: Any]() + nowPlayingInfo[MPMediaItemPropertyTitle] = self.s + nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = cv.player.currentItem?.duration + nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = cv.player.currentTime() + nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = cv.player.rate + //nowPlayingInfo[MPMediaItemPropertyArtwork] = self.art + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo self.from(other: cv.music.music[idx]) cv.update_pbv(idx: idx) } @@ -74,14 +97,39 @@ class PlaybackViewProxy { } struct ContentView: View { + @Environment(\.scenePhase) private var scenePhase @ObservedObject var music = ListViewModel() @ObservedObject var nowplaying : TrackInfo @State var pushState = false @State var geo:CGSize = .zero @State var active = false + var audio_session: AVAudioSession = AVAudioSession.sharedInstance() //@State var _curr_sel_music : TrackInfo = TrackInfo() var pbv : PlaybackViewProxy var dir: String + var cc = MPRemoteCommandCenter.shared() + func play() { + do { + try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, policy:.longForm) + try AVAudioSession.sharedInstance().setActive(true) + + self.player.play() + } catch { + print("Error playing audio: \(error)") + } + /*do { + try audio_session.setCategory(AVAudioSession.Category.ambient, options: .mixWithOthers) + try audio_session.setActive(true) + audio_session.activate { _, e in + if e == nil { + self.player.audiovisualBackgroundPlaybackPolicy = .continuesIfPossible + self.player.playImmediately(atRate: 1) + } + } + } catch { + print(error) + }*/ + } func update_pbv(idx: Int) { let m = music.music[idx] @@ -150,6 +198,18 @@ struct ContentView: View { .toolbar(.visible, for: .navigationBar) self.pbv.tabpbv + NowPlayingView().blur(radius: 0.2) + } + }.onChange(of: scenePhase) { phase in + switch phase { + case .active: + self.nowplaying.background = false + case .inactive: + self.nowplaying.background = true + case .background: + self.nowplaying.background = true + default: + self.nowplaying.background = true } } @@ -161,7 +221,7 @@ struct ContentView: View { let file_url = URL(filePath: dir + "/Documents/" + filename) let asset = AVAsset(url: file_url) let track = TrackInfo() - + asset.loadMetadata(for: .iTunesMetadata) { items, b in if (items == nil) { return } @@ -169,7 +229,7 @@ struct ContentView: View { if(i.identifier == .iTunesMetadataCoverArt) { Task{ let imageData = try await i.load(.dataValue) - track.art = Image(uiImage: UIImage(data: imageData!)!) + track.art = UIImage(data: imageData!)! /*if (track.art != nil) { track.art!.resizable().scaledToFill().frame(width: geo.width, height: geo.height) }*/ @@ -188,7 +248,7 @@ struct ContentView: View { print(self.player.error!) } else { - self.player.play() + //self.play() } } init() { @@ -200,12 +260,6 @@ struct ContentView: View { self.dir = NSHomeDirectory() let dir = self.dir - do { - let session = AVAudioSession.sharedInstance() - try session.setCategory(AVAudioSession.Category.playback, mode: .default, policy: .longFormAudio, options: .duckOthers) - } catch { - print(error) - } self.player = AVQueuePlayer() self.nowplaying = TrackInfo() @@ -213,11 +267,9 @@ struct ContentView: View { self.pbv.pbv.parent = self self.pbv.tabpbv.parent = self - self.player.audiovisualBackgroundPlaybackPolicy = .continuesIfPossible - - self.player.addObserver(self.nowplaying, forKeyPath: "currentItem",options: [.old, .new], context: &self) - + self.player.addObserver(self.nowplaying, forKeyPath: "currentItem", options: [.old, .new], context: &self) + self.player.addObserver(self.nowplaying, forKeyPath: "timeControlStatus", options: [.old, .new], context: nil) session.dataTask(with: request, completionHandler: { [self] (data, response, error) -> Void in if (error != nil) { return } @@ -251,24 +303,7 @@ struct ContentView: View { check_file(filepath) check_file("\(dir)/Documents/\(_file)") if (download) { - var tries = 16 - /* - func try_save_response (data: Data?, response: URLResponse?, error: Error?) -> Void { - if (error == nil) { - let fp = fopen(filepath, "wb") - data!.withUnsafeBytes({ ptr in - fwrite(ptr, 1, data!.count, fp) - }) - fclose(fp) - add_music(filename: file) - } - else { - if (tries > 0) { - tries -= 1 - session.dataTask(with: URLRequest(url: URL(string: base + "/" + _file)!, timeoutInterval: TimeInterval(100000 * (5 - tries))), completionHandler: try_save_response).resume() - } - } - }*/ + var tries = 32 var req = URLRequest(url: URL(string: base + "/" + _file)!, timeoutInterval: 65536) func try_download (u: URL?, r: URLResponse?, e: Error?) -> Void { // use download to avoid memory overflow @@ -289,9 +324,6 @@ struct ContentView: View { } } session.downloadTask(with: req, completionHandler: try_download).resume() - - - //session.dataTask(with: URLRequest(url: URL(string: base + "/" + _file)!, timeoutInterval: TimeInterval(65535)), completionHandler: try_save_response).resume() } } }catch{} @@ -303,6 +335,17 @@ struct ContentView: View { self.pbv.pbv.update(music: self.nowplaying) self.pbv.tabpbv.update(music: self.nowplaying) + + let player = self.player + cc.playCommand.addTarget { _ in + player.play() + return .success + } + cc.stopCommand.addTarget { _ in + player.play() + return .success + } + } } diff --git a/MusicPlayer Watch App/PlaybackView.swift b/MusicPlayer Watch App/PlaybackView.swift index ccd3b62..cd83869 100644 --- a/MusicPlayer Watch App/PlaybackView.swift +++ b/MusicPlayer Watch App/PlaybackView.swift @@ -7,6 +7,10 @@ import SwiftUI import UIKit +import WatchKit + +let window_width = WKInterfaceDevice.current().screenBounds.width +let window_height = WKInterfaceDevice.current().screenBounds.height class AppearTimer : ObservableObject { @Published var appear = false @@ -47,19 +51,24 @@ struct PlaybackView: View { if trackInfo.m != nil { GeometryReader { geo in ZStack { - if(trackInfo.art == nil) { - - Image(systemName: "music.note") - .resizable() - .scaledToFit() - .foregroundColor(.white) - .frame(width: geo.size.width*0.84, height: geo.size.height*0.84) - .padding(.leading, geo.size.width*0.08) - .padding(.top, geo.size.height*0.08) - } - else { - trackInfo.art!.resizable().scaledToFill() - } + ZStack{ + if(trackInfo.art == nil) { + + Image(systemName: "music.note") + .resizable() + .scaledToFit() + .foregroundColor(.white) + .frame(width: window_width*0.7, height: window_height*0.7) + //.padding(.leading, window_width*0.05) + //.padding(.bottom, window_height * 0.15) + + //.padding(.top, window_height*0.08) + } + else { + Image(uiImage: trackInfo.art!).resizable().scaledToFill() + } + }.frame(width : window_width, height: window_height * 0.8) + .background(appearTimer.appear ? Color.gray : Color.clear).opacity(appearTimer.appear ? 0.3 : 1).blur(radius: appearTimer.appear ? 5 : 0) if (appearTimer.appear) { VStack { @@ -70,8 +79,9 @@ struct PlaybackView: View { self.playing = false } else { parent!.player.audiovisualBackgroundPlaybackPolicy = .continuesIfPossible - parent!.player.play() + parent!.play() self.playing = true + appearTimer.appear() } } label: { ( @@ -81,9 +91,9 @@ struct PlaybackView: View { ) .resizable() .scaledToFit() - .frame(width: geo.size.width/5.5) + .frame(width: window_width/5.5) }.background(Color(red: 0,green: 0,blue: 0,opacity: 0.2)) - .frame(width: geo.size.width/2.5) + .frame(width: window_width/2.5) .cornerRadius(90, antialiased: true) .foregroundColor(.white) .opacity(1) @@ -94,25 +104,27 @@ struct PlaybackView: View { curr!.seek(to: .zero) parent!.player.play() self.playing = true + appearTimer.appear() } label : { Image(systemName: "chevron.forward") .resizable() .scaledToFit() - .frame(width: geo.size.width/7, height: geo.size.height/7) + .frame(width: window_width/7, height: window_height/7) }.background(Color.clear) .clipShape(Circle()) .foregroundColor(.white) - .frame(width: geo.size.width/4, height: geo.size.height/4) + .frame(width: window_width/4, height: window_height/4) .padding(0) .opacity(1) .buttonStyle(.plain) } - } + }.zIndex(5) } }.onTapGesture { appearTimer.appear() } }.navigationBarBackButtonHidden(false) + .navigationTitle(trackInfo.s) .toolbar(.visible, for: .navigationBar) .onAppear() { appearTimer.appear(time: 3, _appear: true) diff --git a/MusicPlayer-Watch-App-Info.plist b/MusicPlayer-Watch-App-Info.plist index f753731..e095854 100644 --- a/MusicPlayer-Watch-App-Info.plist +++ b/MusicPlayer-Watch-App-Info.plist @@ -2,9 +2,21 @@ + NSSupportsAutomaticTermination + + NSSupportsSuddenTermination + + UIApplicationExitsOnSuspend + UIBackgroundModes audio + fetch + processing + WKBackgroundModes + + WKSupportsAlwaysOnDisplay + diff --git a/MusicPlayer.xcodeproj/project.pbxproj b/MusicPlayer.xcodeproj/project.pbxproj index ea02f31..2c29c80 100644 --- a/MusicPlayer.xcodeproj/project.pbxproj +++ b/MusicPlayer.xcodeproj/project.pbxproj @@ -20,7 +20,7 @@ isa = PBXContainerItemProxy; containerPortal = A95C117329C531C000737618 /* Project object */; proxyType = 1; - remoteGlobalTrackInfoing = A95C117E29C531C100737618; + remoteGlobalIDString = A95C117E29C531C100737618; remoteInfo = "MusicPlayer Watch App"; }; /* End PBXContainerItemProxy section */ @@ -338,6 +338,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "MusicPlayer-Watch-App-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = MusicPlayer; + INFOPLIST_KEY_UIStatusBarHidden = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKWatchOnly = YES; LD_RUNPATH_SEARCH_PATHS = ( @@ -352,7 +353,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 9.1; + WATCHOS_DEPLOYMENT_TARGET = 9.3; }; name = Debug; }; @@ -369,6 +370,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "MusicPlayer-Watch-App-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = MusicPlayer; + INFOPLIST_KEY_UIStatusBarHidden = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKWatchOnly = YES; LD_RUNPATH_SEARCH_PATHS = ( @@ -384,7 +386,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; VALIDATE_PRODUCT = YES; - WATCHOS_DEPLOYMENT_TARGET = 9.1; + WATCHOS_DEPLOYMENT_TARGET = 9.3; }; name = Release; }; diff --git a/MusicPlayer.xcodeproj/xcuserdata/bill.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/MusicPlayer.xcodeproj/xcuserdata/bill.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index c920a37..7e92f65 100644 --- a/MusicPlayer.xcodeproj/xcuserdata/bill.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/MusicPlayer.xcodeproj/xcuserdata/bill.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -14,10 +14,57 @@ filePath = "MusicPlayer Watch App/ContentView.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "284" - endingLineNumber = "284" + startingLineNumber = "319" + endingLineNumber = "319" landmarkName = "try_download(u:r:e:)" landmarkType = "9"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +