From 6c9ef18d4e6f5ce94418c28172320e32940fa8ce Mon Sep 17 00:00:00 2001 From: Dimitris C Date: Mon, 7 Dec 2020 22:12:30 +0000 Subject: [PATCH] Fixes an issue when queueing a song (#10) - Updates AudioExample with initial queuing of items --- .../Controllers/PlayerViewController.swift | 15 ++++++++++- .../Controllers/PlayerViewModel.swift | 21 ++++++++++------ .../Services/AudioPlayerService.swift | 5 ++++ .../Services/PlaylistItemsService.swift | 15 ++++++++--- .../Streaming/AudioPlayer/AudioPlayer.swift | 25 +++++++++---------- 5 files changed, 56 insertions(+), 25 deletions(-) diff --git a/AudioExample/AudioExample/Controllers/PlayerViewController.swift b/AudioExample/AudioExample/Controllers/PlayerViewController.swift index 19e4f6c..d184a7e 100644 --- a/AudioExample/AudioExample/Controllers/PlayerViewController.swift +++ b/AudioExample/AudioExample/Controllers/PlayerViewController.swift @@ -55,7 +55,7 @@ class PlayerViewController: UIViewController { tableView.translatesAutoresizingMaskIntoConstraints = false tableView.delegate = self tableView.dataSource = self - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "PlaylistCell") + tableView.register(PlaylistTableViewCell.self, forCellReuseIdentifier: "PlaylistCell") let controlsController = controlsProvider() playerControlsController = controlsController @@ -120,6 +120,7 @@ extension PlayerViewController: UITableViewDataSource { return cell } cell.textLabel?.text = item.name + cell.detailTextLabel?.text = item.queues ? "Queue item" : nil update(status: item.status, of: cell) return cell } @@ -147,3 +148,15 @@ extension PlayerViewController: UITableViewDelegate { viewModel.playItem(at: indexPath) } } + + +final class PlaylistTableViewCell: UITableViewCell { + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/AudioExample/AudioExample/Controllers/PlayerViewModel.swift b/AudioExample/AudioExample/Controllers/PlayerViewModel.swift index 8fededd..8f7815f 100644 --- a/AudioExample/AudioExample/Controllers/PlayerViewModel.swift +++ b/AudioExample/AudioExample/Controllers/PlayerViewModel.swift @@ -47,7 +47,7 @@ final class PlayerViewModel { print("malformed url error") return } - playlistItemsService.add(item: PlaylistItem(url: url, name: urlString, status: .stopped)) + playlistItemsService.add(item: PlaylistItem(url: url, name: urlString, status: .stopped, queues: false)) reloadContent?(.all) } @@ -57,13 +57,20 @@ final class PlayerViewModel { func playItem(at indexPath: IndexPath) { guard let item = item(at: indexPath) else { return } - if let index = currentPlayingItemIndex { - playlistItemsService.setStatus(for: index, status: .stopped) - reloadContent?(.item(IndexPath(row: index, section: 0))) - currentPlayingItemIndex = nil + if item.queues { + playerService.queue(url: item.url) + if currentPlayingItemIndex == nil { + currentPlayingItemIndex = indexPath.row + } + } else { + if let index = currentPlayingItemIndex { + playlistItemsService.setStatus(for: index, status: .stopped) + reloadContent?(.item(IndexPath(row: index, section: 0))) + currentPlayingItemIndex = nil + } + playerService.play(url: item.url) + currentPlayingItemIndex = indexPath.row } - playerService.play(url: item.url) - currentPlayingItemIndex = indexPath.row } } diff --git a/AudioExample/AudioExample/Services/AudioPlayerService.swift b/AudioExample/AudioExample/Services/AudioPlayerService.swift index fcf84ef..1f50caa 100644 --- a/AudioExample/AudioExample/Services/AudioPlayerService.swift +++ b/AudioExample/AudioExample/Services/AudioPlayerService.swift @@ -56,6 +56,11 @@ final class AudioPlayerService { player.play(url: url) } + func queue(url: URL) { + activateAudioSession() + player.queue(url: url) + } + func stop() { player.stop() deactivateAudioSession() diff --git a/AudioExample/AudioExample/Services/PlaylistItemsService.swift b/AudioExample/AudioExample/Services/PlaylistItemsService.swift index e162e8a..e2f07a9 100644 --- a/AudioExample/AudioExample/Services/PlaylistItemsService.swift +++ b/AudioExample/AudioExample/Services/PlaylistItemsService.swift @@ -19,17 +19,20 @@ struct PlaylistItem: Equatable { let url: URL let name: String let status: Status + let queues: Bool - init(content: AudioContent) { + init(content: AudioContent, queues: Bool) { name = content.title url = content.streamUrl status = .stopped + self.queues = queues } - init(url: URL, name: String, status: Status) { + init(url: URL, name: String, status: Status, queues: Bool) { self.url = url self.name = name self.status = status + self.queues = queues } } @@ -70,10 +73,14 @@ final class PlaylistItemsService { guard let item = item(at: index) else { return } - items[index] = PlaylistItem(url: item.url, name: item.name, status: status) + items[index] = PlaylistItem(url: item.url, name: item.name, status: status, queues: item.queues) } } func provideInitialPlaylistItems() -> [PlaylistItem] { - AudioContent.allCases.map(PlaylistItem.init(content:)) + let allCases = AudioContent.allCases + let casesForQueueing: [AudioContent] = [.piano, .local, .khruangbin] + let allItems = allCases.map { PlaylistItem.init(content: $0 , queues: false) } + let casesForQueuingItems = casesForQueueing.map { PlaylistItem.init(content: $0 , queues: true) } + return allItems + casesForQueuingItems } diff --git a/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift b/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift index f4136e7..5a29f55 100644 --- a/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift +++ b/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift @@ -116,7 +116,7 @@ public final class AudioPlayer { let playerRenderProcessor: AudioPlayerRenderProcessor private let audioReadSource: DispatchTimerSource - private let underlyingQueue = DispatchQueue(label: "streaming.core.queue", qos: .userInitiated, attributes: .concurrent) + private let serializationQueue: DispatchQueue private let sourceQueue: DispatchQueue private let entryProvider: AudioEntryProviding @@ -130,7 +130,8 @@ public final class AudioPlayer { playerContext = AudioPlayerContext() entriesQueue = PlayerQueueEntries() - sourceQueue = DispatchQueue(label: "source.queue", qos: .userInitiated, target: underlyingQueue) + serializationQueue = DispatchQueue(label: "streaming.core.queue", qos: .userInitiated) + sourceQueue = DispatchQueue(label: "source.queue", qos: .userInitiated, target: serializationQueue) audioReadSource = DispatchTimerSource(interval: .milliseconds(200), queue: sourceQueue) entryProvider = AudioEntryProvider(networkingClient: NetworkingClient(), @@ -206,6 +207,7 @@ public final class AudioPlayer { let audioEntry = entryProvider.provideAudioEntry(url: url, headers: headers) audioEntry.delegate = self entriesQueue.enqueue(item: audioEntry, type: .upcoming) + checkRenderWaitingAndNotifyIfNeeded() sourceQueue.async { [weak self] in self?.processSource() } @@ -215,8 +217,8 @@ public final class AudioPlayer { public func stop() { guard playerContext.internalState != .stopped else { return } - stopEngine(reason: .userAction) stopReadProccessFromSource() + stopEngine(reason: .userAction) sourceQueue.async { [weak self] in guard let self = self else { return } self.playerContext.audioReadingEntry?.delegate = nil @@ -382,9 +384,11 @@ public final class AudioPlayer { playerRenderProcessor.audioFinishedPlaying = { [weak self] entry in guard let self = self else { return } - self.sourceQueue.async { + self.serializationQueue.sync { let nextEntry = self.entriesQueue.dequeue(type: .buffering) self.processFinishPlaying(entry: entry, with: nextEntry) + } + self.sourceQueue.async { self.processSource() } } @@ -462,10 +466,6 @@ public final class AudioPlayer { /// /// - parameter reason: A value of `AudioPlayerStopReason` indicating the reason the engine stopped. private func stopEngine(reason: AudioPlayerStopReason) { - guard isEngineRunning && player.auAudioUnit.isRunning else { - Logger.debug("already already stopped 🛑", category: .generic) - return - } audioEngine.stop() player.auAudioUnit.stopHardware() rendererContext.resetBuffers() @@ -498,11 +498,8 @@ public final class AudioPlayer { if resetBuffers { rendererContext.resetBuffers() } - if !isEngineRunning && !player.auAudioUnit.isRunning { - Logger.debug("trying to start the player when audio engine and player are already running", category: .generic) - return - } do { + try startEngineIfNeeded() try player.auAudioUnit.allocateRenderResources() try player.auAudioUnit.startHardware() } catch { @@ -669,7 +666,9 @@ public final class AudioPlayer { playerContext.audioPlayingEntry = nil playerContext.entriesLock.unlock() } - processSource() + sourceQueue.async { [weak self] in + self?.processSource() + } checkRenderWaitingAndNotifyIfNeeded() }