Skip to content

Commit

Permalink
Separate out moveToSong in player queue
Browse files Browse the repository at this point in the history
Signed-off-by: Claudio Cambra <[email protected]>
  • Loading branch information
claucambra committed Apr 14, 2024
1 parent 48d1621 commit 8ac107a
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 79 deletions.
28 changes: 16 additions & 12 deletions Harmony/Player/PlayerController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,18 +299,22 @@ class PlayerController: NSObject, ObservableObject {
currentSong = queueCurrentSong.song
}

func playSongFromQueue(instanceId: ObjectIdentifier) {
Task {
await queue.moveToSong(instanceId: instanceId)
guard let queueCurrentSong = queue.currentSong else {
Logger.player.error("No current song in queue")
return
}
Task { @MainActor in
currentSong = queueCurrentSong.song
play()
}
}
func playSongFromPlayNext(instanceId: ObjectIdentifier) {
queue.moveToPlayNextSong(instanceId: instanceId)
applyCurrentSongFromQueue()
play()
}

func playSongFromFutureSongs(instanceId: ObjectIdentifier) {
queue.moveToFutureSong(instanceId: instanceId)
applyCurrentSongFromQueue()
play()
}

func playSongFromPastSongs(instanceId: ObjectIdentifier) {
queue.moveToPastSong(instanceId: instanceId)
applyCurrentSongFromQueue()
play()
}

@discardableResult func togglePlayPause() -> MPRemoteCommandHandlerStatus {
Expand Down
119 changes: 60 additions & 59 deletions Harmony/Player/PlayerQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,68 +159,69 @@ class PlayerQueue: ObservableObject {
return nextSong.song
}

// TODO: This is unnecessary, in the view we know which section each song belongs to, create
// TODO: more efficient implementation with independent methods
func moveToSong(instanceId: ObjectIdentifier) async {
guard let currentSong = currentSong, currentSong.id != instanceId else { return }

@Sendable func findAndSetSong(
_ songs: Deque<PlayerQueueItem>,
findHandler: @escaping (Int) -> ()
) {
guard let songIndex = songs.firstIndex(where: { song in
song.id == instanceId
}) else { return }
let song = songs[songIndex]

Task { @MainActor in
findHandler(songIndex)
self.currentSong = song
print(song.title)
}
private func findAndSetSong(
_ songs: Deque<PlayerQueueItem>,
findSongId: ObjectIdentifier,
findHandler: @escaping (Int) -> ()
) {
guard let songIndex = songs.firstIndex(where: { song in
song.id == findSongId
}) else { return }
let song = songs[songIndex]
findHandler(songIndex)
self.currentSong = song
}

func moveToPlayNextSong(instanceId: ObjectIdentifier) {
guard let index = playNextSongs.firstIndex(where: { $0.id == instanceId }) else { return }
if let currentSong = currentSong {
self.pastSongs.append(currentSong)
}
currentSong = playNextSongs[index]
guard index > 0 else {
playNextSongs.remove(at: index)
return
}
for _ in 0..<index {
guard let song = playNextSongs.popFirst() else { continue }
pastSongs.append(song)
}
playNextSongs.removeFirst()
}

// Explore all queues
await withDiscardingTaskGroup { group in
group.addTask {
await findAndSetSong(self.playNextSongs) { index in
Task { @MainActor in
self.pastSongs.append(currentSong)
self.playNextSongs.remove(at: index)
}
}
}
group.addTask {
await findAndSetSong(self.futureSongs) { index in
Task { @MainActor in
self.pastSongs.append(currentSong)
let preSongIndex = index - 1
if preSongIndex >= 0 {
for i in 0...preSongIndex {
let song = self.futureSongs[i]
self.pastSongs.append(song)
}
}
self.futureSongs.removeFirst(index + 1)
}
}
}
group.addTask {
await findAndSetSong(self.pastSongs) { index in
Task { @MainActor in
let postSongIndex = index + 1
if postSongIndex < self.pastSongs.count {
for i in (postSongIndex..<self.pastSongs.count).reversed() {
let song = self.pastSongs[i]
self.futureSongs.prepend(song)
}
}
self.pastSongs.removeLast(self.pastSongs.count - index)
self.futureSongs.append(currentSong)
}
}
}
func moveToFutureSong(instanceId: ObjectIdentifier) {
guard let index = futureSongs.firstIndex(where: { $0.id == instanceId }) else { return }
if let currentSong = currentSong {
self.pastSongs.append(currentSong)
}
playNextSongs.forEach { pastSongs.append($0) }
currentSong = futureSongs[index]
guard index > 0 else {
futureSongs.remove(at: index)
return
}
for _ in 0..<index {
guard let song = futureSongs.popFirst() else { continue }
pastSongs.append(song)
}
futureSongs.removeFirst()
}

func moveToPastSong(instanceId: ObjectIdentifier) {
guard let index = pastSongs.firstIndex(where: { $0.id == instanceId }) else { return }
if let currentSong = currentSong {
futureSongs.prepend(currentSong)
}
currentSong = pastSongs[index]
guard index + 1 <= pastSongs.count - 1 else {
pastSongs.remove(at: index)
return
}
for _ in (index + 1...pastSongs.count - 1).reversed() {
guard let song = pastSongs.popLast() else { continue }
futureSongs.prepend(song)
}
pastSongs.removeLast()
}

func returnToStart() {
Expand Down
32 changes: 24 additions & 8 deletions Harmony/Player/PlayerQueueView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ struct PlayerQueueView: View {
ForEach(queue.pastSongs) { song in
PlayerQueueListItemView(song: song, isCurrentSong: false)
.listRowBackground(rowBackground)
.contextMenu { _ in
// TODO: Add a context menu
} primaryAction: {
$0.forEach {
playerController.playSongFromPastSongs(instanceId: $0)
proxy.scrollTo(currentSongSectionId, anchor: .top)
}
}
}
.onDelete(perform: { indexSet in queue.removePastSongs(indexSet) })
} header: {
Expand Down Expand Up @@ -54,6 +62,14 @@ struct PlayerQueueView: View {
ForEach(queue.playNextSongs) { song in
PlayerQueueListItemView(song: song, isCurrentSong: false)
.listRowBackground(rowBackground)
.contextMenu { _ in
// TODO: Add a context menu
} primaryAction: {
$0.forEach {
playerController.playSongFromPlayNext(instanceId: $0)
proxy.scrollTo(currentSongSectionId, anchor: .top)
}
}
}
.onDelete(perform: { indexSet in queue.removePlayNextSongs(indexSet) })
}
Expand All @@ -66,6 +82,14 @@ struct PlayerQueueView: View {
.onAppear {
queue.loadNextPageIfNeeded(song: song)
}
.contextMenu { _ in
// TODO: Add a context menu
} primaryAction: {
$0.forEach {
playerController.playSongFromFutureSongs(instanceId: $0)
proxy.scrollTo(currentSongSectionId, anchor: .top)
}
}
}
.onDelete(perform: { indexSet in queue.removeFutureSongs(indexSet) })
} header: {
Expand All @@ -83,14 +107,6 @@ struct PlayerQueueView: View {
}
}
}
.contextMenu(forSelectionType: PlayerQueueItem.ID.self) { ids in
// TODO
} primaryAction: { ids in
for id in ids {
PlayerController.shared.playSongFromQueue(instanceId: id)
proxy.scrollTo(currentSongSectionId, anchor: .top)
}
}
.onChange(of: queue.currentSong) {
#if os(macOS) // TODO: iPadOS?
proxy.scrollTo(currentSongSectionId, anchor: .top)
Expand Down

0 comments on commit 8ac107a

Please sign in to comment.