Skip to content

Commit

Permalink
Merge pull request #4 from samzong/feat/macos-media-control
Browse files Browse the repository at this point in the history
✨ feat: Support macos media control play/pause music (close #3)
  • Loading branch information
samzong authored Sep 20, 2024
2 parents 088ac39 + ad251d8 commit 99ca78b
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 10 deletions.
29 changes: 28 additions & 1 deletion AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
//

import Cocoa
import MediaPlayer
import SwiftUI
import AVFoundation

class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem?
Expand All @@ -26,6 +28,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

setupMenu()
setupRemoteCommandCenter()
}

func setupMenu() {
Expand All @@ -51,7 +54,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
menu.addItem(NSMenuItem.separator())

// Reconfigure folder
menu.addItem(NSMenuItem(title: "Sources", action: #selector(reconfigureFolder), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Sources", action: #selector(reconfigureFolder), keyEquivalent: "s"))

menu.addItem(NSMenuItem.separator())

Expand All @@ -63,6 +66,30 @@ class AppDelegate: NSObject, NSApplicationDelegate {
NotificationCenter.default.addObserver(self, selector: #selector(updateMenuItems), name: NSNotification.Name("PlaybackStateChanged"), object: nil)
}

func setupRemoteCommandCenter() {
let commandCenter = MPRemoteCommandCenter.shared()

commandCenter.playCommand.addTarget { [weak self] _ in
self?.playerManager.play()
return .success
}

commandCenter.pauseCommand.addTarget { [weak self] _ in
self?.playerManager.pause()
return .success
}

commandCenter.nextTrackCommand.addTarget { [weak self] _ in
self?.playerManager.playNext()
return .success
}

commandCenter.previousTrackCommand.addTarget { [weak self] _ in
self?.playerManager.playPrevious()
return .success
}
}

@objc func toggleMenu() {
updateMenuItems()
statusItem?.menu = menu
Expand Down
13 changes: 13 additions & 0 deletions MacMusicPlayer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,22 @@
991C445C2C9AB7FA00F2BB76 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
999217BB2C9D42B2008F5CC3 /* Exceptions for "MacMusicPlayer" folder in "MacMusicPlayer" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 991C44362C9A9C6E00F2BB76 /* MacMusicPlayer */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
991C44392C9A9C6E00F2BB76 /* MacMusicPlayer */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
999217BB2C9D42B2008F5CC3 /* Exceptions for "MacMusicPlayer" folder in "MacMusicPlayer" target */,
);
path = MacMusicPlayer;
sourceTree = "<group>";
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,22 @@
uuid = "C5D46313-4BC6-4EEB-9A79-353627BEAB5B"
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "1DF877E0-F1F2-48BD-B6DC-9AEC47DEB856"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "AppDelegate.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "41"
endingLineNumber = "41"
landmarkName = "setupMenu()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>
4 changes: 4 additions & 0 deletions MacMusicPlayer/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@
<array>
<dict/>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</dict>
</plist>
61 changes: 52 additions & 9 deletions Managers/PlayerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation
import AVFoundation
import SwiftUI
import MediaPlayer

class PlayerManager: NSObject, ObservableObject, AVAudioPlayerDelegate {
@Published var playlist: [Track] = []
Expand All @@ -23,20 +24,23 @@ class PlayerManager: NSObject, ObservableObject, AVAudioPlayerDelegate {
}
private var player: AVAudioPlayer?
private var currentIndex = 0

var audioEngine: AVAudioEngine!
var playerNode: AVAudioPlayerNode!

override init() {
super.init()
setupAudioEngine()
loadSavedMusicFolder()
}

private func loadSavedMusicFolder() {
if let savedPath = UserDefaults.standard.string(forKey: "MusicFolderPath") {
loadTracksFromMusicFolder(URL(fileURLWithPath: savedPath))
} else {
requestMusicFolderAccess()
}
}

func requestMusicFolderAccess() {
DispatchQueue.main.async {
let openPanel = NSOpenPanel()
Expand All @@ -53,7 +57,38 @@ class PlayerManager: NSObject, ObservableObject, AVAudioPlayerDelegate {
}
}
}


func setupAudioEngine() {
audioEngine = AVAudioEngine()
playerNode = AVAudioPlayerNode()
audioEngine.attach(playerNode)
let mainMixer = audioEngine.mainMixerNode
audioEngine.connect(playerNode, to: mainMixer, format: nil)

do {
try audioEngine.start()
} catch {
print("Failed to start audio engine: \(error)")
}
}

func updateNowPlayingInfo() {
var nowPlayingInfo = [String: Any]()

if let currentTrack = currentTrack {
nowPlayingInfo[MPMediaItemPropertyTitle] = currentTrack.title
nowPlayingInfo[MPMediaItemPropertyArtist] = currentTrack.artist

if let player = player {
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player.duration
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.isPlaying ? 1.0 : 0.0
}
}

MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}

private func loadTracksFromMusicFolder(_ folderURL: URL) {
do {
let fileURLs = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
Expand All @@ -77,7 +112,7 @@ class PlayerManager: NSObject, ObservableObject, AVAudioPlayerDelegate {
print("Error accessing Music folder: \(error)")
}
}

func play() {
guard let track = currentTrack else {
print("No current track to play")
Expand All @@ -93,28 +128,36 @@ class PlayerManager: NSObject, ObservableObject, AVAudioPlayerDelegate {
} catch {
print("Could not create player for \(track.title): \(error)")
}

updateNowPlayingInfo()
}

func pause() {
player?.pause()
isPlaying = false
print("Paused playback")

updateNowPlayingInfo()
}

func playNext() {
guard !playlist.isEmpty else { return }
currentIndex = (currentIndex + 1) % playlist.count
currentTrack = playlist[currentIndex]
play()

updateNowPlayingInfo()
}

func playPrevious() {
guard !playlist.isEmpty else { return }
currentIndex = (currentIndex - 1 + playlist.count) % playlist.count
currentTrack = playlist[currentIndex]
play()

updateNowPlayingInfo()
}

// AVAudioPlayerDelegate 方法
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if flag {
Expand Down

0 comments on commit 99ca78b

Please sign in to comment.