Skip to content

Commit

Permalink
Add player, playbackstate primitives, reorg files, continue apple mus…
Browse files Browse the repository at this point in the history
…ic progress
  • Loading branch information
jonathangarelick committed May 3, 2024
1 parent c2fa6d4 commit 12a312f
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 51 deletions.
30 changes: 29 additions & 1 deletion SoundSeer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
/* Begin PBXBuildFile section */
9B1461312BD08573009C931B /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B1461302BD08573009C931B /* ScriptingBridge.framework */; };
9B1486722BD475DA00D67669 /* WindowAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1486712BD475DA00D67669 /* WindowAccessor.swift */; };
9B1B0F912BE59C3800E26B26 /* PlayerStateNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1B0F902BE59C3800E26B26 /* PlayerStateNotification.swift */; };
9B1B0F942BE5A55700E26B26 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1B0F932BE5A55700E26B26 /* Player.swift */; };
9B1B0F962BE5A57800E26B26 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1B0F952BE5A57800E26B26 /* PlaybackState.swift */; };
9B55030B2BD5F49C0005BEBD /* StringExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B55030A2BD5F49C0005BEBD /* StringExtensionTests.swift */; };
9B7A30B72BE3F1D600AD7C1E /* MusicApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B7A30B62BE3F1D600AD7C1E /* MusicApplication.swift */; };
9B7A30B92BE3FC4300AD7C1E /* SBProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B7A30B82BE3FC4300AD7C1E /* SBProtocol.swift */; };
Expand Down Expand Up @@ -39,6 +42,9 @@
9B1461302BD08573009C931B /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = System/Library/Frameworks/ScriptingBridge.framework; sourceTree = SDKROOT; };
9B1461322BD086E5009C931B /* SoundSeer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SoundSeer-Bridging-Header.h"; sourceTree = "<group>"; };
9B1486712BD475DA00D67669 /* WindowAccessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowAccessor.swift; sourceTree = "<group>"; };
9B1B0F902BE59C3800E26B26 /* PlayerStateNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStateNotification.swift; sourceTree = "<group>"; };
9B1B0F932BE5A55700E26B26 /* Player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = "<group>"; };
9B1B0F952BE5A57800E26B26 /* PlaybackState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackState.swift; sourceTree = "<group>"; };
9B258F2D2BDF193100DC5EE2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
9B5503012BD5F22E0005BEBD /* SoundSeerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SoundSeerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
9B55030A2BD5F49C0005BEBD /* StringExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -86,6 +92,24 @@
name = Frameworks;
sourceTree = "<group>";
};
9B1B0F922BE5A54A00E26B26 /* Primitives */ = {
isa = PBXGroup;
children = (
9B1B0F952BE5A57800E26B26 /* PlaybackState.swift */,
9B1B0F932BE5A55700E26B26 /* Player.swift */,
);
path = Primitives;
sourceTree = "<group>";
};
9B1B0F972BE5A5B800E26B26 /* Models */ = {
isa = PBXGroup;
children = (
9BD4DCE12BC9E9D4001572F7 /* PlayerModel.swift */,
9B1B0F902BE59C3800E26B26 /* PlayerStateNotification.swift */,
);
path = Models;
sourceTree = "<group>";
};
9B5503022BD5F22E0005BEBD /* SoundSeerTests */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -159,9 +183,10 @@
9BAEAEA22BDAE8AD00C85936 /* Bridge */,
9BAEAEA32BDAE8D000C85936 /* Extensions */,
9B258F2D2BDF193100DC5EE2 /* Info.plist */,
9B1B0F972BE5A5B800E26B26 /* Models */,
9B1B0F922BE5A54A00E26B26 /* Primitives */,
9BE2FA1B2BC97CCC00A7124B /* SoundSeerApp.swift */,
9BC6B6882BCA2D49005421AE /* SpotifyAPI.swift */,
9BD4DCE12BC9E9D4001572F7 /* PlayerModel.swift */,
9BD4DCE32BC9EC8B001572F7 /* SpotifyViewModel.swift */,
9BAEAEA02BDAE6F200C85936 /* Utils.swift */,
9B1486712BD475DA00D67669 /* WindowAccessor.swift */,
Expand Down Expand Up @@ -284,13 +309,16 @@
buildActionMask = 2147483647;
files = (
9B7A30B72BE3F1D600AD7C1E /* MusicApplication.swift in Sources */,
9B1B0F912BE59C3800E26B26 /* PlayerStateNotification.swift in Sources */,
9BE2FA1C2BC97CCC00A7124B /* SoundSeerApp.swift in Sources */,
9BD4DCE22BC9E9D4001572F7 /* PlayerModel.swift in Sources */,
9BAEAEA12BDAE6F200C85936 /* Utils.swift in Sources */,
9B1B0F942BE5A55700E26B26 /* Player.swift in Sources */,
9B1486722BD475DA00D67669 /* WindowAccessor.swift in Sources */,
9B7A30B92BE3FC4300AD7C1E /* SBProtocol.swift in Sources */,
9BEB2DDB2BD1C02E0038E223 /* StringExtension.swift in Sources */,
9BD4DCE42BC9EC8B001572F7 /* SpotifyViewModel.swift in Sources */,
9B1B0F962BE5A57800E26B26 /* PlaybackState.swift in Sources */,
9BAEAE9F2BDAE2C400C85936 /* LoggerExtension.swift in Sources */,
9BAC6E112BD0A082004B80E1 /* SpotifyApplication.swift in Sources */,
9BC6B6892BCA2D49005421AE /* SpotifyAPI.swift in Sources */,
Expand Down
80 changes: 30 additions & 50 deletions SoundSeer/PlayerModel.swift → SoundSeer/Models/PlayerModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,6 @@ import MusicKit
import OSLog
import ScriptingBridge

enum PlaybackState {
case paused
case playing
case stopped

init<T: RawRepresentable>(_ descriptor: T?) where T.RawValue == UInt32 {
switch descriptor?.rawValue {
case 0x6b505370:
self = .paused
case 0x6b505350, 0x6b505352, 0x6b505346: // playing, rewinding, fast-forwarding
self = .playing
default:
self = .stopped
}
}
}

class PlayerModel {
@Published var playerState: PlaybackState = .stopped

Expand All @@ -44,28 +27,18 @@ class PlayerModel {

self.spotifyApp = spotifyApp

notificationCenter.addObserver(forName: musicNotificationName, object: nil, queue: nil) { notification in
// Handle the playback state change notification
print("Received playback state change notification")

// Extract the playback state information from the notification
if let userInfo = notification.userInfo,
let playerState = userInfo["Player State"] as? String {
// Handle the player state
print("Player State: \(playerState)")

// Perform any necessary actions based on the player state
// For example, update your app's UI or trigger specific behaviors
}
}

// Need to trigger a "fake" event when SoundSeer is first opened
Logger.model.debug("Performing initial update")
update()
// Logger.model.debug("Performing initial update")
// update()

Logger.model.debug("Subscribing to Spotify playback change events")
notificationCenter.addObserver(forName: notificationName, object: nil, queue: nil) { [weak self] _ in
self?.update()
notificationCenter.addObserver(forName: notificationName, object: nil, queue: nil) { [weak self] in
self?.update(PlayerStateNotification(.spotify, $0))
}

Logger.model.debug("Subscribing to Apple Music playback change events")
notificationCenter.addObserver(forName: musicNotificationName, object: nil, queue: nil) { [weak self] in
self?.update(PlayerStateNotification(.music, $0))
}
}

Expand All @@ -89,17 +62,28 @@ class PlayerModel {
currentAlbum = ""
}

private func update() {
// An event may be fired when Spotify is closed. This prevents it from reopening
guard Utils.isAppRunning("com.spotify.client") else {
Logger.playback.debug("Cancelled update. Spotify is not running")
resetData()
private func update(_ notification: PlayerStateNotification?) {
guard let notification = notification else {
Logger.model.error("Received bad event. Discarding")
return
}

playerState = PlaybackState(spotifyApp.playerState)
playerState = notification.playbackState
Logger.playback.debug("Player state is now \(String(describing: self.playerState))")

currentSong = notification.songName ?? ""
currentSongId = "abc" //spotifyApp.currentTrack?.id?().components(separatedBy: ":").last ?? ""
currentArtist = notification.artistName ?? ""
currentAlbum = notification.albumName ?? ""

Logger.model.debug("Retrieved current song: \(self.currentSong, privacy: .public)")
Logger.model.debug("Retrieved current song ID: \(self.currentSongId, privacy: .public)")
Logger.model.debug("Retrieved current artist: \(self.currentArtist, privacy: .public)")
Logger.model.debug("Retrieved current album: \(self.currentAlbum, privacy: .public)")

Logger.model.debug("Update completed successfully")

/*
// Sometimes the AEKeyword will be 0 when the app is killed
// Something about the Objective-C bridge allows the enum to still be created
if Utils.playerStateIsStoppedOrUnknown(playerState) {
Expand All @@ -108,9 +92,7 @@ class PlayerModel {
return
}
currentSong = spotifyApp.currentTrack?.name ?? ""
currentSongId = spotifyApp.currentTrack?.id?().components(separatedBy: ":").last ?? ""
currentArtist = spotifyApp.currentTrack?.artist ?? ""
// I have no idea why this sometimes happens. It's only for artist
if currentArtist.isEmpty {
Expand All @@ -123,11 +105,9 @@ class PlayerModel {
currentAlbum = spotifyApp.currentTrack?.album ?? ""
Logger.model.debug("Retrieved current song: \(self.currentSong, privacy: .public)")
Logger.model.debug("Retrieved current song ID: \(self.currentSongId, privacy: .public)")
Logger.model.debug("Retrieved current artist: \(self.currentArtist, privacy: .public)")
Logger.model.debug("Retrieved current album: \(self.currentAlbum, privacy: .public)")
Logger.model.debug("Update completed successfully")
*/
}
}
21 changes: 21 additions & 0 deletions SoundSeer/Models/PlayerStateNotification.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

struct PlayerStateNotification {
let player: Player
let playbackState: PlaybackState
let songName: String?
let artistName: String?
let albumName: String?

init?(_ player: Player, _ notification: Notification) {
guard let playbackStateString = notification.userInfo?["Player State"] as? String else {
return nil
}

self.player = player
self.playbackState = PlaybackState(state: playbackStateString)
self.songName = notification.userInfo?["Name"] as? String
self.artistName = notification.userInfo?["Artist"] as? String
self.albumName = notification.userInfo?["Album"] as? String
}
}
27 changes: 27 additions & 0 deletions SoundSeer/Primitives/PlaybackState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
enum PlaybackState {
case paused
case playing
case stopped

init<T: RawRepresentable>(_ descriptor: T?) where T.RawValue == UInt32 {
switch descriptor?.rawValue {
case 0x6b505370:
self = .paused
case 0x6b505350, 0x6b505352, 0x6b505346: // playing, rewinding, fast-forwarding
self = .playing
default:
self = .stopped
}
}

init(state: String) {
switch state {
case "Paused":
self = .paused
case "Playing":
self = .playing
default:
self = .stopped
}
}
}
8 changes: 8 additions & 0 deletions SoundSeer/Primitives/Player.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
enum Player: String {
case music = "com.apple.music"
case spotify = "com.spotify.client"

var bundleID: String {
return self.rawValue
}
}

0 comments on commit 12a312f

Please sign in to comment.