Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1801e5065 | |||
| d041f17e76 | |||
| 5db7c2921b | |||
| dec732a597 | |||
| 51af91f4d4 | |||
| 02ba7012da | |||
| 473afdaa50 | |||
| 8a13074c21 | |||
| 1b7558a4f1 | |||
| 7d91e318d1 | |||
| 4e2cd36677 | |||
| f2adacb687 | |||
| 553132f486 | |||
| 1f5f7c8be3 | |||
| 70a4dfd698 | |||
| 81bf90f5c4 | |||
| 8424611b34 |
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,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'
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ let package = Package(
|
|||||||
name: "AudioStreaming",
|
name: "AudioStreaming",
|
||||||
platforms: [
|
platforms: [
|
||||||
.iOS(.v12),
|
.iOS(.v12),
|
||||||
.macOS(.v13)
|
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
|
|||||||
Reference in New Issue
Block a user