Compare commits

..

17 Commits

Author SHA1 Message Date
Dimitris C e1801e5065 Update README.md 2024-05-16 13:00:27 +03:00
dimitris-c d041f17e76 Merge branch 'update-example' of https://github.com/dimitris-c/AudioStreaming into update-example 2024-05-16 12:56:36 +03:00
dimitris-c 5db7c2921b fix tests 2024-05-16 12:56:25 +03:00
Dimitris C dec732a597 Update swift.yml 2024-05-16 12:52:42 +03:00
Dimitris C 51af91f4d4 Update swift.yml 2024-05-16 12:47:46 +03:00
dimitris-c 02ba7012da Merge branch 'main' into update-example 2024-05-16 12:41:11 +03:00
dimitris-c 473afdaa50 Updates example, fixes noise output on mute 2024-05-16 12:34:42 +03:00
dimitris-c 8a13074c21 progress 2024-05-15 13:23:46 +03:00
dimitris-c 1b7558a4f1 refactor 2024-05-15 13:23:46 +03:00
dimitris-c 7d91e318d1 progress 2024-05-15 13:23:46 +03:00
dimitris-c 4e2cd36677 adds animations to eq view 2024-05-15 13:23:46 +03:00
dimitris-c f2adacb687 update comment on example project 2024-05-15 13:23:46 +03:00
dimitris-c 553132f486 progress 2024-05-15 13:23:45 +03:00
dimitris-c 1f5f7c8be3 progress 2024-05-15 13:23:45 +03:00
dimitris-c 70a4dfd698 progress 2024-05-15 13:23:45 +03:00
dimitris-c 81bf90f5c4 example project progress 2024-05-15 13:23:45 +03:00
dimitris-c 8424611b34 Adds initial new example project 2024-05-15 13:23:44 +03:00
21 changed files with 155 additions and 402 deletions
@@ -24,7 +24,6 @@
9816A8BB2BC87BC200AD1299 /* AudioPlayerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9816A8BA2BC87BC200AD1299 /* AudioPlayerService.swift */; }; 9816A8BB2BC87BC200AD1299 /* AudioPlayerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9816A8BA2BC87BC200AD1299 /* AudioPlayerService.swift */; };
984DE9552BDAE59C004B427A /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 984DE9542BDAE59C004B427A /* Notifier.swift */; }; 984DE9552BDAE59C004B427A /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 984DE9542BDAE59C004B427A /* Notifier.swift */; };
984DE9572BDAFC7E004B427A /* AudioPlayerControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 984DE9562BDAFC7E004B427A /* AudioPlayerControlsView.swift */; }; 984DE9572BDAFC7E004B427A /* AudioPlayerControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 984DE9562BDAFC7E004B427A /* AudioPlayerControlsView.swift */; };
989E08E72BF7A4E300599F17 /* PrefersTabNavigationEnvironmentKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 989E08E62BF7A4E300599F17 /* PrefersTabNavigationEnvironmentKey.swift */; };
98BFB41A2BC97AF800E812C0 /* DisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFB4192BC97AF800E812C0 /* DisplayLink.swift */; }; 98BFB41A2BC97AF800E812C0 /* DisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFB4192BC97AF800E812C0 /* DisplayLink.swift */; };
98BFB41D2BCD7BB800E812C0 /* EqualizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFB41C2BCD7BB800E812C0 /* EqualizerView.swift */; }; 98BFB41D2BCD7BB800E812C0 /* EqualizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFB41C2BCD7BB800E812C0 /* EqualizerView.swift */; };
98BFB41F2BCD814000E812C0 /* EqualizerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFB41E2BCD814000E812C0 /* EqualizerService.swift */; }; 98BFB41F2BCD814000E812C0 /* EqualizerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFB41E2BCD814000E812C0 /* EqualizerService.swift */; };
@@ -64,7 +63,6 @@
9816A8BA2BC87BC200AD1299 /* AudioPlayerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerService.swift; sourceTree = "<group>"; }; 9816A8BA2BC87BC200AD1299 /* AudioPlayerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerService.swift; sourceTree = "<group>"; };
984DE9542BDAE59C004B427A /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; }; 984DE9542BDAE59C004B427A /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
984DE9562BDAFC7E004B427A /* AudioPlayerControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerControlsView.swift; sourceTree = "<group>"; }; 984DE9562BDAFC7E004B427A /* AudioPlayerControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerControlsView.swift; sourceTree = "<group>"; };
989E08E62BF7A4E300599F17 /* PrefersTabNavigationEnvironmentKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefersTabNavigationEnvironmentKey.swift; sourceTree = "<group>"; };
98BFB4192BC97AF800E812C0 /* DisplayLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayLink.swift; sourceTree = "<group>"; }; 98BFB4192BC97AF800E812C0 /* DisplayLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayLink.swift; sourceTree = "<group>"; };
98BFB41B2BCAAD8A00E812C0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; 98BFB41B2BCAAD8A00E812C0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
98BFB41C2BCD7BB800E812C0 /* EqualizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EqualizerView.swift; sourceTree = "<group>"; }; 98BFB41C2BCD7BB800E812C0 /* EqualizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EqualizerView.swift; sourceTree = "<group>"; };
@@ -178,7 +176,6 @@
children = ( children = (
98BFB4192BC97AF800E812C0 /* DisplayLink.swift */, 98BFB4192BC97AF800E812C0 /* DisplayLink.swift */,
984DE9542BDAE59C004B427A /* Notifier.swift */, 984DE9542BDAE59C004B427A /* Notifier.swift */,
989E08E62BF7A4E300599F17 /* PrefersTabNavigationEnvironmentKey.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -292,7 +289,6 @@
9816A8BB2BC87BC200AD1299 /* AudioPlayerService.swift in Sources */, 9816A8BB2BC87BC200AD1299 /* AudioPlayerService.swift in Sources */,
984DE9572BDAFC7E004B427A /* AudioPlayerControlsView.swift in Sources */, 984DE9572BDAFC7E004B427A /* AudioPlayerControlsView.swift in Sources */,
9806E8182BC5D12500757370 /* App.swift in Sources */, 9806E8182BC5D12500757370 /* App.swift in Sources */,
989E08E72BF7A4E300599F17 /* PrefersTabNavigationEnvironmentKey.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -443,8 +439,6 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioPlayer; PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioPlayer;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@@ -476,8 +470,6 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioPlayer; PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioPlayer;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@@ -34,7 +34,6 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableThreadSanitizer = "YES"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
+1 -1
View File
@@ -41,7 +41,7 @@ func provideEqualizerService(playerService: AudioPlayerService) -> EqualizerServ
func provideAudioPlayerService() -> AudioPlayerService { func provideAudioPlayerService() -> AudioPlayerService {
AudioPlayerService( AudioPlayerService(
audioPlayerProvider: provideDefaultAudioPlayer audioPlayer: provideDefaultAudioPlayer()
) )
} }
@@ -25,9 +25,7 @@ struct AddNewAudioURLView: View {
TextField(value: $audioUrl, format: urlStyle, prompt: nil, label: { TextField(value: $audioUrl, format: urlStyle, prompt: nil, label: {
Text("Insert URL") Text("Insert URL")
}) })
#if os(iOS)
.keyboardType(.URL) .keyboardType(.URL)
#endif
.autocorrectionDisabled() .autocorrectionDisabled()
.textFieldStyle(RoundedBorderTextFieldStyle()) .textFieldStyle(RoundedBorderTextFieldStyle())
.onSubmit { .onSubmit {
@@ -50,7 +48,6 @@ struct AddNewAudioURLView: View {
} }
.foregroundStyle(Color.white) .foregroundStyle(Color.white)
} }
.buttonStyle(.plain)
.disabled(audioUrl == nil) .disabled(audioUrl == nil)
.opacity(audioUrl == nil ? 0.5 : 1.0) .opacity(audioUrl == nil ? 0.5 : 1.0)
.padding(.horizontal, 16) .padding(.horizontal, 16)
@@ -59,11 +56,8 @@ struct AddNewAudioURLView: View {
.clipShape(RoundedRectangle(cornerRadius: 16)) .clipShape(RoundedRectangle(cornerRadius: 16))
} }
.navigationTitle("Add Audio URL") .navigationTitle("Add Audio URL")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#endif
.toolbar { .toolbar {
#if os(iOS)
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button { Button {
dismiss() dismiss()
@@ -71,13 +65,7 @@ struct AddNewAudioURLView: View {
Image(systemName: "xmark.circle.fill") Image(systemName: "xmark.circle.fill")
.foregroundStyle(Color.gray) .foregroundStyle(Color.gray)
} }
.buttonStyle(.plain)
} }
#else
ToolbarItem(placement: .confirmationAction) {
Button("Done", action: dismiss.callAsFunction)
}
#endif
} }
} }
} }
@@ -95,8 +95,6 @@ struct AudioTrackView: View {
} else if track.status == .buffering { } else if track.status == .buffering {
ProgressView() ProgressView()
.progressViewStyle(.circular) .progressViewStyle(.circular)
.frame(alignment: .center)
.scaleEffect(0.7)
} }
} }
} }
@@ -2,7 +2,6 @@
// Created by Dimitris Chatzieleftheriou on 26/04/2024. // Created by Dimitris Chatzieleftheriou on 26/04/2024.
// //
import AVFoundation
import SwiftUI import SwiftUI
struct AudioPlayerControls: View { struct AudioPlayerControls: View {
@@ -18,41 +17,28 @@ struct AudioPlayerControls: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack { HStack {
Button(action: { model.playPause() }) { Button(action: { model.playPause() }) {
Image(systemName: model.isPlaying ? "pause" : "play") Image(systemName: model.isPlaying ? "pause.fill" : "play.fill")
.symbolVariant(.fill)
.font(.title) .font(.title)
.imageScale(.small) .imageScale(.small)
} }
.buttonStyle(.plain)
.contentTransition(.symbolEffect(.replace)) .contentTransition(.symbolEffect(.replace))
Button(action: { Button(action: {
model.stop() model.stop()
currentTrack = nil currentTrack = nil
}) { }) {
Image(systemName: "stop") Image(systemName: "stop.fill")
.symbolVariant(.fill)
.font(.title) .font(.title)
.imageScale(.small) .imageScale(.small)
} }
.buttonStyle(.plain)
.padding(.leading, 8) .padding(.leading, 8)
Spacer() Spacer()
HStack { Button(action: { model.mute() }) {
Slider(value: $model.volume) Image(systemName: model.isMuted ? "speaker.slash.fill" : "speaker.fill")
.frame(width: 80) .font(.title)
.onChange(of: model.volume) { _, newValue in .imageScale(.small)
model.update(volume: newValue)
}
Button(action: { model.mute() }) {
Image(systemName: model.iconForVolume)
.symbolVariant(model.isMuted || model.volume == 0 ? .slash.fill : .fill)
.foregroundStyle(.teal, .gray)
.font(.title.monospaced())
.imageScale(.small)
}
.buttonStyle(.plain)
.frame(width: 20, height: 20)
} }
.frame(width: 20, height: 20)
.contentTransition(.symbolEffect(.replace))
} }
.tint(.mint) .tint(.mint)
.padding(16) .padding(16)
@@ -132,8 +118,6 @@ extension AudioPlayerControls {
var isPlaying: Bool = false var isPlaying: Bool = false
var isMuted: Bool = false var isMuted: Bool = false
var volume: Float = 0.5
var playbackRate: Double = 0.0 var playbackRate: Double = 0.0
var currentTime: Double = 0 var currentTime: Double = 0
@@ -146,19 +130,6 @@ extension AudioPlayerControls {
var currentTrack: AudioTrack? var currentTrack: AudioTrack?
var iconForVolume: String {
if isMuted || volume == 0 {
return "speaker"
}
if volume < 0.4 {
return "speaker.wave.1"
} else if volume < 0.8 {
return "speaker.wave.2"
} else {
return "speaker.wave.3"
}
}
init(audioPlayerService: AudioPlayerService) { init(audioPlayerService: AudioPlayerService) {
self.audioPlayerService = audioPlayerService self.audioPlayerService = audioPlayerService
@@ -233,10 +204,6 @@ extension AudioPlayerControls {
audioPlayerService.update(rate: rate) audioPlayerService.update(rate: rate)
} }
func update(volume: Float) {
audioPlayerService.update(volume: volume)
}
func stop() { func stop() {
isPlaying = false isPlaying = false
audioPlayerService.stop() audioPlayerService.stop()
@@ -3,12 +3,7 @@
// Copyright © 2024 Decimal. All rights reserved. // Copyright © 2024 Decimal. All rights reserved.
// //
#if os(iOS)
import UIKit import UIKit
#else
import AppKit
#endif
import Foundation import Foundation
import AudioStreaming import AudioStreaming
@@ -51,37 +51,24 @@ struct AudioPlayerView: View {
} }
.ignoresSafeArea(.keyboard, edges: .bottom) .ignoresSafeArea(.keyboard, edges: .bottom)
.navigationTitle("Audio Player") .navigationTitle("Audio Player")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#endif
.toolbar { .toolbar {
#if os(iOS) ToolbarItemGroup(placement: .topBarTrailing) {
let placement: ToolbarItemPlacement = .topBarTrailing
#else
let placement: ToolbarItemPlacement = .automatic
#endif
ToolbarItemGroup(placement: placement) {
Button { Button {
eqSheetIsShown.toggle() eqSheetIsShown.toggle()
} label: { } label: {
Image(systemName: "slider.horizontal.3") Image(systemName: "slider.horizontal.3")
} }
.buttonStyle(.plain)
Button { Button {
addNewAudioIsShown.toggle() addNewAudioIsShown.toggle()
} label: { } label: {
Image(systemName: "plus") Image(systemName: "plus")
} }
.buttonStyle(.plain)
} }
} }
.sheet(isPresented: $eqSheetIsShown) { .sheet(isPresented: $eqSheetIsShown) {
EqualizerView(appModel: appModel) EqualizerView(appModel: appModel)
#if os(iOS)
.presentationDetents([.medium]) .presentationDetents([.medium])
#elseif os(macOS)
.frame(minWidth: 520, maxWidth: .infinity, minHeight: 400, maxHeight: .infinity)
#endif
} }
.sheet(isPresented: $addNewAudioIsShown) { .sheet(isPresented: $addNewAudioIsShown) {
AddNewAudioURLView( AddNewAudioURLView(
@@ -89,11 +76,7 @@ struct AudioPlayerView: View {
model.addNewAudioTrack(url: url) model.addNewAudioTrack(url: url)
} }
) )
#if os(iOS)
.presentationDetents([.height(150)]) .presentationDetents([.height(150)])
#elseif os(macOS)
.frame(minWidth: 320, maxWidth: .infinity, minHeight: 140, maxHeight: .infinity)
#endif
} }
} }
} }
@@ -85,11 +85,8 @@ struct EqualizerView: View {
} }
} }
.navigationTitle("Equalizer") .navigationTitle("Equalizer")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#endif
.toolbar { .toolbar {
#if os(iOS)
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button { Button {
dismiss() dismiss()
@@ -98,11 +95,6 @@ struct EqualizerView: View {
.foregroundStyle(Color.gray) .foregroundStyle(Color.gray)
} }
} }
#else
ToolbarItem(placement: .confirmationAction) {
Button("Done", action: dismiss.callAsFunction)
}
#endif
} }
} }
} }
@@ -44,11 +44,8 @@ final class AudioPlayerService {
var metadataReceivedNotifier = Notifier<[String: String]>() var metadataReceivedNotifier = Notifier<[String: String]>()
var playingStartedStopped = Notifier<(started: Bool, AudioEntryId, AudioPlayerStopReason?)>() var playingStartedStopped = Notifier<(started: Bool, AudioEntryId, AudioPlayerStopReason?)>()
private let audioPlayerProvider: () -> AudioPlayer init(audioPlayer: AudioPlayer) {
player = audioPlayer
init(audioPlayerProvider: @escaping () -> AudioPlayer) {
self.audioPlayerProvider = audioPlayerProvider
player = audioPlayerProvider()
player.delegate = self player.delegate = self
configureAudioSession() configureAudioSession()
@@ -86,10 +83,6 @@ final class AudioPlayerService {
player.rate = rate player.rate = rate
} }
func update(volume: Float) {
player.volume = volume
}
func add(_ node: AVAudioNode) { func add(_ node: AVAudioNode) {
player.attach(node: node) player.attach(node: node)
} }
@@ -111,13 +104,12 @@ final class AudioPlayerService {
} }
private func recreatePlayer() { private func recreatePlayer() {
player = audioPlayerProvider() player = AudioPlayer(configuration: .init(enableLogs: true))
player.delegate = self player.delegate = self
} }
private func registerSessionEvents() { private func registerSessionEvents() {
// Note that a real app might need to observer other AVAudioSession notifications as well // Note that a real app might need to observer other AVAudioSession notifications as well
#if os(iOS)
audioSystemResetObserver = NotificationCenter.default.addObserver( audioSystemResetObserver = NotificationCenter.default.addObserver(
forName: AVAudioSession.mediaServicesWereResetNotification, forName: AVAudioSession.mediaServicesWereResetNotification,
object: nil, object: nil,
@@ -126,11 +118,9 @@ final class AudioPlayerService {
self.configureAudioSession() self.configureAudioSession()
self.recreatePlayer() self.recreatePlayer()
} }
#endif
} }
private func configureAudioSession() { private func configureAudioSession() {
#if os(iOS)
do { do {
print("AudioSession category is AVAudioSessionCategoryPlayback") print("AudioSession category is AVAudioSessionCategoryPlayback")
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, policy: .longFormAudio, options: []) try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, policy: .longFormAudio, options: [])
@@ -138,11 +128,9 @@ final class AudioPlayerService {
} catch let error as NSError { } catch let error as NSError {
print("Couldn't setup audio session category to Playback \(error.localizedDescription)") print("Couldn't setup audio session category to Playback \(error.localizedDescription)")
} }
#endif
} }
private func activateAudioSession() { private func activateAudioSession() {
#if os(iOS)
do { do {
print("AudioSession is active") print("AudioSession is active")
try AVAudioSession.sharedInstance().setActive(true, options: []) try AVAudioSession.sharedInstance().setActive(true, options: [])
@@ -150,18 +138,15 @@ final class AudioPlayerService {
} catch let error as NSError { } catch let error as NSError {
print("Couldn't set audio session to active: \(error.localizedDescription)") print("Couldn't set audio session to active: \(error.localizedDescription)")
} }
#endif
} }
private func deactivateAudioSession() { private func deactivateAudioSession() {
#if os(iOS)
do { do {
print("AudioSession is deactivated") print("AudioSession is deactivated")
try AVAudioSession.sharedInstance().setActive(false) try AVAudioSession.sharedInstance().setActive(false)
} catch let error as NSError { } catch let error as NSError {
print("Couldn't deactivate audio session: \(error.localizedDescription)") print("Couldn't deactivate audio session: \(error.localizedDescription)")
} }
#endif
} }
} }
@@ -202,3 +187,4 @@ extension AudioPlayerService: AudioPlayerDelegate {
delegate?.metadataReceived(metadata: metadata) delegate?.metadataReceived(metadata: metadata)
} }
} }
+14 -103
View File
@@ -1,13 +1,14 @@
//
// Created by Dimitris C.
// Copyright © 2024 Decimal. All rights reserved.
//
#if os(iOS)
import UIKit import UIKit
#else
import AppKit
#endif
final class DisplayLink { final class DisplayLink {
private var displayLink: DisplayLinkPlatform? private var displayLink: CADisplayLink?
private var target = DisplayLinkTarget()
var isPaused: Bool = true { var isPaused: Bool = true {
didSet { didSet {
@@ -15,124 +16,34 @@ final class DisplayLink {
} }
} }
init(onTick: @escaping (DisplayLinkFrame) -> Void) { init(onTick: @escaping (CADisplayLink) -> Void) {
displayLink = DisplayLinkPlatform() target.onTick = onTick
displayLink?.onTick = onTick
} }
deinit { deinit {
deactivate() deactivate()
} }
func activate() {
displayLink?.activate()
self.isPaused = false
}
func deactivate() {
displayLink?.deactivate()
isPaused = true
}
}
struct DisplayLinkFrame {
var timestamp: TimeInterval
var duration: TimeInterval
}
#if os(iOS)
final class DisplayLinkPlatform {
private final class DisplayLinkTarget {
var onTick: ((DisplayLinkFrame) -> Void)?
@objc func tick(_ link: CADisplayLink) {
onTick?(DisplayLinkFrame(timestamp: link.timestamp, duration: link.duration))
}
}
var onTick: ((DisplayLinkFrame) -> Void)?
private var target = DisplayLinkTarget()
var displayLink: CADisplayLink?
var isPaused: Bool {
get { displayLink?.isPaused ?? false }
set { displayLink?.isPaused = newValue }
}
init() {
displayLink = CADisplayLink(target: target, selector: #selector(DisplayLinkTarget.tick(_:)))
target.onTick = { [weak self] value in
self?.onTick?(value)
}
}
deinit {
displayLink?.invalidate()
}
func activate() { func activate() {
displayLink?.invalidate() displayLink?.invalidate()
displayLink = nil displayLink = nil
displayLink = CADisplayLink(target: target, selector: #selector(DisplayLinkTarget.tick(_:))) displayLink = CADisplayLink(target: target, selector: #selector(DisplayLinkTarget.tick(_:)))
displayLink?.preferredFrameRateRange = .init(minimum: 6, maximum: 10) displayLink?.preferredFrameRateRange = .init(minimum: 6, maximum: 10)
displayLink?.add(to: .current, forMode: .common) displayLink?.add(to: .current, forMode: .common)
self.isPaused = false
} }
func deactivate() { func deactivate() {
isPaused = true
displayLink?.invalidate() displayLink?.invalidate()
displayLink = nil displayLink = nil
} }
} }
#else
final class DisplayLinkPlatform {
var onTick: ((DisplayLinkFrame) -> Void)? private final class DisplayLinkTarget {
var isPaused: Bool = true { var onTick: ((CADisplayLink) -> Void)?
didSet {
guard isPaused != oldValue else { return }
if isPaused == true {
CVDisplayLinkStop(self.displayLink)
} else {
CVDisplayLinkStart(self.displayLink)
}
}
}
/// The CVDisplayLink that powers this DisplayLink instance. @objc func tick(_ link: CADisplayLink) {
var displayLink: CVDisplayLink = { onTick?(link)
var dl: CVDisplayLink? = nil
CVDisplayLinkCreateWithActiveCGDisplays(&dl)
return dl!
}()
init() {
CVDisplayLinkSetOutputHandler(self.displayLink, { [weak self] (displayLink, inNow, inOutputTime, flageIn, flagsOut) -> CVReturn in
let frame = DisplayLinkFrame(
timestamp: inNow.pointee.timeInterval,
duration: inOutputTime.pointee.timeInterval - inNow.pointee.timeInterval)
DispatchQueue.main.async {
guard self?.isPaused == false else { return }
self?.onTick?(frame)
}
return kCVReturnSuccess
})
}
func activate() {
isPaused = true
}
func deactivate() {
isPaused = false
} }
} }
extension CVTimeStamp {
fileprivate var timeInterval: TimeInterval {
return TimeInterval(videoTime) / TimeInterval(self.videoTimeScale)
}
}
#endif
@@ -1,24 +0,0 @@
import SwiftUI
struct PrefersStackNavigationEnvironmentKey: EnvironmentKey {
static var defaultValue: Bool = false
}
extension EnvironmentValues {
var prefersStackNavigation: Bool {
get { self[PrefersStackNavigationEnvironmentKey.self] }
set { self[PrefersStackNavigationEnvironmentKey.self] = newValue }
}
}
#if os(iOS)
extension PrefersStackNavigationEnvironmentKey: UITraitBridgedEnvironmentKey {
static func read(from traitCollection: UITraitCollection) -> Bool {
return traitCollection.userInterfaceIdiom == .phone || traitCollection.userInterfaceIdiom == .tv
}
static func write(to mutableTraits: inout UIMutableTraits, value: Bool) {
// Do not write.
}
}
#endif
@@ -1,4 +1,4 @@
// //
// Created by Dimitris C. // Created by Dimitris C.
// Copyright © 2024 Decimal. All rights reserved. // Copyright © 2024 Decimal. All rights reserved.
// //
@@ -8,28 +8,28 @@ import SwiftUI
struct ContentView: View { struct ContentView: View {
@Environment(AppModel.self) var appModel @Environment(AppModel.self) var appModel
@Environment(\.prefersStackNavigation) private var prefersStackNavigation
@State private var selection: NavigationContent? @State private var selection: NavigationContent?
var body: some View { var body: some View {
if prefersStackNavigation { NavigationStack {
NavigationStack { List {
ContentSidebar(selection: $selection) NavigationLink(value: NavigationContent.audioPlayer) {
.navigationTitle("Home") Label("Audio Player", systemImage: "play")
} }
} else {
NavigationSplitView { NavigationLink(value: NavigationContent.audioQueue) {
ContentSidebar(selection: $selection) Label("Audio Queue", systemImage: "play.square.stack")
.navigationTitle("Home")
} detail: {
if let selection {
DetailView(selection: selection)
} }
} }
.onAppear { .navigationTitle("Home")
selection = .audioPlayer .navigationDestination(for: NavigationContent.self) { content in
DetailView(selection: content)
} }
} }
} }
} }
#Preview {
ContentView()
}
@@ -24,9 +24,6 @@ struct ContentSidebar: View {
} }
} }
.navigationTitle("Home") .navigationTitle("Home")
.navigationDestination(item: $selection, destination: { selection in
DetailView(selection: selection)
})
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'AudioStreaming' s.name = 'AudioStreaming'
s.version = '1.2.3' s.version = '1.2.1'
s.license = 'MIT' s.license = 'MIT'
s.summary = 'An AudioPlayer/Streaming library for iOS written in Swift using AVAudioEngine.' s.summary = 'An AudioPlayer/Streaming library for iOS written in Swift using AVAudioEngine.'
s.homepage = 'https://github.com/dimitris-c/AudioStreaming' s.homepage = 'https://github.com/dimitris-c/AudioStreaming'
+2 -6
View File
@@ -742,7 +742,6 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.1.0; MARKETING_VERSION = 1.1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
@@ -802,7 +801,6 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.1.0; MARKETING_VERSION = 1.1.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
@@ -833,12 +831,11 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.2.3; MARKETING_VERSION = 1.2.1;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming; PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -865,12 +862,11 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.2.3; MARKETING_VERSION = 1.2.1;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming; PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -121,21 +121,15 @@ class AudioEntry {
} }
func duration() -> Double { func duration() -> Double {
lock.lock() guard sampleRate > 0 else { return 0 }
guard sampleRate > 0 else {
lock.unlock()
return 0
}
if let audioDataPacketOffset = audioStreamState.dataPacketOffset { if let audioDataPacketOffset = audioStreamState.dataPacketOffset {
let framesPerPacket = UInt64(audioStreamFormat.mFramesPerPacket) let framesPerPacket = UInt64(audioStreamFormat.mFramesPerPacket)
if audioDataPacketOffset > 0, framesPerPacket > 0 { if audioDataPacketOffset > 0, framesPerPacket > 0 {
let duration = Double(audioDataPacketOffset * framesPerPacket) / audioStreamFormat.mSampleRate return Double(audioDataPacketOffset * framesPerPacket) / audioStreamFormat.mSampleRate
lock.unlock()
return duration
} }
} }
lock.unlock()
let calculatedBitrate = self.calculatedBitrate() let calculatedBitrate = self.calculatedBitrate()
if calculatedBitrate < 1.0 || source.length == 0 { if calculatedBitrate < 1.0 || source.length == 0 {
return 0 return 0
@@ -9,14 +9,14 @@ import CoreAudio
var maxFramesPerSlice: AVAudioFrameCount = 8192 var maxFramesPerSlice: AVAudioFrameCount = 8192
final class AudioRendererContext { final class AudioRendererContext {
let waiting = Atomic<Bool>(false) var waiting = Atomic<Bool>(false)
let lock = UnfairLock() let lock = UnfairLock()
let bufferContext: BufferContext let bufferContext: BufferContext
let audioBuffer: AudioBuffer var audioBuffer: AudioBuffer
let inOutAudioBufferList: UnsafeMutablePointer<AudioBufferList> var inOutAudioBufferList: UnsafeMutablePointer<AudioBufferList>
let packetsSemaphore = DispatchSemaphore(value: 0) let packetsSemaphore = DispatchSemaphore(value: 0)
@@ -24,7 +24,7 @@ final class AudioRendererContext {
let framesRequiredAfterRebuffering: UInt32 let framesRequiredAfterRebuffering: UInt32
let framesRequiredForDataAfterSeekPlaying: UInt32 let framesRequiredForDataAfterSeekPlaying: UInt32
let waitingForDataAfterSeekFrameCount = Atomic<Int32>(0) var waitingForDataAfterSeekFrameCount = Atomic<Int32>(0)
private let configuration: AudioPlayerConfiguration private let configuration: AudioPlayerConfiguration
@@ -214,24 +214,23 @@ final class AudioFileStreamProcessor {
propertyId: AudioFileStreamPropertyID, propertyId: AudioFileStreamPropertyID,
flags _: UnsafeMutablePointer<AudioFileStreamPropertyFlags>) flags _: UnsafeMutablePointer<AudioFileStreamPropertyFlags>)
{ {
guard let entry = playerContext.audioReadingEntry else { return }
switch propertyId { switch propertyId {
case kAudioFileStreamProperty_DataOffset: case kAudioFileStreamProperty_DataOffset:
processDataOffset(entry: entry, fileStream: fileStream) processDataOffset(fileStream: fileStream)
case kAudioFileStreamProperty_FileFormat: case kAudioFileStreamProperty_FileFormat:
processFileFormat(fileStream: fileStream) processFileFormat(fileStream: fileStream)
case kAudioFileStreamProperty_DataFormat: case kAudioFileStreamProperty_DataFormat:
processDataFormat(entry: entry, fileStream: fileStream) processDataFormat(fileStream: fileStream)
case kAudioFileStreamProperty_AudioDataByteCount: case kAudioFileStreamProperty_AudioDataByteCount:
processDataByteCount(entry: entry, fileStream: fileStream) processDataByteCount(fileStream: fileStream)
case kAudioFileStreamProperty_AudioDataPacketCount: case kAudioFileStreamProperty_AudioDataPacketCount:
processAudioDataPacketCount(entry: entry, fileStream: fileStream) processAudioDataPacketCount(fileStream: fileStream)
case kAudioFileStreamProperty_ReadyToProducePackets: case kAudioFileStreamProperty_ReadyToProducePackets:
// check converter for discontinuous stream // check converter for discontinuous stream
processReadyToProducePackets(entry: entry, fileStream: fileStream) processReadyToProducePackets(fileStream: fileStream)
processPacketUpperBoundAndMaxPacketSize(entry: entry, fileStream: fileStream) processPacketUpperBoundAndMaxPacketSize(fileStream: fileStream)
case kAudioFileStreamProperty_FormatList: case kAudioFileStreamProperty_FormatList:
processFormatList(entry: entry, fileStream: fileStream) processFormatList(fileStream: fileStream)
default: default:
break break
} }
@@ -239,21 +238,19 @@ final class AudioFileStreamProcessor {
// MARK: AudioFileStream properties Processing // MARK: AudioFileStream properties Processing
private func processDataOffset(entry: AudioEntry, fileStream: AudioFileStreamID) { private func processDataOffset(fileStream: AudioFileStreamID) {
var offset: UInt64 = 0 var offset: UInt64 = 0
fileStreamGetProperty(value: &offset, fileStream: fileStream, propertyId: kAudioFileStreamProperty_DataOffset) fileStreamGetProperty(value: &offset, fileStream: fileStream, propertyId: kAudioFileStreamProperty_DataOffset)
entry.lock.lock(); defer { playerContext.audioReadingEntry?.lock.unlock() } playerContext.audioReadingEntry?.audioStreamState.processedDataFormat = true
entry.audioStreamState.processedDataFormat = true playerContext.audioReadingEntry?.audioStreamState.dataOffset = offset
entry.audioStreamState.dataOffset = offset
} }
private func processReadyToProducePackets(entry: AudioEntry, fileStream: AudioFileStreamID) { private func processReadyToProducePackets(fileStream: AudioFileStreamID) {
var packetCount: UInt64 = 0 var packetCount: UInt64 = 0
var packetCountSize = UInt32(MemoryLayout.size(ofValue: packetCount)) var packetCountSize = UInt32(MemoryLayout.size(ofValue: packetCount))
AudioFileStreamGetProperty(fileStream, kAudioFileStreamProperty_AudioDataPacketCount, &packetCountSize, &packetCount) AudioFileStreamGetProperty(fileStream, kAudioFileStreamProperty_AudioDataPacketCount, &packetCountSize, &packetCount)
entry.lock.lock(); defer { entry.lock.unlock() } playerContext.audioPlayingEntry?.audioStreamState.dataPacketCount = Double(packetCount)
entry.audioStreamState.dataPacketCount = Double(packetCount) if playerContext.audioPlayingEntry?.audioStreamFormat.mFormatID != kAudioFormatLinearPCM {
if entry.audioStreamFormat.mFormatID != kAudioFormatLinearPCM {
discontinuous = true discontinuous = true
} }
} }
@@ -267,9 +264,9 @@ final class AudioFileStreamProcessor {
} }
} }
private func processDataFormat(entry: AudioEntry, fileStream: AudioFileStreamID) { private func processDataFormat(fileStream: AudioFileStreamID) {
var audioStreamFormat = AudioStreamBasicDescription() var audioStreamFormat = AudioStreamBasicDescription()
entry.lock.lock(); defer { entry.lock.unlock() } guard let entry = playerContext.audioReadingEntry else { return }
if !entry.audioStreamState.processedDataFormat { if !entry.audioStreamState.processedDataFormat {
fileStreamGetProperty(value: &audioStreamFormat, fileStream: fileStream, propertyId: kAudioFileStreamProperty_DataFormat) fileStreamGetProperty(value: &audioStreamFormat, fileStream: fileStream, propertyId: kAudioFileStreamProperty_DataFormat)
@@ -292,8 +289,9 @@ final class AudioFileStreamProcessor {
packetBufferSize = 2048 // default value packetBufferSize = 2048 // default value
} }
} }
entry.lock.withLock {
entry.processedPacketsState.bufferSize = packetBufferSize entry.processedPacketsState.bufferSize = packetBufferSize
}
if !fileFormatsForDelayedConverterCreation.contains(currentFileFormat) { if !fileFormatsForDelayedConverterCreation.contains(currentFileFormat) {
createAudioConverter(from: entry.audioStreamFormat, to: outputAudioFormat) createAudioConverter(from: entry.audioStreamFormat, to: outputAudioFormat)
@@ -301,7 +299,8 @@ final class AudioFileStreamProcessor {
} }
} }
private func processPacketUpperBoundAndMaxPacketSize(entry: AudioEntry, fileStream: AudioFileStreamID) { private func processPacketUpperBoundAndMaxPacketSize(fileStream: AudioFileStreamID) {
guard let entry = playerContext.audioReadingEntry else { return }
var packetBufferSize: UInt32 = 0 var packetBufferSize: UInt32 = 0
var status = fileStreamGetProperty(value: &packetBufferSize, var status = fileStreamGetProperty(value: &packetBufferSize,
fileStream: fileStream, fileStream: fileStream,
@@ -319,22 +318,21 @@ final class AudioFileStreamProcessor {
} }
} }
private func processDataByteCount(entry: AudioEntry, fileStream: AudioFileStreamID) { private func processDataByteCount(fileStream: AudioFileStreamID) {
guard let entry = playerContext.audioReadingEntry else { return }
var audioDataByteCount: UInt64 = 0 var audioDataByteCount: UInt64 = 0
fileStreamGetProperty(value: &audioDataByteCount, fileStream: fileStream, propertyId: kAudioFileStreamProperty_AudioDataByteCount) fileStreamGetProperty(value: &audioDataByteCount, fileStream: fileStream, propertyId: kAudioFileStreamProperty_AudioDataByteCount)
entry.lock.lock(); defer { entry.lock.unlock() }
entry.audioStreamState.dataByteCount = audioDataByteCount entry.audioStreamState.dataByteCount = audioDataByteCount
} }
private func processAudioDataPacketCount(entry: AudioEntry, fileStream: AudioFileStreamID) { private func processAudioDataPacketCount(fileStream: AudioFileStreamID) {
guard let entry = playerContext.audioReadingEntry else { return }
var audioDataPacketCount: UInt64 = 0 var audioDataPacketCount: UInt64 = 0
fileStreamGetProperty(value: &audioDataPacketCount, fileStream: fileStream, propertyId: kAudioFileStreamProperty_AudioDataPacketCount) fileStreamGetProperty(value: &audioDataPacketCount, fileStream: fileStream, propertyId: kAudioFileStreamProperty_AudioDataPacketCount)
entry.lock.lock(); defer { entry.lock.unlock() }
entry.audioStreamState.dataPacketOffset = audioDataPacketCount entry.audioStreamState.dataPacketOffset = audioDataPacketCount
} }
private func processFormatList(entry: AudioEntry, fileStream: AudioFileStreamID) { private func processFormatList(fileStream: AudioFileStreamID) {
entry.lock.lock(); defer { entry.lock.unlock() }
let info = fileStreamGetPropertyInfo(fileStream: fileStream, propertyId: kAudioFileStreamProperty_FormatList) let info = fileStreamGetPropertyInfo(fileStream: fileStream, propertyId: kAudioFileStreamProperty_FormatList)
guard info.status == noErr else { return } guard info.status == noErr else { return }
var list: [AudioFormatListItem] = Array(repeating: AudioFormatListItem(), count: Int(info.size)) var list: [AudioFormatListItem] = Array(repeating: AudioFormatListItem(), count: Int(info.size))
@@ -361,12 +359,11 @@ final class AudioFileStreamProcessor {
// MARK: Packets Proc // MARK: Packets Proc
func propertyPacketsProc( func propertyPacketsProc(inNumberBytes: UInt32,
inNumberBytes: UInt32, inNumberPackets: UInt32,
inNumberPackets: UInt32, inInputData: UnsafeRawPointer,
inInputData: UnsafeRawPointer, inPacketDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>?)
inPacketDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>? {
) {
guard let entry = playerContext.audioReadingEntry else { return } guard let entry = playerContext.audioReadingEntry else { return }
guard entry.audioStreamState.processedDataFormat else { return } guard entry.audioStreamState.processedDataFormat else { return }
@@ -454,20 +451,16 @@ final class AudioFileStreamProcessor {
var framesToDecode: UInt32 = rendererContext.bufferContext.totalFrameCount - end var framesToDecode: UInt32 = rendererContext.bufferContext.totalFrameCount - end
let offset = Int(end * rendererContext.bufferContext.sizeInBytes) let offset = Int(end * rendererContext.bufferContext.sizeInBytes)
prefillLocalBufferList( prefillLocalBufferList(bufferList: localBufferList,
bufferList: localBufferList, dataOffset: offset,
dataOffset: offset, framesToDecode: framesToDecode)
framesToDecode: framesToDecode
)
status = AudioConverterFillComplexBuffer( status = AudioConverterFillComplexBuffer(converter,
converter, _converterCallback,
_converterCallback, &convertInfo,
&convertInfo, &framesToDecode,
&framesToDecode, localBufferList.unsafeMutablePointer,
localBufferList.unsafeMutablePointer, nil)
nil
)
framesAdded = framesToDecode framesAdded = framesToDecode
@@ -484,20 +477,16 @@ final class AudioFileStreamProcessor {
fillUsedFrames(framesCount: framesAdded) fillUsedFrames(framesCount: framesAdded)
continue packetProcess continue packetProcess
} }
prefillLocalBufferList( prefillLocalBufferList(bufferList: localBufferList,
bufferList: localBufferList, dataOffset: 0,
dataOffset: 0, framesToDecode: framesToDecode)
framesToDecode: framesToDecode
)
status = AudioConverterFillComplexBuffer( status = AudioConverterFillComplexBuffer(converter,
converter, _converterCallback,
_converterCallback, &convertInfo,
&convertInfo, &framesToDecode,
&framesToDecode, localBufferList.unsafeMutablePointer,
localBufferList.unsafeMutablePointer, nil)
nil
)
framesAdded += framesToDecode framesAdded += framesToDecode
@@ -516,20 +505,16 @@ final class AudioFileStreamProcessor {
var framesToDecode: UInt32 = start - end var framesToDecode: UInt32 = start - end
let offset = Int(end * rendererContext.bufferContext.sizeInBytes) let offset = Int(end * rendererContext.bufferContext.sizeInBytes)
prefillLocalBufferList( prefillLocalBufferList(bufferList: localBufferList,
bufferList: localBufferList, dataOffset: offset,
dataOffset: offset, framesToDecode: framesToDecode)
framesToDecode: framesToDecode
)
status = AudioConverterFillComplexBuffer( status = AudioConverterFillComplexBuffer(converter,
converter, _converterCallback,
_converterCallback, &convertInfo,
&convertInfo, &framesToDecode,
&framesToDecode, localBufferList.unsafeMutablePointer,
localBufferList.unsafeMutablePointer, nil)
nil
)
framesAdded = framesToDecode framesAdded = framesToDecode
if status == AudioConvertStatus.done.rawValue { if status == AudioConvertStatus.done.rawValue {
@@ -552,11 +537,10 @@ final class AudioFileStreamProcessor {
/// - parameter dataOffset: An `Int` value indicating any offset to be applied to the buffer data /// - parameter dataOffset: An `Int` value indicating any offset to be applied to the buffer data
/// - parameter framesToDecode: An `UInt32` value indicating the frames to be decoded, used in calculating the data size of the buffer. /// - parameter framesToDecode: An `UInt32` value indicating the frames to be decoded, used in calculating the data size of the buffer.
@inline(__always) @inline(__always)
private func prefillLocalBufferList( private func prefillLocalBufferList(bufferList: UnsafeMutableAudioBufferListPointer,
bufferList: UnsafeMutableAudioBufferListPointer, dataOffset: Int,
dataOffset: Int, framesToDecode: UInt32)
framesToDecode: UInt32 {
) {
if let mData = rendererContext.audioBuffer.mData { if let mData = rendererContext.audioBuffer.mData {
bufferList[0].mData = dataOffset > 0 ? mData + dataOffset : mData bufferList[0].mData = dataOffset > 0 ? mData + dataOffset : mData
} }
@@ -579,10 +563,9 @@ final class AudioFileStreamProcessor {
} }
@inline(__always) @inline(__always)
private func updateProcessedPackets( private func updateProcessedPackets(inPacketDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>?,
inPacketDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>?, inNumberPackets: UInt32)
inNumberPackets: UInt32 {
) {
guard let inPacketDescriptions = inPacketDescriptions else { return } guard let inPacketDescriptions = inPacketDescriptions else { return }
guard let readingEntry = playerContext.audioReadingEntry else { return } guard let readingEntry = playerContext.audioReadingEntry else { return }
let processedPackCount = readingEntry.processedPacketsState.count let processedPackCount = readingEntry.processedPacketsState.count
@@ -602,25 +585,23 @@ final class AudioFileStreamProcessor {
// MARK: - AudioFileStream proc method // MARK: - AudioFileStream proc method
private func _propertyListenerProc( private func _propertyListenerProc(clientData: UnsafeMutableRawPointer,
clientData: UnsafeMutableRawPointer, fileStream: AudioFileStreamID,
fileStream: AudioFileStreamID, propertyId: AudioFileStreamPropertyID,
propertyId: AudioFileStreamPropertyID, flags: UnsafeMutablePointer<AudioFileStreamPropertyFlags>)
flags: UnsafeMutablePointer<AudioFileStreamPropertyFlags> {
) {
let processor = clientData.to(type: AudioFileStreamProcessor.self) let processor = clientData.to(type: AudioFileStreamProcessor.self)
processor.propertyListenerProc(fileStream: fileStream, processor.propertyListenerProc(fileStream: fileStream,
propertyId: propertyId, propertyId: propertyId,
flags: flags) flags: flags)
} }
private func _propertyPacketsProc( private func _propertyPacketsProc(clientData: UnsafeMutableRawPointer,
clientData: UnsafeMutableRawPointer, inNumberBytes: UInt32,
inNumberBytes: UInt32, inNumberPackets: UInt32,
inNumberPackets: UInt32, inInputData: UnsafeRawPointer,
inInputData: UnsafeRawPointer, inPacketDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>?)
inPacketDescriptions: UnsafeMutablePointer<AudioStreamPacketDescription>? {
) {
let processor = clientData.to(type: AudioFileStreamProcessor.self) let processor = clientData.to(type: AudioFileStreamProcessor.self)
processor.propertyPacketsProc(inNumberBytes: inNumberBytes, processor.propertyPacketsProc(inNumberBytes: inNumberBytes,
inNumberPackets: inNumberPackets, inNumberPackets: inNumberPackets,
@@ -630,13 +611,12 @@ private func _propertyPacketsProc(
// MARK: - AudioConverterFillComplexBuffer callback method // MARK: - AudioConverterFillComplexBuffer callback method
private func _converterCallback( private func _converterCallback(inAudioConverter _: AudioConverterRef,
inAudioConverter _: AudioConverterRef, ioNumberDataPackets: UnsafeMutablePointer<UInt32>,
ioNumberDataPackets: UnsafeMutablePointer<UInt32>, ioData: UnsafeMutablePointer<AudioBufferList>,
ioData: UnsafeMutablePointer<AudioBufferList>, outDataPacketDescription: UnsafeMutablePointer<UnsafeMutablePointer<AudioStreamPacketDescription>?>?,
outDataPacketDescription: UnsafeMutablePointer<UnsafeMutablePointer<AudioStreamPacketDescription>?>?, inUserData: UnsafeMutableRawPointer?) -> OSStatus
inUserData: UnsafeMutableRawPointer? {
) -> OSStatus {
guard let convertInfo = inUserData?.assumingMemoryBound(to: AudioConvertInfo.self) else { return 0 } guard let convertInfo = inUserData?.assumingMemoryBound(to: AudioConvertInfo.self) else { return 0 }
// we need to tell the converter to stop converting after it should stop converting // we need to tell the converter to stop converting after it should stop converting
@@ -114,7 +114,9 @@ final class AudioPlayerRenderProcessor: NSObject {
} }
} else { } else {
if let mDataBuffer = audioBuffer.mData { if let mDataBuffer = audioBuffer.mData {
memcpy(bufferList.mBuffers.mData, mDataBuffer + Int(start * frameSizeInBytes), Int(bufferList.mBuffers.mDataByteSize)) memcpy(bufferList.mBuffers.mData,
mDataBuffer + Int(start * frameSizeInBytes),
Int(bufferList.mBuffers.mDataByteSize))
} }
} }
totalFramesCopied = framesToCopy totalFramesCopied = framesToCopy
@@ -135,7 +137,9 @@ final class AudioPlayerRenderProcessor: NSObject {
} }
} else { } else {
if let mDataBuffer = audioBuffer.mData { if let mDataBuffer = audioBuffer.mData {
memcpy(bufferList.mBuffers.mData, mDataBuffer + Int(start * frameSizeInBytes), Int(bufferList.mBuffers.mDataByteSize)) memcpy(bufferList.mBuffers.mData,
mDataBuffer + Int(start * frameSizeInBytes),
Int(bufferList.mBuffers.mDataByteSize))
} }
} }
@@ -150,7 +154,9 @@ final class AudioPlayerRenderProcessor: NSObject {
memset(ioBufferData + Int(frameToCopy * frameSizeInBytes), 0, Int(frameSizeInBytes * moreFramesToCopy)) memset(ioBufferData + Int(frameToCopy * frameSizeInBytes), 0, Int(frameSizeInBytes * moreFramesToCopy))
} else { } else {
if let mDataBuffer = audioBuffer.mData { if let mDataBuffer = audioBuffer.mData {
memcpy(ioBufferData + Int(frameToCopy * frameSizeInBytes), mDataBuffer, Int(frameSizeInBytes * moreFramesToCopy)) memcpy(ioBufferData + Int(frameToCopy * frameSizeInBytes),
mDataBuffer,
Int(frameSizeInBytes * moreFramesToCopy))
} }
} }
} }
@@ -264,11 +270,10 @@ final class AudioPlayerRenderProcessor: NSObject {
return UnsafePointer(rendererContext.inOutAudioBufferList) return UnsafePointer(rendererContext.inOutAudioBufferList)
} }
func render( func render(inNumberFrames: UInt32,
inNumberFrames: UInt32, ioData: UnsafeMutablePointer<AudioBufferList>,
ioData: UnsafeMutablePointer<AudioBufferList>, flags _: UnsafeMutablePointer<AudioUnitRenderActionFlags>) -> OSStatus
flags _: UnsafeMutablePointer<AudioUnitRenderActionFlags> {
) -> OSStatus {
var status = noErr var status = noErr
rendererContext.inOutAudioBufferList[0].mBuffers.mData = ioData.pointee.mBuffers.mData rendererContext.inOutAudioBufferList[0].mBuffers.mData = ioData.pointee.mBuffers.mData
@@ -303,18 +308,13 @@ final class AudioPlayerRenderProcessor: NSObject {
return status return status
} }
func renderProvider( func renderProvider(flags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
flags: UnsafeMutablePointer<AudioUnitRenderActionFlags>, timeStamp _: UnsafePointer<AudioTimeStamp>,
timeStamp _: UnsafePointer<AudioTimeStamp>, inNumberFrames: AUAudioFrameCount,
inNumberFrames: AUAudioFrameCount, inputBusNumber: Int,
inputBusNumber: Int, inputData: UnsafeMutablePointer<AudioBufferList>) -> AUAudioUnitStatus
inputData: UnsafeMutablePointer<AudioBufferList> {
) -> AUAudioUnitStatus {
guard inputBusNumber == 0 else { return noErr } guard inputBusNumber == 0 else { return noErr }
return render( return render(inNumberFrames: inNumberFrames, ioData: inputData, flags: flags)
inNumberFrames: inNumberFrames,
ioData: inputData,
flags: flags
)
} }
} }
-1
View File
@@ -6,7 +6,6 @@ let package = Package(
name: "AudioStreaming", name: "AudioStreaming",
platforms: [ platforms: [
.iOS(.v12), .iOS(.v12),
.macOS(.v13)
], ],
products: [ products: [
.library( .library(