Skip to content

Commit

Permalink
Allow playing custom streams
Browse files Browse the repository at this point in the history
This lets users implement custom streams that can be played. For
example, I have a websocket interface that I fetch data from. I
can wrap that stream into a CoreAudioStreamSource and add that to
the player.
  • Loading branch information
jacksonh committed Sep 12, 2024
1 parent b89d3d9 commit e7e08d2
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 16 deletions.
4 changes: 2 additions & 2 deletions AudioStreaming/Streaming/Audio Source/AudioStreamSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import AudioToolbox
import Foundation

protocol AudioStreamSourceDelegate: AnyObject {
public protocol AudioStreamSourceDelegate: AnyObject {
/// Indicates that there's data available
func dataAvailable(source: CoreAudioStreamSource, data: Data)
/// Indicates an error occurred
Expand All @@ -17,7 +17,7 @@ protocol AudioStreamSourceDelegate: AnyObject {
func metadataReceived(data: [String: String])
}

protocol CoreAudioStreamSource: AnyObject {
public protocol CoreAudioStreamSource: AnyObject {
/// An `Int` that represents the position of the audio
var position: Int { get }
/// The length of the audio in bytes
Expand Down
18 changes: 9 additions & 9 deletions AudioStreaming/Streaming/Audio Source/RemoteAudioSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ enum RemoteAudioSourceError: Error {
}

public class RemoteAudioSource: AudioStreamSource {
weak var delegate: AudioStreamSourceDelegate?
public weak var delegate: AudioStreamSourceDelegate?

var position: Int {
public var position: Int {
return seekOffset + relativePosition
}

var length: Int {
public var length: Int {
guard let parsedHeader = parsedHeaderOutput else { return 0 }
return parsedHeader.fileLength
}
Expand All @@ -40,7 +40,7 @@ public class RemoteAudioSource: AudioStreamSource {
private var shouldTryParsingIcycastHeaders: Bool = false
private let icycastHeadersProcessor: IcycastHeadersProcessor

var audioFileHint: AudioFileTypeID {
public var audioFileHint: AudioFileTypeID {
guard let output = parsedHeaderOutput, output.typeId != 0 else {
return audioFileType(fileExtension: url.pathExtension)
}
Expand All @@ -49,7 +49,7 @@ public class RemoteAudioSource: AudioStreamSource {

private let mp4Restructure: RemoteMp4Restructure

let underlyingQueue: DispatchQueue
public let underlyingQueue: DispatchQueue
let streamOperationQueue: OperationQueue
let netStatusService: NetStatusProvider
var waitingForNetwork = false
Expand Down Expand Up @@ -114,7 +114,7 @@ public class RemoteAudioSource: AudioStreamSource {
httpHeaders: [:])
}

func close() {
public func close() {
retrierTimeout.cancel()
streamOperationQueue.isSuspended = false
streamOperationQueue.cancelAllOperations()
Expand All @@ -125,7 +125,7 @@ public class RemoteAudioSource: AudioStreamSource {
streamRequest = nil
}

func seek(at offset: Int) {
public func seek(at offset: Int) {
close()

relativePosition = 0
Expand All @@ -144,11 +144,11 @@ public class RemoteAudioSource: AudioStreamSource {
performOpen(seek: offset)
}

func suspend() {
public func suspend() {
streamOperationQueue.isSuspended = true
}

func resume() {
public func resume() {
streamOperationQueue.isSuspended = false
}

Expand Down
24 changes: 19 additions & 5 deletions AudioStreaming/Streaming/AudioPlayer/AudioPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ open class AudioPlayer {
private let frameFilterProcessor: FrameFilterProcessor

private let serializationQueue: DispatchQueue
private let sourceQueue: DispatchQueue
public let sourceQueue: DispatchQueue

private let entryProvider: AudioEntryProviding

Expand Down Expand Up @@ -190,6 +190,20 @@ open class AudioPlayer {
/// - parameter headers: A `Dictionary` specifying any additional headers to be pass to the network request.
public func play(url: URL, headers: [String: String]) {
let audioEntry = entryProvider.provideAudioEntry(url: url, headers: headers)
play(audioEntry: audioEntry)
}

/// Starts the audio playback for the supplied stream
///
/// - parameter source: A `CoreAudioStreamSource` that will providing streaming data
/// - parameter entryId: A `String` that provides a unique id for this item
/// - parameter format: An `AVAudioFormat` the format of this audio source
public func play(source: CoreAudioStreamSource, entryId: String, format: AVAudioFormat) {
let audioEntry = AudioEntry(source: source, entryId: AudioEntryId(id: entryId), outputAudioFormat: format)
play(audioEntry: audioEntry)
}

private func play(audioEntry: AudioEntry) {
audioEntry.delegate = self

checkRenderWaitingAndNotifyIfNeeded()
Expand Down Expand Up @@ -805,7 +819,7 @@ open class AudioPlayer {
}

extension AudioPlayer: AudioStreamSourceDelegate {
func dataAvailable(source: CoreAudioStreamSource, data: Data) {
public func dataAvailable(source: CoreAudioStreamSource, data: Data) {
guard let readingEntry = playerContext.audioReadingEntry, readingEntry.has(same: source) else {
return
}
Expand Down Expand Up @@ -835,12 +849,12 @@ extension AudioPlayer: AudioStreamSourceDelegate {
}
}

func errorOccurred(source: CoreAudioStreamSource, error: Error) {
public func errorOccurred(source: CoreAudioStreamSource, error: Error) {
guard let entry = playerContext.audioReadingEntry, entry.has(same: source) else { return }
raiseUnexpected(error: .networkError(.failure(error)))
}

func endOfFileOccurred(source: CoreAudioStreamSource) {
public func endOfFileOccurred(source: CoreAudioStreamSource) {
let hasSameSource = playerContext.audioReadingEntry?.has(same: source) ?? false
guard playerContext.audioReadingEntry == nil || hasSameSource else {
source.delegate = nil
Expand Down Expand Up @@ -877,7 +891,7 @@ extension AudioPlayer: AudioStreamSourceDelegate {
}
}

func metadataReceived(data: [String: String]) {
public func metadataReceived(data: [String: String]) {
asyncOnMain { [weak self] in
guard let self = self else { return }
self.delegate?.audioPlayerDidReadMetadata(player: self, metadata: data)
Expand Down

0 comments on commit e7e08d2

Please sign in to comment.