Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f0e894f86 | |||
| decb12641d | |||
| 4e485f924a | |||
| 7e770197e6 | |||
| 6f552e60c0 |
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'AudioStreaming'
|
||||
s.version = '1.0.0'
|
||||
s.version = '1.1.0'
|
||||
s.license = 'MIT'
|
||||
s.summary = 'An AudioPlayer/Streaming library for iOS written in Swift using AVAudioEngine.'
|
||||
s.homepage = 'https://github.com/dimitris-c/AudioStreaming'
|
||||
|
||||
@@ -722,7 +722,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
@@ -781,7 +781,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -811,7 +811,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
@@ -842,7 +842,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
|
||||
@@ -73,14 +73,8 @@ final class FileAudioSource: NSObject, CoreAudioStreamSource {
|
||||
}
|
||||
|
||||
private func performOpen(seek seekOffset: Int) throws {
|
||||
|
||||
var reopened = false
|
||||
let streamStatus = inputStream?.streamStatus ?? .closed
|
||||
if streamStatus == .notOpen || streamStatus == .closed || streamStatus == .error || streamStatus == .atEnd {
|
||||
reopened = true
|
||||
close()
|
||||
try open()
|
||||
}
|
||||
close()
|
||||
try open()
|
||||
|
||||
guard let inputStream = inputStream else {
|
||||
return
|
||||
@@ -91,11 +85,6 @@ final class FileAudioSource: NSObject, CoreAudioStreamSource {
|
||||
} else {
|
||||
position = 0
|
||||
}
|
||||
if !reopened {
|
||||
if inputStream.hasBytesAvailable {
|
||||
dataAvailable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dataAvailable() {
|
||||
|
||||
@@ -108,7 +108,7 @@ open class AudioPlayer {
|
||||
private var stateBeforePaused: InternalState = .initial
|
||||
|
||||
/// The underlying `AVAudioEngine` object
|
||||
private let audioEngine = AVAudioEngine()
|
||||
private let audioEngine: AVAudioEngine
|
||||
/// An `AVAudioUnit` object that represents the audio player
|
||||
private(set) var player = AVAudioUnit()
|
||||
/// An `AVAudioUnitTimePitch` that controls the playback rate of the audio engine
|
||||
@@ -134,28 +134,36 @@ open class AudioPlayer {
|
||||
|
||||
public init(configuration: AudioPlayerConfiguration = .default) {
|
||||
self.configuration = configuration.normalizeValues()
|
||||
|
||||
let engine = AVAudioEngine()
|
||||
self.audioEngine = engine
|
||||
rendererContext = AudioRendererContext(configuration: configuration, outputAudioFormat: outputAudioFormat)
|
||||
playerContext = AudioPlayerContext()
|
||||
entriesQueue = PlayerQueueEntries()
|
||||
|
||||
serializationQueue = DispatchQueue(label: "streaming.core.queue", qos: .userInitiated)
|
||||
sourceQueue = DispatchQueue(label: "source.queue", qos: .default)
|
||||
|
||||
entryProvider = AudioEntryProvider(networkingClient: NetworkingClient(),
|
||||
underlyingQueue: sourceQueue,
|
||||
outputAudioFormat: outputAudioFormat)
|
||||
|
||||
fileStreamProcessor = AudioFileStreamProcessor(playerContext: playerContext,
|
||||
rendererContext: rendererContext,
|
||||
outputAudioFormat: outputAudioFormat.basicStreamDescription)
|
||||
|
||||
frameFilterProcessor = FrameFilterProcessor(mixerNode: audioEngine.mainMixerNode)
|
||||
|
||||
playerRenderProcessor = AudioPlayerRenderProcessor(playerContext: playerContext,
|
||||
rendererContext: rendererContext,
|
||||
outputAudioFormat: outputAudioFormat.basicStreamDescription)
|
||||
|
||||
|
||||
entryProvider = AudioEntryProvider(
|
||||
networkingClient: NetworkingClient(),
|
||||
underlyingQueue: sourceQueue,
|
||||
outputAudioFormat: outputAudioFormat
|
||||
)
|
||||
|
||||
fileStreamProcessor = AudioFileStreamProcessor(
|
||||
playerContext: playerContext,
|
||||
rendererContext: rendererContext,
|
||||
outputAudioFormat: outputAudioFormat.basicStreamDescription)
|
||||
|
||||
playerRenderProcessor = AudioPlayerRenderProcessor(
|
||||
playerContext: playerContext,
|
||||
rendererContext: rendererContext,
|
||||
outputAudioFormat: outputAudioFormat.basicStreamDescription)
|
||||
|
||||
frameFilterProcessor = FrameFilterProcessor(
|
||||
mixerNodeProvider: {
|
||||
engine.mainMixerNode
|
||||
}
|
||||
)
|
||||
configPlayerContext()
|
||||
configPlayerNode()
|
||||
setupEngine()
|
||||
@@ -506,6 +514,7 @@ open class AudioPlayer {
|
||||
/// Pauses the audio engine and stops the player's hardware
|
||||
private func pauseEngine() {
|
||||
guard isEngineRunning else { return }
|
||||
audioEngine.reset()
|
||||
audioEngine.pause()
|
||||
player.auAudioUnit.stopHardware()
|
||||
Logger.debug("engine paused ⏸", category: .generic)
|
||||
@@ -650,24 +659,6 @@ open class AudioPlayer {
|
||||
|
||||
let isPlayingSameItemProbablySeek = playerContext.audioPlayingEntry === nextEntry
|
||||
|
||||
let notifyDelegateEntryFinishedPlaying: (AudioEntry?, Bool) -> Void = { [weak self] entry, _ in
|
||||
guard let self = self else { return }
|
||||
if let entry = entry, !isPlayingSameItemProbablySeek {
|
||||
let entryId = entry.id
|
||||
let progressInFrames = entry.progressInFrames()
|
||||
let progress = Double(progressInFrames) / self.outputAudioFormat.basicStreamDescription.mSampleRate
|
||||
let duration = entry.duration()
|
||||
|
||||
asyncOnMain {
|
||||
self.delegate?.audioPlayerDidFinishPlaying(player: self,
|
||||
entryId: entryId,
|
||||
stopReason: self.stopReason,
|
||||
progress: progress,
|
||||
duration: duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let nextEntry = nextEntry {
|
||||
if !isPlayingSameItemProbablySeek {
|
||||
nextEntry.lock.withLock {
|
||||
@@ -682,7 +673,22 @@ open class AudioPlayer {
|
||||
let playingQueueEntryId = playerContext.audioPlayingEntry?.id ?? AudioEntryId(id: "")
|
||||
playerContext.entriesLock.unlock()
|
||||
|
||||
notifyDelegateEntryFinishedPlaying(entry, isPlayingSameItemProbablySeek)
|
||||
if let entry = entry, !isPlayingSameItemProbablySeek {
|
||||
let entryId = entry.id
|
||||
let progressInFrames = entry.progressInFrames()
|
||||
let progress = Double(progressInFrames) / self.outputAudioFormat.basicStreamDescription.mSampleRate
|
||||
let duration = entry.duration()
|
||||
asyncOnMain { [weak self] in
|
||||
guard let self else { return }
|
||||
self.delegate?.audioPlayerDidFinishPlaying(
|
||||
player: self,
|
||||
entryId: entryId,
|
||||
stopReason: self.stopReason,
|
||||
progress: progress,
|
||||
duration: duration
|
||||
)
|
||||
}
|
||||
}
|
||||
if !isPlayingSameItemProbablySeek {
|
||||
playerContext.setInternalState(to: .waitingForData)
|
||||
|
||||
@@ -692,10 +698,29 @@ open class AudioPlayer {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
notifyDelegateEntryFinishedPlaying(entry, isPlayingSameItemProbablySeek)
|
||||
playerContext.entriesLock.lock()
|
||||
playerContext.audioPlayingEntry = nil
|
||||
playerContext.entriesLock.unlock()
|
||||
if let entry = entry, !isPlayingSameItemProbablySeek {
|
||||
let entryId = entry.id
|
||||
let progressInFrames = entry.progressInFrames()
|
||||
let progress = Double(progressInFrames) / self.outputAudioFormat.basicStreamDescription.mSampleRate
|
||||
let duration = entry.duration()
|
||||
|
||||
sourceQueue.async { [weak self] in
|
||||
guard let self else { return }
|
||||
self.processSource()
|
||||
asyncOnMain {
|
||||
self.delegate?.audioPlayerDidFinishPlaying(
|
||||
player: self,
|
||||
entryId: entryId,
|
||||
stopReason: self.stopReason,
|
||||
progress: progress,
|
||||
duration: duration
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceQueue.async { [weak self] in
|
||||
self?.processSource()
|
||||
|
||||
@@ -42,7 +42,9 @@ internal final class AudioPlayerContext {
|
||||
when inState: ((AudioPlayer.InternalState) -> Bool)? = nil)
|
||||
{
|
||||
let newValues = playerStateAndStopReason(for: state)
|
||||
stopReason.write { $0 = newValues.stopReason }
|
||||
if let stopReason = newValues.stopReason {
|
||||
self.stopReason.write { $0 = stopReason }
|
||||
}
|
||||
guard state != internalState else { return }
|
||||
if let inState = inState, !inState(internalState) {
|
||||
return
|
||||
|
||||
@@ -30,26 +30,27 @@ extension AudioPlayer {
|
||||
/// Helper method that returns `AudioPlayerState` and `StopReason` based on the given `InternalState`
|
||||
/// - Parameter internalState: A value of `InternalState`
|
||||
/// - Returns: A tuple of `(AudioPlayerState, AudioPlayerStopReason)`
|
||||
func playerStateAndStopReason(for internalState: AudioPlayer.InternalState) -> (state: AudioPlayerState,
|
||||
stopReason: AudioPlayerStopReason)
|
||||
func playerStateAndStopReason(
|
||||
for internalState: AudioPlayer.InternalState
|
||||
) -> (state: AudioPlayerState, stopReason: AudioPlayerStopReason?)
|
||||
{
|
||||
switch internalState {
|
||||
case .initial:
|
||||
return (.ready, .none)
|
||||
return (.ready, AudioPlayerStopReason.none)
|
||||
case .running, .playing, .waitingForDataAfterSeek:
|
||||
return (.playing, .none)
|
||||
return (.playing, AudioPlayerStopReason.none)
|
||||
case .pendingNext, .rebuffering, .waitingForData:
|
||||
return (.bufferring, .none)
|
||||
return (.bufferring, AudioPlayerStopReason.none)
|
||||
case .stopped:
|
||||
return (.stopped, .userAction)
|
||||
return (.stopped, nil)
|
||||
case .paused:
|
||||
return (.paused, .none)
|
||||
return (.paused, AudioPlayerStopReason.none)
|
||||
case .disposed:
|
||||
return (.disposed, .userAction)
|
||||
case .error:
|
||||
return (.error, .error)
|
||||
return (.error, AudioPlayerStopReason.error)
|
||||
default:
|
||||
return (.ready, .none)
|
||||
return (.ready, AudioPlayerStopReason.none)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,14 +78,17 @@ final class FrameFilterProcessor: NSObject, FrameFiltering {
|
||||
}
|
||||
|
||||
private let lock = UnfairLock()
|
||||
private let mixerNode: AVAudioMixerNode
|
||||
private let mixerNodeProvider: (() -> AVAudioMixerNode)
|
||||
private lazy var mixerNode: AVAudioMixerNode = {
|
||||
return mixerNodeProvider()
|
||||
}()
|
||||
|
||||
private(set) var entries: [FilterEntry] = []
|
||||
|
||||
private var hasInstalledTap: Bool = false
|
||||
|
||||
init(mixerNode: AVAudioMixerNode) {
|
||||
self.mixerNode = mixerNode
|
||||
init(mixerNodeProvider: @escaping (() -> AVAudioMixerNode)) {
|
||||
self.mixerNodeProvider = mixerNodeProvider
|
||||
}
|
||||
|
||||
public func add(entry: FilterEntry) {
|
||||
|
||||
Reference in New Issue
Block a user