From 1fc938f14f1585b0f95d8d707fe19e105e7ae4b5 Mon Sep 17 00:00:00 2001 From: dimitris-c Date: Sat, 22 Nov 2025 18:08:46 +0200 Subject: [PATCH 1/2] Adds loop mode --- .../xcschemes/AudioPlayer.xcscheme | 7 + .../AudioPlayer/AudioPlayerControlsView.swift | 99 ++++++++++++ .../Dependencies/AudioPlayerService.swift | 43 +++++- AudioStreaming/Core/Helpers/Logger.swift | 31 ++-- .../OggVorbis/VorbisFileDecoder.swift | 1 + .../Audio Source/Mp4/Mp4Restructure.swift | 1 + .../Mp4/RemoteMp4Restructure.swift | 1 + .../Streaming/AudioPlayer/AudioPlayer.swift | 142 +++++++++++++++++- .../AudioPlayerConfiguration.swift | 4 +- .../AudioPlayer/AudioPlayerLoopMode.swift | 25 +++ .../Processors/AudioFileStreamProcessor.swift | 1 + .../AudioPlayerRenderProcessor.swift | 1 + .../Processors/OggVorbisStreamProcessor.swift | 1 + README.md | 21 +++ 14 files changed, 358 insertions(+), 20 deletions(-) create mode 100644 AudioStreaming/Streaming/AudioPlayer/AudioPlayerLoopMode.swift diff --git a/AudioPlayer/AudioPlayer.xcodeproj/xcshareddata/xcschemes/AudioPlayer.xcscheme b/AudioPlayer/AudioPlayer.xcodeproj/xcshareddata/xcschemes/AudioPlayer.xcscheme index a60a8f9..7517c81 100644 --- a/AudioPlayer/AudioPlayer.xcodeproj/xcshareddata/xcschemes/AudioPlayer.xcscheme +++ b/AudioPlayer/AudioPlayer.xcodeproj/xcshareddata/xcschemes/AudioPlayer.xcscheme @@ -51,6 +51,13 @@ ReferencedContainer = "container:AudioPlayer.xcodeproj"> + + + + 0 { + return "Loop: Single (\(times)x)" + } + return "Loop: Single (∞)" + case .all(let times): + if let times = times, times > 0 { + return "Loop: All (\(times)x)" + } + return "Loop: All (∞)" + } + } + + var isLoopActive: Bool { + if case .off = loopMode { + return false + } + return true + } + + var currentLoopTimes: Int? { + switch loopMode { + case .off: + return nil + case .single(let times): + return times + case .all(let times): + return times + } + } init(audioPlayerService: AudioPlayerService) { self.audioPlayerService = audioPlayerService @@ -220,6 +306,19 @@ extension AudioPlayerControls { isMuted.toggle() audioPlayerService.toggleMute() } + + func cycleLoopMode() { + audioPlayerService.cycleLoopMode() + loopMode = audioPlayerService.loopMode + // Update loopTimes to match the current mode's times + loopTimes = Double(currentLoopTimes ?? 0) + } + + func updateLoopTimes(_ times: Double) { + let timesValue = times == 0 ? nil : Int(times) + audioPlayerService.setLoopTimes(timesValue) + loopMode = audioPlayerService.loopMode + } func playPause() { if audioPlayerService.state == .playing { diff --git a/AudioPlayer/AudioPlayer/Dependencies/AudioPlayerService.swift b/AudioPlayer/AudioPlayer/Dependencies/AudioPlayerService.swift index 3c56949..a0f5980 100644 --- a/AudioPlayer/AudioPlayer/Dependencies/AudioPlayerService.swift +++ b/AudioPlayer/AudioPlayer/Dependencies/AudioPlayerService.swift @@ -39,6 +39,11 @@ final class AudioPlayerService { var state: AudioPlayerState { player.state } + + var loopMode: AudioPlayerLoopMode { + get { player.loopMode } + set { player.loopMode = newValue } + } var statusChangedNotifier = Notifier() var metadataReceivedNotifier = Notifier<[String: String]>() @@ -57,7 +62,17 @@ final class AudioPlayerService { func play(url: URL) { activateAudioSession() - player.play(url: url) +// player.play(url: url) + + player.queue(url: AudioContent.piano.streamUrl) + player.queue(url: AudioContent.loopBeatFlac.streamUrl) + +// player.queue(url: URL(string: "https://github.com/dimitris-c/sample-audio/raw/refs/heads/main/01%20-%20Before%20the%20Light%20Knew%20Us%20(Mixed).ogg")!) +// player.queue(url: URL(string: "https://github.com/dimitris-c/sample-audio/raw/refs/heads/main/01%20-%20Before%20the%20Light%20Knew%20Us%20(Mixed).m4a")!) + +// player.queue(url: URL(string: "https://github.com/dimitris-c/sample-audio/raw/refs/heads/main/02%20-%20Classrooms%20(Mixed).m4a")!) + +// player.queue(url: URL(string: "https://github.com/dimitris-c/sample-audio/raw/refs/heads/main/02%20-%20Classrooms%20(Mixed).ogg")!) } func play(source: CoreAudioStreamSource, entryId: String, format: AVAudioFormat) { @@ -114,6 +129,32 @@ final class AudioPlayerService { func seek(at time: Double) { player.seek(to: time) } + + func setLoopMode(_ mode: AudioPlayerLoopMode) { + player.loopMode = mode + } + + func cycleLoopMode() { + switch player.loopMode { + case .off: + player.loopMode = .single(times: nil) + case .single: + player.loopMode = .all(times: nil) + case .all: + player.loopMode = .off + } + } + + func setLoopTimes(_ times: Int?) { + switch player.loopMode { + case .off: + break + case .single: + player.loopMode = .single(times: times) + case .all: + player.loopMode = .all(times: times) + } + } private func recreatePlayer() { player = audioPlayerProvider() diff --git a/AudioStreaming/Core/Helpers/Logger.swift b/AudioStreaming/Core/Helpers/Logger.swift index b871872..c469e15 100644 --- a/AudioStreaming/Core/Helpers/Logger.swift +++ b/AudioStreaming/Core/Helpers/Logger.swift @@ -4,14 +4,14 @@ // import Foundation -import os +import OSLog private let loggingSubsystem = "audio.streaming.log" -enum Logger { - private static let audioRendering = OSLog(subsystem: loggingSubsystem, category: "audio.rendering") - private static let networking = OSLog(subsystem: loggingSubsystem, category: "audio.networking") - private static let generic = OSLog(subsystem: loggingSubsystem, category: "audio.streaming.generic") +extension Logger { + public static let audioRendering = Logger(subsystem: loggingSubsystem, category: "audio.rendering") + public static let networking = Logger(subsystem: loggingSubsystem, category: "audio.networking") + public static let generic = Logger(subsystem: loggingSubsystem, category: "audio.streaming.generic") /// Defines is the the logger displays any logs static var isEnabled = true @@ -21,7 +21,7 @@ enum Logger { case networking case generic - func toOSLog() -> OSLog { + func toOSLog() -> Logger { switch self { case .audioRendering: return Logger.audioRendering case .networking: return Logger.networking @@ -30,24 +30,31 @@ enum Logger { } } - static func error(_ message: StaticString, category: Category, args: CVarArg...) { + static func error(_ message: String, category: Category, args: CVarArg...) { process(message, category: category, type: .error, args: args) } - static func error(_ message: StaticString, category: Category) { + static func error(_ message: String, category: Category) { error(message, category: category, args: []) } - static func debug(_ message: StaticString, category: Category, args: CVarArg...) { + static func debug(_ message: String, category: Category, args: CVarArg...) { process(message, category: category, type: .debug, args: args) } - static func debug(_ message: StaticString, category: Category) { + static func debug(_ message: String, category: Category) { debug(message, category: category, args: []) } - private static func process(_ message: StaticString, category: Category, type: OSLogType, args: CVarArg...) { + private static func process(_ message: String, category: Category, type: OSLogType, args: CVarArg...) { guard isEnabled else { return } - os_log(message, log: category.toOSLog(), type: type, args) + switch type { + case .debug: + category.toOSLog().debug("\(message)") + case .error: + category.toOSLog().error("\(message)") + default: + category.toOSLog().info("\(message)") + } } } diff --git a/AudioStreaming/OggVorbis/VorbisFileDecoder.swift b/AudioStreaming/OggVorbis/VorbisFileDecoder.swift index 90ff3d8..14988f4 100644 --- a/AudioStreaming/OggVorbis/VorbisFileDecoder.swift +++ b/AudioStreaming/OggVorbis/VorbisFileDecoder.swift @@ -1,6 +1,7 @@ import Foundation import AudioCodecs import AVFoundation +import OSLog /// A simple decoder for Ogg Vorbis files using libvorbisfile final class VorbisFileDecoder { diff --git a/AudioStreaming/Streaming/Audio Source/Mp4/Mp4Restructure.swift b/AudioStreaming/Streaming/Audio Source/Mp4/Mp4Restructure.swift index 9eed1e1..c41395c 100644 --- a/AudioStreaming/Streaming/Audio Source/Mp4/Mp4Restructure.swift +++ b/AudioStreaming/Streaming/Audio Source/Mp4/Mp4Restructure.swift @@ -4,6 +4,7 @@ // import Foundation +import OSLog struct MP4Atom: Equatable, CustomDebugStringConvertible { let type: Int diff --git a/AudioStreaming/Streaming/Audio Source/Mp4/RemoteMp4Restructure.swift b/AudioStreaming/Streaming/Audio Source/Mp4/RemoteMp4Restructure.swift index b51ed0e..25f1de8 100644 --- a/AudioStreaming/Streaming/Audio Source/Mp4/RemoteMp4Restructure.swift +++ b/AudioStreaming/Streaming/Audio Source/Mp4/RemoteMp4Restructure.swift @@ -4,6 +4,7 @@ // import Foundation +import OSLog final class RemoteMp4Restructure { struct RestructuredData { diff --git a/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift b/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift index 97bfd3e..73556a9 100644 --- a/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift +++ b/AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift @@ -5,6 +5,7 @@ import AVFoundation import CoreAudio +import OSLog open class AudioPlayer { public weak var delegate: AudioPlayerDelegate? @@ -124,6 +125,13 @@ open class AudioPlayer { /// The current configuration of the player. public let configuration: AudioPlayerConfiguration + /// The loop mode for the audio player + /// Defaults to `.off`. + /// - Use `.single(times:)` to loop the current track + /// - Use `.all(times:)` to loop the entire queue + /// - Pass `nil` for times to loop infinitely, or a positive integer to loop that many times + public var loopMode: AudioPlayerLoopMode = .off + /// A Boolean value that indicates whether the audio engine is running. /// `true` if the engine is running, otherwise, `false` public var isEngineRunning: Bool { audioEngine.isRunning } @@ -167,6 +175,13 @@ open class AudioPlayer { private let entryProvider: AudioEntryProviding var entriesQueue: PlayerQueueEntries + + /// Stores the original queue entries for loop all functionality + private var originalQueueForLoop: [LoopEntryInfo] = [] + /// Tracks played entries for loop functionality + private var playedEntries: [LoopEntryInfo] = [] + /// Tracks the current loop iteration (how many times we've looped so far) + private var currentLoopIteration: Int = 0 public init(configuration: AudioPlayerConfiguration = .default) { self.configuration = configuration.normalizeValues() @@ -259,6 +274,9 @@ open class AudioPlayer { checkRenderWaitingAndNotifyIfNeeded() serializationQueue.sync { clearQueue() + // Reset loop tracking when starting new playback + playedEntries.removeAll() + currentLoopIteration = 0 entriesQueue.enqueue(item: audioEntry, type: .upcoming) playerContext.setInternalState(to: .pendingNext) do { @@ -432,7 +450,7 @@ open class AudioPlayer { do { try startEngine() } catch { - Logger.debug("resuming audio engine failed: %@", category: .generic, args: error.localizedDescription) + Logger.debug("resuming audio engine failed: \(error.localizedDescription)", category: .generic) } if let playingEntry = playerContext.audioReadingEntry { if playingEntry.seekRequest.requested { @@ -535,7 +553,7 @@ open class AudioPlayer { audioEngine.prepare() try audioEngine.start() } catch { - Logger.error("⚠️ error setting up audio engine: %@", category: .generic, args: error.localizedDescription) + Logger.error("⚠️ error setting up audio engine: \(error.localizedDescription)", category: .generic) } } @@ -578,7 +596,14 @@ open class AudioPlayer { guard let self = self else { return } self.serializationQueue.sync { let nextEntry = self.entriesQueue.dequeue(type: .buffering) - self.processFinishPlaying(entry: entry, with: nextEntry) + + if nextEntry == nil { + if let entry = entry { + self.handleLoopingIfNeeded(for: entry) + } + } else { + self.processFinishPlaying(entry: entry, with: nextEntry) + } } self.sourceQueue.async { self.processSource() @@ -763,7 +788,7 @@ open class AudioPlayer { private func setCurrentReading(entry: AudioEntry?, startPlaying: Bool, shouldClearQueue: Bool) { guard let entry = entry else { return } - Logger.debug("Setting current reading entry to: %@", category: .generic, args: entry.debugDescription) + Logger.debug("Setting current reading entry to: \(entry.debugDescription)", category: .generic) if startPlaying { rendererContext.fillSilenceAudioBuffer() } @@ -794,7 +819,9 @@ open class AudioPlayer { private func processFinishPlaying(entry: AudioEntry?, with nextEntry: AudioEntry?) { let playingEntry = playerContext.entriesLock.withLock { playerContext.audioPlayingEntry } - guard entry == playingEntry else { return } + guard entry == playingEntry else { + return + } let isPlayingSameItemProbablySeek = playerContext.audioPlayingEntry === nextEntry @@ -807,6 +834,20 @@ open class AudioPlayer { nextEntry.seekRequest.requested = false } } + + // Track entries as they transition (NOT for .all mode - handled separately to preserve order) + if let entry = entry, !isPlayingSameItemProbablySeek, let url = URL(string: entry.id.id) { + if case .all = loopMode { + // Skip tracking for .all mode - it's handled in handleLoopingIfNeeded to preserve order + } else { + let alreadyTracked = playedEntries.contains(where: { $0.url == url }) + if !alreadyTracked { + let entryInfo = LoopEntryInfo(url: url, headers: [:]) + playedEntries.append(entryInfo) + } + } + } + playerContext.entriesLock.lock() playerContext.audioPlayingEntry = nextEntry let playingQueueEntryId = playerContext.audioPlayingEntry?.id ?? AudioEntryId(id: "") @@ -830,6 +871,15 @@ open class AudioPlayer { } if !isPlayingSameItemProbablySeek { playerContext.setInternalState(to: .waitingForData) + + // For .all mode, track the STARTING entry to preserve queue order + if case .all = loopMode, let url = URL(string: nextEntry.id.id) { + let alreadyTracked = playedEntries.contains(where: { $0.url == url }) + if !alreadyTracked { + let entryInfo = LoopEntryInfo(url: url, headers: [:]) + playedEntries.append(entryInfo) + } + } asyncOnMain { [weak self] in guard let self = self else { return } @@ -837,6 +887,19 @@ open class AudioPlayer { } } } else { + // Track the LAST entry (NOT for .all mode - handled in handleLoopingIfNeeded to preserve order) + if let entry = entry, !isPlayingSameItemProbablySeek, let url = URL(string: entry.id.id) { + if case .all = loopMode { + // Skip tracking for .all mode - it's handled in handleLoopingIfNeeded to preserve order + } else { + let alreadyTracked = playedEntries.contains(where: { $0.url == url }) + if !alreadyTracked { + let entryInfo = LoopEntryInfo(url: url, headers: [:]) + playedEntries.append(entryInfo) + } + } + } + playerContext.entriesLock.lock() playerContext.audioPlayingEntry = nil playerContext.entriesLock.unlock() @@ -871,6 +934,8 @@ open class AudioPlayer { private func clearQueue() { let pendingItems = entriesQueue.pendingEntriesId() entriesQueue.removeAll() + playedEntries.removeAll() + currentLoopIteration = 0 if !pendingItems.isEmpty { asyncOnMain { [weak self] in guard let self = self else { return } @@ -892,7 +957,72 @@ open class AudioPlayer { guard let self = self else { return } self.delegate?.audioPlayerUnexpectedError(player: self, error: error) } - Logger.error("Error: %@", category: .generic, args: error.localizedDescription) + Logger.error("Error: \(error.localizedDescription)", category: .generic) + } + + /// Handles looping based on the current loop mode + /// - Parameter entry: The audio entry that just finished playing + private func handleLoopingIfNeeded(for entry: AudioEntry) { + // For .all mode, track the last entry before checking if we should loop + // This ensures the complete queue is captured for re-queueing + if case .all = loopMode, let url = URL(string: entry.id.id) { + let alreadyTracked = playedEntries.contains(where: { $0.url == url }) + if !alreadyTracked { + let entryInfo = LoopEntryInfo(url: url, headers: [:]) + playedEntries.append(entryInfo) + } + } + + switch loopMode { + case .off: + processFinishPlaying(entry: entry, with: nil) + + case .single(let times): + // Check if we've reached the loop count limit + if let maxLoops = times, maxLoops > 0, currentLoopIteration >= maxLoops { + processFinishPlaying(entry: entry, with: nil) + return + } + + guard let url = URL(string: entry.id.id) else { + processFinishPlaying(entry: entry, with: nil) + return + } + + // Increment loop iteration + currentLoopIteration += 1 + + // Re-create the entry and enqueue for playback + let newEntry = entryProvider.provideAudioEntry(url: url, headers: [:]) + newEntry.delegate = self + entriesQueue.enqueue(item: newEntry, type: .upcoming) + + case .all(let times): + // Check if we're at the end of all entries (nothing in queue) + if entriesQueue.count(for: .upcoming) == 0 && entriesQueue.count(for: .buffering) == 0 { + // Check if we've reached the loop count limit + if let maxLoops = times, maxLoops > 0, currentLoopIteration >= maxLoops { + processFinishPlaying(entry: entry, with: nil) + return + } + + // Increment loop iteration + currentLoopIteration += 1 + + // Make a copy of playedEntries to re-queue + let entriesToRequeue = playedEntries + + // Clear played entries BEFORE re-queueing to prevent duplicate tracking + playedEntries.removeAll() + + // Re-create all played entries and enqueue as upcoming + for entryInfo in entriesToRequeue { + let newEntry = entryProvider.provideAudioEntry(url: entryInfo.url, headers: entryInfo.headers) + newEntry.delegate = self + entriesQueue.enqueue(item: newEntry, type: .upcoming) + } + } + } } } diff --git a/AudioStreaming/Streaming/AudioPlayer/AudioPlayerConfiguration.swift b/AudioStreaming/Streaming/AudioPlayer/AudioPlayerConfiguration.swift index 90069fc..949a823 100644 --- a/AudioStreaming/Streaming/AudioPlayer/AudioPlayerConfiguration.swift +++ b/AudioStreaming/Streaming/AudioPlayer/AudioPlayerConfiguration.swift @@ -4,6 +4,7 @@ // import Foundation +import OSLog public struct AudioPlayerConfiguration: Equatable { /// All pending items will be flushed when seeking a track if this is set to `true` @@ -44,7 +45,7 @@ public struct AudioPlayerConfiguration: Equatable { secondsRequiredToStartPlaying: Double = 1, gracePeriodAfterSeekInSeconds: Double = 0.5, secondsRequiredToStartPlayingAfterBufferUnderrun: Int = 1, - enableLogs: Bool = false) + enableLogs: Bool = true) { self.flushQueueOnSeek = flushQueueOnSeek self.bufferSizeInSeconds = bufferSizeInSeconds @@ -52,6 +53,7 @@ public struct AudioPlayerConfiguration: Equatable { self.gracePeriodAfterSeekInSeconds = gracePeriodAfterSeekInSeconds self.secondsRequiredToStartPlayingAfterBufferUnderrun = secondsRequiredToStartPlayingAfterBufferUnderrun self.enableLogs = enableLogs + Logger.isEnabled = enableLogs } /// Normalize values on any zero values passed diff --git a/AudioStreaming/Streaming/AudioPlayer/AudioPlayerLoopMode.swift b/AudioStreaming/Streaming/AudioPlayer/AudioPlayerLoopMode.swift new file mode 100644 index 0000000..4922069 --- /dev/null +++ b/AudioStreaming/Streaming/AudioPlayer/AudioPlayerLoopMode.swift @@ -0,0 +1,25 @@ +// +// Created by Dimitrios Chatzieleftheriou on 22/11/2025. +// Copyright © 2025 Decimal. All rights reserved. +// + +import Foundation + +/// Defines the loop/repeat mode for the audio player +public enum AudioPlayerLoopMode: Equatable { + /// No looping - plays through the queue once + case off + /// Loop the current track + /// - Parameter times: Number of times to loop (nil = infinite) + case single(times: Int?) + /// Loop the entire queue + /// - Parameter times: Number of times to loop (nil = infinite) + case all(times: Int?) +} + +/// Stores information needed to recreate an audio entry for looping +internal struct LoopEntryInfo: Equatable { + let url: URL + let headers: [String: String] +} + diff --git a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift index 82b6cff..e67fa6a 100644 --- a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift +++ b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift @@ -7,6 +7,7 @@ import AVFoundation import CoreAudio +import OSLog enum AudioConvertStatus: Int32 { case done = 100 diff --git a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioPlayerRenderProcessor.swift b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioPlayerRenderProcessor.swift index 812d9bc..08e2a27 100644 --- a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioPlayerRenderProcessor.swift +++ b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioPlayerRenderProcessor.swift @@ -6,6 +6,7 @@ // import AVFoundation +import OSLog final class AudioPlayerRenderProcessor: NSObject { /// The AVAudioEngine's `AVAudioEngineManualRenderingBlock` render block from manual rendering diff --git a/AudioStreaming/Streaming/AudioPlayer/Processors/OggVorbisStreamProcessor.swift b/AudioStreaming/Streaming/AudioPlayer/Processors/OggVorbisStreamProcessor.swift index cb01857..884f467 100644 --- a/AudioStreaming/Streaming/AudioPlayer/Processors/OggVorbisStreamProcessor.swift +++ b/AudioStreaming/Streaming/AudioPlayer/Processors/OggVorbisStreamProcessor.swift @@ -8,6 +8,7 @@ import Foundation import AVFoundation import CoreAudio +import OSLog /// A processor for Ogg Vorbis audio streams using libvorbisfile final class OggVorbisStreamProcessor { diff --git a/README.md b/README.md index 8423257..99181a8 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,27 @@ player.queue(urls: [ ]) ``` +### Loop mode +```swift +let player = AudioPlayer() +player.play(url: URL(fileURLWithPath: "your-local-path/to/audio-file.mp3")!) + +// no looping (default) +player.loopMode = .off + +// loop the current track infinitely +player.loopMode = .single(times: nil) + +// loop the current track 3 times +player.loopMode = .single(times: 3) + +// loop the entire queue infinitely +player.loopMode = .all(times: nil) + +// loop the entire queue 2 times +player.loopMode = .all(times: 2) +``` + ### Adjusting playback properties ```swift let player = AudioPlayer() From a21ea22c5743b0c01581e12d77029e6263c5eb6f Mon Sep 17 00:00:00 2001 From: dimitris-c Date: Sat, 22 Nov 2025 18:11:07 +0200 Subject: [PATCH 2/2] Removes commented code --- .../Dependencies/AudioPlayerService.swift | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/AudioPlayer/AudioPlayer/Dependencies/AudioPlayerService.swift b/AudioPlayer/AudioPlayer/Dependencies/AudioPlayerService.swift index a0f5980..f6455ff 100644 --- a/AudioPlayer/AudioPlayer/Dependencies/AudioPlayerService.swift +++ b/AudioPlayer/AudioPlayer/Dependencies/AudioPlayerService.swift @@ -62,17 +62,7 @@ final class AudioPlayerService { func play(url: URL) { activateAudioSession() -// player.play(url: url) - - player.queue(url: AudioContent.piano.streamUrl) - player.queue(url: AudioContent.loopBeatFlac.streamUrl) - -// player.queue(url: URL(string: "https://github.com/dimitris-c/sample-audio/raw/refs/heads/main/01%20-%20Before%20the%20Light%20Knew%20Us%20(Mixed).ogg")!) -// player.queue(url: URL(string: "https://github.com/dimitris-c/sample-audio/raw/refs/heads/main/01%20-%20Before%20the%20Light%20Knew%20Us%20(Mixed).m4a")!) - -// player.queue(url: URL(string: "https://github.com/dimitris-c/sample-audio/raw/refs/heads/main/02%20-%20Classrooms%20(Mixed).m4a")!) - -// player.queue(url: URL(string: "https://github.com/dimitris-c/sample-audio/raw/refs/heads/main/02%20-%20Classrooms%20(Mixed).ogg")!) + player.play(url: url) } func play(source: CoreAudioStreamSource, entryId: String, format: AVAudioFormat) {