Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 45178a9afc |
@@ -10,15 +10,14 @@ on:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
iOS:
|
||||
name: Test iOS
|
||||
test:
|
||||
name: Test Swift Package
|
||||
runs-on: macOS-latest
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
|
||||
strategy:
|
||||
matrix:
|
||||
destination: ["OS=latest,name=iPhone 17"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: iOS - ${{ matrix.destination }}
|
||||
run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project "AudioStreaming.xcodeproj" -scheme "AudioStreaming" -destination "${{ matrix.destination }}" clean test | xcpretty
|
||||
- name: Build
|
||||
run: swift build
|
||||
- name: Run tests
|
||||
run: swift test --parallel
|
||||
|
||||
+3
-3
@@ -41,11 +41,11 @@ playground.xcworkspace
|
||||
# Packages/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
# *.xcodeproj
|
||||
*.xcodeproj
|
||||
#
|
||||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
||||
# hence it is not needed unless you have added a package configuration file to your project
|
||||
# .swiftpm
|
||||
.swiftpm
|
||||
|
||||
.build/
|
||||
|
||||
@@ -58,7 +58,7 @@ playground.xcworkspace
|
||||
# Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
*.xcworkspace
|
||||
|
||||
# Carthage
|
||||
#
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
#include "include/VorbisFileBridge.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <vorbis/vorbisfile.h>
|
||||
|
||||
struct VFRemoteStream {
|
||||
uint8_t *buf;
|
||||
size_t cap, head, tail, size;
|
||||
int eof;
|
||||
long long pos; // Current read position in the stream
|
||||
long long total_pushed; // Total bytes pushed into the buffer
|
||||
pthread_mutex_t m;
|
||||
pthread_cond_t cv;
|
||||
};
|
||||
|
||||
// Simple ring buffer write
|
||||
static size_t rb_write(struct VFRemoteStream *s, const uint8_t *src, size_t len) {
|
||||
size_t written = 0;
|
||||
while (written < len) {
|
||||
size_t free_space = s->cap - s->size;
|
||||
if (free_space == 0) break;
|
||||
size_t chunk = s->cap - s->tail;
|
||||
if (chunk > len - written) chunk = len - written;
|
||||
if (chunk > free_space) chunk = free_space;
|
||||
memcpy(s->buf + s->tail, src + written, chunk);
|
||||
s->tail = (s->tail + chunk) % s->cap;
|
||||
s->size += chunk;
|
||||
written += chunk;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
// Simple ring buffer read
|
||||
static size_t rb_read(struct VFRemoteStream *s, uint8_t *dst, size_t len) {
|
||||
size_t read = 0;
|
||||
while (read < len && s->size > 0) {
|
||||
size_t chunk = s->cap - s->head;
|
||||
if (chunk > s->size) chunk = s->size;
|
||||
if (chunk > len - read) chunk = len - read;
|
||||
memcpy(dst + read, s->buf + s->head, chunk);
|
||||
s->head = (s->head + chunk) % s->cap;
|
||||
s->size -= chunk;
|
||||
read += chunk;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
// Create a stream buffer
|
||||
VFStreamRef VFStreamCreate(size_t capacity_bytes) {
|
||||
struct VFRemoteStream *s = (struct VFRemoteStream *)calloc(1, sizeof(struct VFRemoteStream));
|
||||
if (!s) return NULL;
|
||||
s->buf = (uint8_t *)malloc(capacity_bytes);
|
||||
if (!s->buf) { free(s); return NULL; }
|
||||
s->cap = capacity_bytes;
|
||||
pthread_mutex_init(&s->m, NULL);
|
||||
pthread_cond_init(&s->cv, NULL);
|
||||
return s;
|
||||
}
|
||||
|
||||
// Destroy a stream buffer
|
||||
void VFStreamDestroy(VFStreamRef sr) {
|
||||
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
|
||||
if (!s) return;
|
||||
pthread_mutex_destroy(&s->m);
|
||||
pthread_cond_destroy(&s->cv);
|
||||
free(s->buf);
|
||||
free(s);
|
||||
}
|
||||
|
||||
// Get available bytes in the buffer
|
||||
size_t VFStreamAvailableBytes(VFStreamRef sr) {
|
||||
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
|
||||
if (!s) return 0;
|
||||
pthread_mutex_lock(&s->m);
|
||||
size_t sz = s->size;
|
||||
pthread_mutex_unlock(&s->m);
|
||||
return sz;
|
||||
}
|
||||
|
||||
// Push data into the stream
|
||||
void VFStreamPush(VFStreamRef sr, const uint8_t *data, size_t len) {
|
||||
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
|
||||
if (!s || !data || len == 0) return;
|
||||
|
||||
pthread_mutex_lock(&s->m);
|
||||
size_t written_total = 0;
|
||||
while (written_total < len) {
|
||||
size_t w = rb_write(s, data + written_total, len - written_total);
|
||||
written_total += w;
|
||||
if (written_total < len) {
|
||||
// Buffer full, wait for consumer to read
|
||||
pthread_cond_wait(&s->cv, &s->m);
|
||||
}
|
||||
}
|
||||
s->total_pushed += (long long)len;
|
||||
pthread_cond_broadcast(&s->cv);
|
||||
pthread_mutex_unlock(&s->m);
|
||||
}
|
||||
|
||||
// Mark the stream as EOF
|
||||
void VFStreamMarkEOF(VFStreamRef sr) {
|
||||
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
|
||||
if (!s) return;
|
||||
pthread_mutex_lock(&s->m);
|
||||
s->eof = 1;
|
||||
pthread_cond_broadcast(&s->cv);
|
||||
pthread_mutex_unlock(&s->m);
|
||||
}
|
||||
|
||||
// libvorbisfile callbacks
|
||||
|
||||
// Read callback for libvorbisfile
|
||||
static size_t read_cb(void *ptr, size_t size, size_t nmemb, void *datasrc) {
|
||||
struct VFRemoteStream *s = (struct VFRemoteStream *)datasrc;
|
||||
size_t want_bytes = size * nmemb;
|
||||
size_t got = 0;
|
||||
|
||||
pthread_mutex_lock(&s->m);
|
||||
// Read what's available NOW - don't block waiting for more data
|
||||
while (got < want_bytes && s->size > 0) {
|
||||
size_t chunk = rb_read(s, (uint8_t *)ptr + got, want_bytes - got);
|
||||
s->pos += (long long)chunk;
|
||||
got += chunk;
|
||||
|
||||
if (chunk == 0) break;
|
||||
// Allow producer to push more
|
||||
pthread_cond_broadcast(&s->cv);
|
||||
}
|
||||
|
||||
// If nothing available and EOF, we're done
|
||||
if (got == 0 && s->eof) {
|
||||
// Return 0 to signal EOF to libvorbisfile
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&s->m);
|
||||
|
||||
return size ? (got / size) : 0;
|
||||
}
|
||||
|
||||
// Seek callback - seek within the ring buffer
|
||||
static int seek_cb(void *datasrc, ogg_int64_t offset, int whence) {
|
||||
struct VFRemoteStream *s = (struct VFRemoteStream *)datasrc;
|
||||
if (!s) return -1;
|
||||
|
||||
pthread_mutex_lock(&s->m);
|
||||
|
||||
ogg_int64_t new_pos = 0;
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
new_pos = offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
new_pos = s->pos + offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
new_pos = s->total_pushed + offset;
|
||||
break;
|
||||
default:
|
||||
pthread_mutex_unlock(&s->m);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if the new position is valid (within available data)
|
||||
if (new_pos < 0 || new_pos > s->total_pushed) {
|
||||
pthread_mutex_unlock(&s->m);
|
||||
return -1; // Can't seek outside available data
|
||||
}
|
||||
|
||||
// Calculate how much data we've already consumed from the buffer
|
||||
long long already_consumed = s->pos - ((long long)s->total_pushed - (long long)s->size);
|
||||
|
||||
// Calculate the new head position
|
||||
long long pos_delta = new_pos - s->pos;
|
||||
|
||||
// For forward seeks, we need to have enough data in the buffer
|
||||
if (pos_delta > 0 && pos_delta > (long long)s->size) {
|
||||
pthread_mutex_unlock(&s->m);
|
||||
return -1; // Not enough data in buffer to seek forward
|
||||
}
|
||||
|
||||
// For backward seeks, check if that data is still in the buffer
|
||||
if (pos_delta < 0 && (-pos_delta) > already_consumed) {
|
||||
pthread_mutex_unlock(&s->m);
|
||||
return -1; // Data has been discarded from buffer
|
||||
}
|
||||
|
||||
// Adjust head pointer
|
||||
if (pos_delta >= 0) {
|
||||
// Forward seek: advance head
|
||||
s->head = (s->head + pos_delta) % s->cap;
|
||||
s->size -= (size_t)pos_delta;
|
||||
} else {
|
||||
// Backward seek: rewind head
|
||||
size_t rewind = (size_t)(-pos_delta);
|
||||
if (s->head >= rewind) {
|
||||
s->head -= rewind;
|
||||
} else {
|
||||
s->head = s->cap - (rewind - s->head);
|
||||
}
|
||||
s->size += rewind;
|
||||
}
|
||||
|
||||
s->pos = new_pos;
|
||||
pthread_mutex_unlock(&s->m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Close callback - no-op
|
||||
static int close_cb(void *datasrc) {
|
||||
(void)datasrc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Tell callback - return current position
|
||||
static long tell_cb(void *datasrc) {
|
||||
struct VFRemoteStream *s = (struct VFRemoteStream *)datasrc;
|
||||
return (long)s->pos;
|
||||
}
|
||||
|
||||
// Open a vorbis file using callbacks
|
||||
int VFOpen(VFStreamRef sr, VFFileRef *out_vf) {
|
||||
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
|
||||
if (!s || !out_vf) return -1;
|
||||
|
||||
OggVorbis_File *vf = (OggVorbis_File *)malloc(sizeof(OggVorbis_File));
|
||||
if (!vf) return -1;
|
||||
|
||||
ov_callbacks cbs;
|
||||
cbs.read_func = read_cb;
|
||||
cbs.seek_func = NULL; // Non-seekable streaming (seeking handled at Swift level)
|
||||
cbs.close_func = close_cb;
|
||||
cbs.tell_func = tell_cb;
|
||||
|
||||
int rc = ov_open_callbacks((void *)s, vf, NULL, 0, cbs);
|
||||
if (rc < 0) { free(vf); return rc; }
|
||||
|
||||
*out_vf = (VFFileRef)vf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Clear a vorbis file
|
||||
void VFClear(VFFileRef fr) {
|
||||
OggVorbis_File *vf = (OggVorbis_File *)fr;
|
||||
if (!vf) return;
|
||||
ov_clear(vf);
|
||||
free(vf);
|
||||
}
|
||||
|
||||
// Get stream info
|
||||
int VFGetInfo(VFFileRef fr, VFStreamInfo *out_info) {
|
||||
OggVorbis_File *vf = (OggVorbis_File *)fr;
|
||||
if (!vf || !out_info) return -1;
|
||||
|
||||
vorbis_info const *info = ov_info(vf, -1);
|
||||
if (!info) return -1;
|
||||
|
||||
out_info->sample_rate = info->rate;
|
||||
out_info->channels = info->channels;
|
||||
out_info->total_pcm_samples = ov_pcm_total(vf, -1);
|
||||
out_info->duration_seconds = ov_time_total(vf, -1);
|
||||
out_info->bitrate_nominal = info->bitrate_nominal;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read deinterleaved float PCM frames
|
||||
long VFReadFloat(VFFileRef fr, float ***out_pcm, int max_frames) {
|
||||
OggVorbis_File *vf = (OggVorbis_File *)fr;
|
||||
if (!vf || !out_pcm || max_frames <= 0) return -1;
|
||||
|
||||
int bitstream = 0;
|
||||
long frames = ov_read_float(vf, out_pcm, max_frames, &bitstream);
|
||||
|
||||
// Returns: frames read (0 = EOF, <0 = error)
|
||||
return frames;
|
||||
}
|
||||
|
||||
// Read interleaved float PCM frames (legacy, less efficient)
|
||||
long VFReadInterleavedFloat(VFFileRef fr, float *dst, int max_frames) {
|
||||
OggVorbis_File *vf = (OggVorbis_File *)fr;
|
||||
if (!vf || !dst || max_frames <= 0) return -1;
|
||||
|
||||
int bitstream = 0;
|
||||
float **pcm = NULL;
|
||||
long frames = ov_read_float(vf, &pcm, max_frames, &bitstream);
|
||||
|
||||
if (frames <= 0) return frames; // 0 EOF, <0 error/hole
|
||||
|
||||
vorbis_info const *info = ov_info(vf, -1);
|
||||
int ch = info->channels;
|
||||
|
||||
// Interleave the PCM data
|
||||
for (long f = 0; f < frames; ++f) {
|
||||
for (int c = 0; c < ch; ++c) {
|
||||
dst[f * ch + c] = pcm[c][f];
|
||||
}
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
// Seek to a specific time in seconds
|
||||
int VFSeekTime(VFFileRef fr, double time_seconds) {
|
||||
OggVorbis_File *vf = (OggVorbis_File *)fr;
|
||||
if (!vf) return -1;
|
||||
|
||||
// Use ov_time_seek for time-based seeking
|
||||
// Returns 0 on success, nonzero on failure
|
||||
return ov_time_seek(vf, time_seconds);
|
||||
}
|
||||
|
||||
// Check if the stream is seekable
|
||||
int VFIsSeekable(VFFileRef fr) {
|
||||
OggVorbis_File *vf = (OggVorbis_File *)fr;
|
||||
if (!vf) return 0;
|
||||
|
||||
// Returns nonzero if the stream is seekable
|
||||
return ov_seekable(vf);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// AudioCodecs.h
|
||||
// AudioStreaming
|
||||
//
|
||||
// Created on 25/10/2025.
|
||||
//
|
||||
|
||||
#ifndef AudioCodecs_h
|
||||
#define AudioCodecs_h
|
||||
|
||||
#import "VorbisFileBridge.h"
|
||||
|
||||
#endif /* AudioCodecs_h */
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef VORBIS_FILE_BRIDGE_H
|
||||
#define VORBIS_FILE_BRIDGE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Opaque refs for Swift-friendly API
|
||||
typedef void * VFStreamRef;
|
||||
typedef void * VFFileRef;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Stream info structure
|
||||
typedef struct {
|
||||
int sample_rate;
|
||||
int channels;
|
||||
long long total_pcm_samples; // -1 if unknown
|
||||
double duration_seconds; // < 0 if unknown
|
||||
long bitrate_nominal; // nominal bitrate in bits/sec, or 0 if unknown
|
||||
} VFStreamInfo;
|
||||
|
||||
// Stream lifecycle
|
||||
VFStreamRef VFStreamCreate(size_t capacity_bytes);
|
||||
void VFStreamDestroy(VFStreamRef s);
|
||||
size_t VFStreamAvailableBytes(VFStreamRef s);
|
||||
|
||||
// Feeding data
|
||||
void VFStreamPush(VFStreamRef s, const uint8_t *data, size_t len);
|
||||
void VFStreamMarkEOF(VFStreamRef s);
|
||||
|
||||
// Decoder lifecycle
|
||||
// Returns 0 on success, negative on error (same codes as ov_open_callbacks)
|
||||
int VFOpen(VFStreamRef s, VFFileRef *out_vf);
|
||||
void VFClear(VFFileRef vf);
|
||||
|
||||
// Query info; returns 0 on success
|
||||
int VFGetInfo(VFFileRef vf, VFStreamInfo *out_info);
|
||||
|
||||
// Read interleaved float32 PCM frames into dst; returns number of frames read, 0 on EOF, <0 on error
|
||||
long VFReadInterleavedFloat(VFFileRef vf, float *dst, int max_frames);
|
||||
|
||||
// Read deinterleaved float32 PCM frames (channel-by-channel); returns number of frames read, 0 on EOF, <0 on error
|
||||
// out_pcm will point to an array of channel pointers (float**)
|
||||
long VFReadFloat(VFFileRef vf, float ***out_pcm, int max_frames);
|
||||
|
||||
// Seek to a specific time in seconds; returns 0 on success, <0 on error
|
||||
int VFSeekTime(VFFileRef vf, double time_seconds);
|
||||
|
||||
// Check if the stream is seekable; returns 1 if seekable, 0 if not
|
||||
int VFIsSeekable(VFFileRef vf);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // VORBIS_FILE_BRIDGE_H
|
||||
@@ -0,0 +1,4 @@
|
||||
module AudioCodecs {
|
||||
umbrella header "AudioCodecs.h"
|
||||
export *
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objectVersion = 60;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -15,16 +15,16 @@
|
||||
9806E8262BC5D2A900757370 /* Sidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9806E8252BC5D2A900757370 /* Sidebar.swift */; };
|
||||
9806E82A2BC68F8700757370 /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9806E8292BC68F8700757370 /* AudioPlayerView.swift */; };
|
||||
9806E8312BC6927D00757370 /* AudioPlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9806E8302BC6927D00757370 /* AudioPlayerModel.swift */; };
|
||||
9816A8A52BC7D8A200AD1299 /* AudioStreaming.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9816A8A42BC7D8A200AD1299 /* AudioStreaming.framework */; };
|
||||
9816A8A62BC7D8A200AD1299 /* AudioStreaming.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9816A8A42BC7D8A200AD1299 /* AudioStreaming.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
9816A8AA2BC7F4F000AD1299 /* AudioTrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9816A8A92BC7F4F000AD1299 /* AudioTrack.swift */; };
|
||||
9816A8AC2BC820DF00AD1299 /* AudioContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9816A8AB2BC820DF00AD1299 /* AudioContent.swift */; };
|
||||
9816A8B12BC8330C00AD1299 /* bensound-jazzyfrenchy.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 9816A8AD2BC832DB00AD1299 /* bensound-jazzyfrenchy.mp3 */; };
|
||||
9816A8B22BC8330C00AD1299 /* bensound-jazzyfrenchy.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 9816A8AE2BC832DB00AD1299 /* bensound-jazzyfrenchy.m4a */; };
|
||||
9816A8B32BC8330C00AD1299 /* hipjazz.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9816A8AF2BC832DC00AD1299 /* hipjazz.wav */; };
|
||||
9816A8BB2BC87BC200AD1299 /* AudioPlayerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9816A8BA2BC87BC200AD1299 /* AudioPlayerService.swift */; };
|
||||
981DA0762EAD61A90062223D /* AudioStreaming in Frameworks */ = {isa = PBXBuildFile; productRef = 981DA0752EAD61A90062223D /* AudioStreaming */; };
|
||||
984DE9552BDAE59C004B427A /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 984DE9542BDAE59C004B427A /* Notifier.swift */; };
|
||||
984DE9572BDAFC7E004B427A /* AudioPlayerControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 984DE9562BDAFC7E004B427A /* AudioPlayerControlsView.swift */; };
|
||||
9881693E2EBCDB0100CE7EFF /* hipjazz.ogg in Resources */ = {isa = PBXBuildFile; fileRef = 9881693D2EBCDB0100CE7EFF /* hipjazz.ogg */; };
|
||||
989E08E72BF7A4E300599F17 /* PrefersTabNavigationEnvironmentKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 989E08E62BF7A4E300599F17 /* PrefersTabNavigationEnvironmentKey.swift */; };
|
||||
98BFB41A2BC97AF800E812C0 /* DisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFB4192BC97AF800E812C0 /* DisplayLink.swift */; };
|
||||
98BFB41D2BCD7BB800E812C0 /* EqualizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFB41C2BCD7BB800E812C0 /* EqualizerView.swift */; };
|
||||
@@ -33,20 +33,6 @@
|
||||
98E6119C2BC72C0E0036BC47 /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E6119B2BC72C0E0036BC47 /* DetailView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9816A8A72BC7D8A200AD1299 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
9816A8A62BC7D8A200AD1299 /* AudioStreaming.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
42BE42F42C9322AA00C0E448 /* CustomStreamSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomStreamSource.swift; sourceTree = "<group>"; };
|
||||
9806E8142BC5D12500757370 /* AudioPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AudioPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -66,6 +52,7 @@
|
||||
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>"; };
|
||||
984DE9562BDAFC7E004B427A /* AudioPlayerControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerControlsView.swift; sourceTree = "<group>"; };
|
||||
9881693D2EBCDB0100CE7EFF /* hipjazz.ogg */ = {isa = PBXFileReference; lastKnownFileType = file; path = hipjazz.ogg; 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>"; };
|
||||
98BFB41B2BCAAD8A00E812C0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -80,7 +67,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9816A8A52BC7D8A200AD1299 /* AudioStreaming.framework in Frameworks */,
|
||||
981DA0762EAD61A90062223D /* AudioStreaming in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -168,6 +155,7 @@
|
||||
9816A8B02BC832E100AD1299 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9881693D2EBCDB0100CE7EFF /* hipjazz.ogg */,
|
||||
9816A8AE2BC832DB00AD1299 /* bensound-jazzyfrenchy.m4a */,
|
||||
9816A8AF2BC832DC00AD1299 /* hipjazz.wav */,
|
||||
9816A8AD2BC832DB00AD1299 /* bensound-jazzyfrenchy.mp3 */,
|
||||
@@ -216,7 +204,6 @@
|
||||
9806E8102BC5D12500757370 /* Sources */,
|
||||
9806E8112BC5D12500757370 /* Frameworks */,
|
||||
9806E8122BC5D12500757370 /* Resources */,
|
||||
9816A8A72BC7D8A200AD1299 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -251,6 +238,9 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 9806E80B2BC5D12500757370;
|
||||
packageReferences = (
|
||||
981DA0742EAD61A90062223D /* XCLocalSwiftPackageReference "../../AudioStreaming" */,
|
||||
);
|
||||
productRefGroup = 9806E8152BC5D12500757370 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -269,6 +259,7 @@
|
||||
9806E81C2BC5D12700757370 /* Assets.xcassets in Resources */,
|
||||
9816A8B12BC8330C00AD1299 /* bensound-jazzyfrenchy.mp3 in Resources */,
|
||||
9816A8B22BC8330C00AD1299 /* bensound-jazzyfrenchy.m4a in Resources */,
|
||||
9881693E2EBCDB0100CE7EFF /* hipjazz.ogg in Resources */,
|
||||
9816A8B32BC8330C00AD1299 /* hipjazz.wav in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -444,6 +435,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioPlayer;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -477,6 +469,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioPlayer;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -510,6 +503,20 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
981DA0742EAD61A90062223D /* XCLocalSwiftPackageReference "../../AudioStreaming" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = ../../AudioStreaming;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
981DA0752EAD61A90062223D /* AudioStreaming */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = AudioStreaming;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 9806E80C2BC5D12500757370 /* Project object */;
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"originHash" : "2be026a121d718059bac101ee8cabdd866a56e3b58b2908f27213c8a08755a25",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "ogg-binary-xcframework",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sbooth/ogg-binary-xcframework",
|
||||
"state" : {
|
||||
"revision" : "c0e822e18738ad913864e98d9614927ac1e9337c",
|
||||
"version" : "0.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "vorbis-binary-xcframework",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sbooth/vorbis-binary-xcframework",
|
||||
"state" : {
|
||||
"revision" : "842020eabcebe410e698c68545d6597b2d232e51",
|
||||
"version" : "0.1.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -20,6 +20,8 @@ enum AudioContent {
|
||||
case local
|
||||
case localWave
|
||||
case loopBeatFlac
|
||||
case oggVorbis
|
||||
case oggVorbisLocal
|
||||
case custom(String)
|
||||
|
||||
var title: String {
|
||||
@@ -45,13 +47,17 @@ enum AudioContent {
|
||||
case .local:
|
||||
return "Jazzy Frenchy"
|
||||
case .localWave:
|
||||
return "Local file"
|
||||
return "Hip Jazz"
|
||||
case .optimized:
|
||||
return "Jazzy Frenchy"
|
||||
case .nonOptimized:
|
||||
return "Jazzy Frenchy"
|
||||
case .loopBeatFlac:
|
||||
return "Beat loop"
|
||||
case .oggVorbis:
|
||||
return "Jazzy Fetchy"
|
||||
case .oggVorbisLocal:
|
||||
return "Hip Jazz"
|
||||
case .custom(let url):
|
||||
return url
|
||||
}
|
||||
@@ -76,7 +82,7 @@ enum AudioContent {
|
||||
case .piano:
|
||||
return "Remote mp3"
|
||||
case .remoteWave:
|
||||
return "wave"
|
||||
return "Local wav"
|
||||
case .local:
|
||||
return "Music by: bensound.com"
|
||||
case .localWave:
|
||||
@@ -87,6 +93,10 @@ enum AudioContent {
|
||||
return "Music by: bensound.com - m4a non-optimized"
|
||||
case .loopBeatFlac:
|
||||
return "Remote flac"
|
||||
case .oggVorbis:
|
||||
return "Remote Ogg Vorbis"
|
||||
case .oggVorbisLocal:
|
||||
return "Local Ogg Vorbis"
|
||||
case .custom:
|
||||
return ""
|
||||
}
|
||||
@@ -124,6 +134,11 @@ enum AudioContent {
|
||||
return URL(string: "https://github.com/dimitris-c/sample-audio/raw/main/5-MB-WAV.wav")!
|
||||
case .loopBeatFlac:
|
||||
return URL(string: "https://github.com/dimitris-c/sample-audio/raw/main/drumbeat-loop.flac")!
|
||||
case .oggVorbis:
|
||||
return URL(string: "https://github.com/dimitris-c/sample-audio/raw/refs/heads/main/bensound-jazzyfrenchy.ogg")!
|
||||
case .oggVorbisLocal:
|
||||
let path = Bundle.main.path(forResource: "hipjazz", ofType: "ogg")!
|
||||
return URL(fileURLWithPath: path)
|
||||
case .custom(let url):
|
||||
return URL(string: url)!
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class AudioPlayerModel {
|
||||
}
|
||||
|
||||
private let radioTracks: [AudioContent] = [.offradio, .enlefko, .pepper966, .kosmos, .kosmosJazz, .radiox]
|
||||
private let audioTracks: [AudioContent] = [.khruangbin, .piano, .optimized, .nonOptimized, .remoteWave, .local, .localWave, .loopBeatFlac]
|
||||
private let audioTracks: [AudioContent] = [.khruangbin, .piano, .optimized, .nonOptimized, .remoteWave, .local, .localWave, .loopBeatFlac, .oggVorbis, .oggVorbisLocal]
|
||||
private let customStreams: [AudioContent] = [.custom("custom://sinwave")]
|
||||
|
||||
func audioTracksProvider() -> [AudioPlaylist] {
|
||||
|
||||
@@ -180,7 +180,7 @@ extension AudioPlayerService: AudioPlayerDelegate {
|
||||
func audioPlayerDidFinishBuffering(player _: AudioPlayer, with _: AudioEntryId) {}
|
||||
|
||||
func audioPlayerStateChanged(player _: AudioPlayer, with newState: AudioPlayerState, previous _: AudioPlayerState) {
|
||||
print("audioPlayerDidStartPlaying newState: \(newState)")
|
||||
print("audioPlayerStateChanged newState: \(newState)")
|
||||
Task { await statusChangedNotifier.send(newState) }
|
||||
delegate?.statusChanged(status: newState)
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,971 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 55;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
98ABF69E2BAB07A20059C441 /* Mp4Restructure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98ABF69D2BAB07A20059C441 /* Mp4Restructure.swift */; };
|
||||
98C82AE62B8CA8BC00AED485 /* RemoteMp4Restructure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98C82AE52B8CA8BC00AED485 /* RemoteMp4Restructure.swift */; };
|
||||
98CC396E28BD651E006C9FF9 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CC396D28BD651E006C9FF9 /* Atomic.swift */; };
|
||||
98DC00CC2B961F5E0068900A /* ByteBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98DC00CB2B961F5E0068900A /* ByteBuffer.swift */; };
|
||||
98DC00CE2B9726380068900A /* ByteBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98DC00CD2B9726380068900A /* ByteBufferTests.swift */; };
|
||||
B500732024D00BAC00BB4475 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500731F24D00BAC00BB4475 /* Logger.swift */; };
|
||||
B514657F248E3884005C03F7 /* DispatchTimerSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514657E248E3884005C03F7 /* DispatchTimerSource.swift */; };
|
||||
B51B9F9A24DBE5BF00BDEAA2 /* AVAudioFormat+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51B9F9924DBE5BF00BDEAA2 /* AVAudioFormat+Convenience.swift */; };
|
||||
B51FE0C02488F67C00F2A4D2 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE0BF2488F67C00F2A4D2 /* Queue.swift */; };
|
||||
B51FE0C22488F96A00F2A4D2 /* QueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE0C12488F96A00F2A4D2 /* QueueTests.swift */; };
|
||||
B51FE0C624890CCB00F2A4D2 /* PlayerQueueEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE0C3248905B400F2A4D2 /* PlayerQueueEntries.swift */; };
|
||||
B51FE0C824892D1600F2A4D2 /* PlayerQueueEntriesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE0C724892D1600F2A4D2 /* PlayerQueueEntriesTest.swift */; };
|
||||
B5276B6F247D21A000D2F56A /* NetworkingClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5276B6E247D21A000D2F56A /* NetworkingClient.swift */; };
|
||||
B5276B74247D4D9F00D2F56A /* NetworkSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5276B73247D4D9F00D2F56A /* NetworkSessionDelegate.swift */; };
|
||||
B54C3E56255F286D00B356F2 /* Retrier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54C3E55255F286D00B356F2 /* Retrier.swift */; };
|
||||
B54D876D2490E4A000C361A0 /* UnitDescriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D876C2490E4A000C361A0 /* UnitDescriptions.swift */; };
|
||||
B54D876F2490E4DD00C361A0 /* AudioRendererContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D876E2490E4DD00C361A0 /* AudioRendererContext.swift */; };
|
||||
B55A736C247FCB420050C53D /* HTTPHeaderParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55A736B247FCB420050C53D /* HTTPHeaderParser.swift */; };
|
||||
B55CE96E248058B60001C498 /* MetadataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55CE96D248058B60001C498 /* MetadataParser.swift */; };
|
||||
B55CE97124810DE20001C498 /* MetadataStreamProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55CE97024810DE20001C498 /* MetadataStreamProcessor.swift */; };
|
||||
B55CE97824813BCA0001C498 /* UnsafeMutablePointer+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55CE97724813BCA0001C498 /* UnsafeMutablePointer+Helpers.swift */; };
|
||||
B55CEAB42485107C0001C498 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55CEAB32485107C0001C498 /* Parser.swift */; };
|
||||
B55CEAB82485172D0001C498 /* HTTPHeaderParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55CEAB72485172D0001C498 /* HTTPHeaderParserTests.swift */; };
|
||||
B55CEABA248530C00001C498 /* MetadataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55CEAB9248530C00001C498 /* MetadataParser.swift */; };
|
||||
B55CEABC24853CD20001C498 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55CEABB24853CD20001C498 /* AudioPlayer.swift */; };
|
||||
B55F77CF24D82ADE0057F431 /* AudioPlayerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55F77CE24D82ADE0057F431 /* AudioPlayerDelegate.swift */; };
|
||||
B55F77D124D82CD50057F431 /* AVAudioUnit+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55F77D024D82CD50057F431 /* AVAudioUnit+Convenience.swift */; };
|
||||
B55F77D624DACE140057F431 /* BufferContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55F77D524DACE140057F431 /* BufferContext.swift */; };
|
||||
B5667A902499018D00D93F85 /* AudioFileStreamProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5667A8F2499018D00D93F85 /* AudioFileStreamProcessor.swift */; };
|
||||
B5667A922499063D00D93F85 /* AudioPlayerContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5667A912499063D00D93F85 /* AudioPlayerContext.swift */; };
|
||||
B5667B3E249BC43100D93F85 /* AudioPlayerRenderProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5667B3D249BC43000D93F85 /* AudioPlayerRenderProcessor.swift */; };
|
||||
B57829CF2548B32B00C78D36 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57829CE2548B32B00C78D36 /* Lock.swift */; };
|
||||
B58386382544A2C10087A712 /* EntryFrames.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58386372544A2C10087A712 /* EntryFrames.swift */; };
|
||||
B5838640254584A50087A712 /* ProcessedPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = B583863F254584A50087A712 /* ProcessedPackets.swift */; };
|
||||
B5838644254584BE0087A712 /* AudioStreamState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5838643254584BE0087A712 /* AudioStreamState.swift */; };
|
||||
B5838648254584D90087A712 /* SeekRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5838647254584D90087A712 /* SeekRequest.swift */; };
|
||||
B592E1252545FF9A008866FB /* BiMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5276B71247D4D5B00D2F56A /* BiMap.swift */; };
|
||||
B592E12925460146008866FB /* BiMapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B592E12825460146008866FB /* BiMapTests.swift */; };
|
||||
B592E134254608B4008866FB /* DispatchTimerSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B592E133254608B4008866FB /* DispatchTimerSourceTests.swift */; };
|
||||
B59CB46C25420B4D00F8CAD0 /* MetadataStreamProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59CB46B25420B4D00F8CAD0 /* MetadataStreamProcessorTests.swift */; };
|
||||
B59CB4BB25421F3500F8CAD0 /* raw-stream-audio-normal-metadata in Resources */ = {isa = PBXBuildFile; fileRef = B59CB4BA25421F3500F8CAD0 /* raw-stream-audio-normal-metadata */; };
|
||||
B59CB4C225421F7A00F8CAD0 /* raw-stream-audio-empty-metadata in Resources */ = {isa = PBXBuildFile; fileRef = B59CB4B225421D8200F8CAD0 /* raw-stream-audio-empty-metadata */; };
|
||||
B59CB4C625421FD400F8CAD0 /* raw-stream-audio-no-metadata in Resources */ = {isa = PBXBuildFile; fileRef = B59CB4C525421FD400F8CAD0 /* raw-stream-audio-no-metadata */; };
|
||||
B59CB4CE2542204D00F8CAD0 /* raw-stream-audio-normal-metadata-alt in Resources */ = {isa = PBXBuildFile; fileRef = B59CB4CD2542204D00F8CAD0 /* raw-stream-audio-normal-metadata-alt */; };
|
||||
B59D0B6F255C904900D6CCE5 /* FileAudioSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59D0B6E255C904900D6CCE5 /* FileAudioSource.swift */; };
|
||||
B59DF10424916FD50043C498 /* DispatchQueue+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59DF10324916FD50043C498 /* DispatchQueue+Helpers.swift */; };
|
||||
B59DF1A32493E90C0043C498 /* AudioFileStream+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59DF1A22493E90C0043C498 /* AudioFileStream+Helpers.swift */; };
|
||||
B5AEDBB824744153007D8101 /* AudioStreaming.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5AEDBAE24744153007D8101 /* AudioStreaming.framework */; platformFilters = (ios, tvos, ); };
|
||||
B5AEDBBF24744153007D8101 /* AudioStreaming.h in Headers */ = {isa = PBXBuildFile; fileRef = B5AEDBB124744153007D8101 /* AudioStreaming.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B5B36E432655A32200DC96F5 /* FrameFilterProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B36E422655A32200DC96F5 /* FrameFilterProcessor.swift */; };
|
||||
B5B3B7CC248647ED00656828 /* AudioPlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B3B7CB248647ED00656828 /* AudioPlayerState.swift */; };
|
||||
B5D4A40925D9321400E1450C /* IcycastHeaderParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A40825D9321400E1450C /* IcycastHeaderParser.swift */; };
|
||||
B5D4A41025D948EF00E1450C /* IcycastHeadersProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A40B25D9445600E1450C /* IcycastHeadersProcessor.swift */; };
|
||||
B5D82E65255DD562009EDAA4 /* NetStatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D82E64255DD562009EDAA4 /* NetStatusService.swift */; };
|
||||
B5DB66E2255C2EAB00B8DF53 /* AudioEntryProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DB66E1255C2EAB00B8DF53 /* AudioEntryProvider.swift */; };
|
||||
B5E1DE2524B70B4200955BFB /* AudioPlayerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E1DE2424B70B4200955BFB /* AudioPlayerConfiguration.swift */; };
|
||||
B5EF954E247DA5AC003E8FF8 /* NetworkingClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EF954D247DA5AC003E8FF8 /* NetworkingClientTests.swift */; };
|
||||
B5EF9555247E9393003E8FF8 /* AudioEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EF9554247E9393003E8FF8 /* AudioEntry.swift */; };
|
||||
B5EF9557247E9439003E8FF8 /* AudioStreamSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EF9556247E9439003E8FF8 /* AudioStreamSource.swift */; };
|
||||
B5EF955B247EBCB3003E8FF8 /* AudioFileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EF955A247EBCB3003E8FF8 /* AudioFileType.swift */; };
|
||||
B5EF955D247ECBB1003E8FF8 /* RemoteAudioSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EF955C247ECBB1003E8FF8 /* RemoteAudioSource.swift */; };
|
||||
B5F883BA2477CEFC00D277C1 /* AtomicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F883B82477CBF600D277C1 /* AtomicTests.swift */; };
|
||||
B5F883C32477DC4400D277C1 /* NetworkDataStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F883C22477DC4400D277C1 /* NetworkDataStream.swift */; };
|
||||
B5FB6C0525516507002C0A37 /* AudioConverter+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FB6C0425516507002C0A37 /* AudioConverter+Helpers.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
B5AEDBB924744153007D8101 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B5AEDBA524744153007D8101 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = B5AEDBAD24744153007D8101;
|
||||
remoteInfo = AudioStreaming;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
B57A4F7D24AB4E6C00D7EA51 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
98ABF69D2BAB07A20059C441 /* Mp4Restructure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mp4Restructure.swift; sourceTree = "<group>"; };
|
||||
98C82AE52B8CA8BC00AED485 /* RemoteMp4Restructure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteMp4Restructure.swift; sourceTree = "<group>"; };
|
||||
98CC396D28BD651E006C9FF9 /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
|
||||
98DC00CB2B961F5E0068900A /* ByteBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ByteBuffer.swift; sourceTree = "<group>"; };
|
||||
98DC00CD2B9726380068900A /* ByteBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ByteBufferTests.swift; sourceTree = "<group>"; };
|
||||
B500731F24D00BAC00BB4475 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
B514657E248E3884005C03F7 /* DispatchTimerSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchTimerSource.swift; sourceTree = "<group>"; };
|
||||
B51B9F9924DBE5BF00BDEAA2 /* AVAudioFormat+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAudioFormat+Convenience.swift"; sourceTree = "<group>"; };
|
||||
B51FE0BF2488F67C00F2A4D2 /* Queue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = "<group>"; };
|
||||
B51FE0C12488F96A00F2A4D2 /* QueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTests.swift; sourceTree = "<group>"; };
|
||||
B51FE0C3248905B400F2A4D2 /* PlayerQueueEntries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueueEntries.swift; sourceTree = "<group>"; };
|
||||
B51FE0C724892D1600F2A4D2 /* PlayerQueueEntriesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueueEntriesTest.swift; sourceTree = "<group>"; };
|
||||
B5276B6E247D21A000D2F56A /* NetworkingClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingClient.swift; sourceTree = "<group>"; };
|
||||
B5276B71247D4D5B00D2F56A /* BiMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiMap.swift; sourceTree = "<group>"; };
|
||||
B5276B73247D4D9F00D2F56A /* NetworkSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSessionDelegate.swift; sourceTree = "<group>"; };
|
||||
B54C3E55255F286D00B356F2 /* Retrier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Retrier.swift; sourceTree = "<group>"; };
|
||||
B54D876C2490E4A000C361A0 /* UnitDescriptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitDescriptions.swift; sourceTree = "<group>"; };
|
||||
B54D876E2490E4DD00C361A0 /* AudioRendererContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRendererContext.swift; sourceTree = "<group>"; };
|
||||
B55A736B247FCB420050C53D /* HTTPHeaderParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeaderParser.swift; sourceTree = "<group>"; };
|
||||
B55CE96D248058B60001C498 /* MetadataParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataParser.swift; sourceTree = "<group>"; };
|
||||
B55CE97024810DE20001C498 /* MetadataStreamProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataStreamProcessor.swift; sourceTree = "<group>"; };
|
||||
B55CE97724813BCA0001C498 /* UnsafeMutablePointer+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnsafeMutablePointer+Helpers.swift"; sourceTree = "<group>"; };
|
||||
B55CEAB32485107C0001C498 /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
|
||||
B55CEAB72485172D0001C498 /* HTTPHeaderParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeaderParserTests.swift; sourceTree = "<group>"; };
|
||||
B55CEAB9248530C00001C498 /* MetadataParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataParser.swift; sourceTree = "<group>"; };
|
||||
B55CEABB24853CD20001C498 /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
|
||||
B55F77CE24D82ADE0057F431 /* AudioPlayerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerDelegate.swift; sourceTree = "<group>"; };
|
||||
B55F77D024D82CD50057F431 /* AVAudioUnit+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAudioUnit+Convenience.swift"; sourceTree = "<group>"; };
|
||||
B55F77D524DACE140057F431 /* BufferContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BufferContext.swift; sourceTree = "<group>"; };
|
||||
B5667A8F2499018D00D93F85 /* AudioFileStreamProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioFileStreamProcessor.swift; sourceTree = "<group>"; };
|
||||
B5667A912499063D00D93F85 /* AudioPlayerContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerContext.swift; sourceTree = "<group>"; };
|
||||
B5667B3D249BC43000D93F85 /* AudioPlayerRenderProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerRenderProcessor.swift; sourceTree = "<group>"; };
|
||||
B57829CE2548B32B00C78D36 /* Lock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = "<group>"; };
|
||||
B580CB1D25628CF4006D7DD8 /* AudioStreaming.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = AudioStreaming.podspec; sourceTree = "<group>"; };
|
||||
B580CB1E25628CF4006D7DD8 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
B580CB1F25628D09006D7DD8 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
||||
B58386372544A2C10087A712 /* EntryFrames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFrames.swift; sourceTree = "<group>"; };
|
||||
B583863F254584A50087A712 /* ProcessedPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessedPackets.swift; sourceTree = "<group>"; };
|
||||
B5838643254584BE0087A712 /* AudioStreamState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioStreamState.swift; sourceTree = "<group>"; };
|
||||
B5838647254584D90087A712 /* SeekRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeekRequest.swift; sourceTree = "<group>"; };
|
||||
B592E12825460146008866FB /* BiMapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiMapTests.swift; sourceTree = "<group>"; };
|
||||
B592E133254608B4008866FB /* DispatchTimerSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchTimerSourceTests.swift; sourceTree = "<group>"; };
|
||||
B59CB46B25420B4D00F8CAD0 /* MetadataStreamProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataStreamProcessorTests.swift; sourceTree = "<group>"; };
|
||||
B59CB4B225421D8200F8CAD0 /* raw-stream-audio-empty-metadata */ = {isa = PBXFileReference; lastKnownFileType = file; path = "raw-stream-audio-empty-metadata"; sourceTree = "<group>"; };
|
||||
B59CB4BA25421F3500F8CAD0 /* raw-stream-audio-normal-metadata */ = {isa = PBXFileReference; lastKnownFileType = file; path = "raw-stream-audio-normal-metadata"; sourceTree = "<group>"; };
|
||||
B59CB4C525421FD400F8CAD0 /* raw-stream-audio-no-metadata */ = {isa = PBXFileReference; lastKnownFileType = file; path = "raw-stream-audio-no-metadata"; sourceTree = "<group>"; };
|
||||
B59CB4CD2542204D00F8CAD0 /* raw-stream-audio-normal-metadata-alt */ = {isa = PBXFileReference; lastKnownFileType = file; path = "raw-stream-audio-normal-metadata-alt"; sourceTree = "<group>"; };
|
||||
B59D0B6E255C904900D6CCE5 /* FileAudioSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileAudioSource.swift; sourceTree = "<group>"; };
|
||||
B59DF10324916FD50043C498 /* DispatchQueue+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Helpers.swift"; sourceTree = "<group>"; };
|
||||
B59DF1A22493E90C0043C498 /* AudioFileStream+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AudioFileStream+Helpers.swift"; sourceTree = "<group>"; };
|
||||
B5AEDBAE24744153007D8101 /* AudioStreaming.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AudioStreaming.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5AEDBB124744153007D8101 /* AudioStreaming.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioStreaming.h; sourceTree = "<group>"; };
|
||||
B5AEDBB224744153007D8101 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B5AEDBB724744153007D8101 /* AudioStreamingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AudioStreamingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5AEDBBE24744153007D8101 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B5B36E422655A32200DC96F5 /* FrameFilterProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameFilterProcessor.swift; sourceTree = "<group>"; };
|
||||
B5B3B7CB248647ED00656828 /* AudioPlayerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerState.swift; sourceTree = "<group>"; };
|
||||
B5D4A40825D9321400E1450C /* IcycastHeaderParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IcycastHeaderParser.swift; sourceTree = "<group>"; };
|
||||
B5D4A40B25D9445600E1450C /* IcycastHeadersProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IcycastHeadersProcessor.swift; sourceTree = "<group>"; };
|
||||
B5D82E64255DD562009EDAA4 /* NetStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetStatusService.swift; sourceTree = "<group>"; };
|
||||
B5DB66DA255C079C00B8DF53 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
B5DB66E1255C2EAB00B8DF53 /* AudioEntryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEntryProvider.swift; sourceTree = "<group>"; };
|
||||
B5E1DE2424B70B4200955BFB /* AudioPlayerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerConfiguration.swift; sourceTree = "<group>"; };
|
||||
B5EF954D247DA5AC003E8FF8 /* NetworkingClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingClientTests.swift; sourceTree = "<group>"; };
|
||||
B5EF9554247E9393003E8FF8 /* AudioEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEntry.swift; sourceTree = "<group>"; };
|
||||
B5EF9556247E9439003E8FF8 /* AudioStreamSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioStreamSource.swift; sourceTree = "<group>"; };
|
||||
B5EF955A247EBCB3003E8FF8 /* AudioFileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioFileType.swift; sourceTree = "<group>"; };
|
||||
B5EF955C247ECBB1003E8FF8 /* RemoteAudioSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteAudioSource.swift; sourceTree = "<group>"; };
|
||||
B5F883B82477CBF600D277C1 /* AtomicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicTests.swift; sourceTree = "<group>"; };
|
||||
B5F883C22477DC4400D277C1 /* NetworkDataStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDataStream.swift; sourceTree = "<group>"; };
|
||||
B5FB6C0425516507002C0A37 /* AudioConverter+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AudioConverter+Helpers.swift"; sourceTree = "<group>"; };
|
||||
B5FFF5FD2549FA02006BBB7C /* AudioExample.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = AudioExample.xctestplan; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
B5AEDBAB24744153007D8101 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B5AEDBB424744153007D8101 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5AEDBB824744153007D8101 /* AudioStreaming.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
98C82AE42B8CA8AA00AED485 /* Mp4 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
98C82AE52B8CA8BC00AED485 /* RemoteMp4Restructure.swift */,
|
||||
98ABF69D2BAB07A20059C441 /* Mp4Restructure.swift */,
|
||||
);
|
||||
path = Mp4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5276B70247D4D3D00D2F56A /* Network */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5D82E64255DD562009EDAA4 /* NetStatusService.swift */,
|
||||
B5276B6E247D21A000D2F56A /* NetworkingClient.swift */,
|
||||
B5276B73247D4D9F00D2F56A /* NetworkSessionDelegate.swift */,
|
||||
B5F883C22477DC4400D277C1 /* NetworkDataStream.swift */,
|
||||
);
|
||||
path = Network;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B55A7369247FCB160050C53D /* Audio Entry */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B58386362544A2A60087A712 /* Models */,
|
||||
B5DB66E1255C2EAB00B8DF53 /* AudioEntryProvider.swift */,
|
||||
B5EF9554247E9393003E8FF8 /* AudioEntry.swift */,
|
||||
);
|
||||
path = "Audio Entry";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B55A736A247FCB310050C53D /* Parsers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B55CEAB32485107C0001C498 /* Parser.swift */,
|
||||
B55A736B247FCB420050C53D /* HTTPHeaderParser.swift */,
|
||||
B55CE96D248058B60001C498 /* MetadataParser.swift */,
|
||||
B5D4A40825D9321400E1450C /* IcycastHeaderParser.swift */,
|
||||
);
|
||||
path = Parsers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B55CE97624813BA10001C498 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5FB6C0425516507002C0A37 /* AudioConverter+Helpers.swift */,
|
||||
B59DF1A22493E90C0043C498 /* AudioFileStream+Helpers.swift */,
|
||||
B55CE97724813BCA0001C498 /* UnsafeMutablePointer+Helpers.swift */,
|
||||
B59DF10324916FD50043C498 /* DispatchQueue+Helpers.swift */,
|
||||
B55F77D024D82CD50057F431 /* AVAudioUnit+Convenience.swift */,
|
||||
B51B9F9924DBE5BF00BDEAA2 /* AVAudioFormat+Convenience.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B55CEAB5248517170001C498 /* Streaming */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B59CB4B125421D8200F8CAD0 /* Metadata Stream Processor */,
|
||||
B55CEAB62485171E0001C498 /* Parsers */,
|
||||
B51FE0C724892D1600F2A4D2 /* PlayerQueueEntriesTest.swift */,
|
||||
);
|
||||
path = Streaming;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B55CEAB62485171E0001C498 /* Parsers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B55CEAB72485172D0001C498 /* HTTPHeaderParserTests.swift */,
|
||||
B55CEAB9248530C00001C498 /* MetadataParser.swift */,
|
||||
);
|
||||
path = Parsers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B55CEABF24855A900001C498 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B51FE0C3248905B400F2A4D2 /* PlayerQueueEntries.swift */,
|
||||
B5EF955A247EBCB3003E8FF8 /* AudioFileType.swift */,
|
||||
B55F77D524DACE140057F431 /* BufferContext.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B55CEAC024855AA20001C498 /* Processors */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5B36E422655A32200DC96F5 /* FrameFilterProcessor.swift */,
|
||||
B5667A8F2499018D00D93F85 /* AudioFileStreamProcessor.swift */,
|
||||
B5667B3D249BC43000D93F85 /* AudioPlayerRenderProcessor.swift */,
|
||||
B55CE97024810DE20001C498 /* MetadataStreamProcessor.swift */,
|
||||
B5D4A40B25D9445600E1450C /* IcycastHeadersProcessor.swift */,
|
||||
);
|
||||
path = Processors;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B57A4F7A24AB4E6C00D7EA51 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5DB66DA255C079C00B8DF53 /* AVFoundation.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B580CB1C25628CE4006D7DD8 /* Deployment */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B580CB1D25628CF4006D7DD8 /* AudioStreaming.podspec */,
|
||||
B580CB1E25628CF4006D7DD8 /* LICENSE */,
|
||||
B580CB1F25628D09006D7DD8 /* Package.swift */,
|
||||
);
|
||||
name = Deployment;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B58386362544A2A60087A712 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B58386372544A2C10087A712 /* EntryFrames.swift */,
|
||||
B583863F254584A50087A712 /* ProcessedPackets.swift */,
|
||||
B5838643254584BE0087A712 /* AudioStreamState.swift */,
|
||||
B5838647254584D90087A712 /* SeekRequest.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B58BD7FC255DB653005B756D /* Audio Source */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
98C82AE42B8CA8AA00AED485 /* Mp4 */,
|
||||
B5EF9556247E9439003E8FF8 /* AudioStreamSource.swift */,
|
||||
B5EF955C247ECBB1003E8FF8 /* RemoteAudioSource.swift */,
|
||||
B59D0B6E255C904900D6CCE5 /* FileAudioSource.swift */,
|
||||
);
|
||||
path = "Audio Source";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B592E11E2545FF33008866FB /* Structures */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5276B71247D4D5B00D2F56A /* BiMap.swift */,
|
||||
B51FE0BF2488F67C00F2A4D2 /* Queue.swift */,
|
||||
);
|
||||
path = Structures;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B592E13025460883008866FB /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
98DC00CB2B961F5E0068900A /* ByteBuffer.swift */,
|
||||
98CC396D28BD651E006C9FF9 /* Atomic.swift */,
|
||||
B514657E248E3884005C03F7 /* DispatchTimerSource.swift */,
|
||||
B57829CE2548B32B00C78D36 /* Lock.swift */,
|
||||
B500731F24D00BAC00BB4475 /* Logger.swift */,
|
||||
B54C3E55255F286D00B356F2 /* Retrier.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B59CB4B125421D8200F8CAD0 /* Metadata Stream Processor */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B59CB4B525421D8D00F8CAD0 /* raw-audio-streams */,
|
||||
B59CB46B25420B4D00F8CAD0 /* MetadataStreamProcessorTests.swift */,
|
||||
);
|
||||
path = "Metadata Stream Processor";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B59CB4B525421D8D00F8CAD0 /* raw-audio-streams */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B59CB4CD2542204D00F8CAD0 /* raw-stream-audio-normal-metadata-alt */,
|
||||
B59CB4C525421FD400F8CAD0 /* raw-stream-audio-no-metadata */,
|
||||
B59CB4BA25421F3500F8CAD0 /* raw-stream-audio-normal-metadata */,
|
||||
B59CB4B225421D8200F8CAD0 /* raw-stream-audio-empty-metadata */,
|
||||
);
|
||||
path = "raw-audio-streams";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5AEDBA424744153007D8101 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B580CB1C25628CE4006D7DD8 /* Deployment */,
|
||||
B5AEDBB024744153007D8101 /* AudioStreaming */,
|
||||
B5AEDBBB24744153007D8101 /* AudioStreamingTests */,
|
||||
B5AEDBAF24744153007D8101 /* Products */,
|
||||
B57A4F7A24AB4E6C00D7EA51 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5AEDBAF24744153007D8101 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5AEDBAE24744153007D8101 /* AudioStreaming.framework */,
|
||||
B5AEDBB724744153007D8101 /* AudioStreamingTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5AEDBB024744153007D8101 /* AudioStreaming */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5F883B42476DABE00D277C1 /* Core */,
|
||||
B5EF9553247E9235003E8FF8 /* Streaming */,
|
||||
B5AEDBB124744153007D8101 /* AudioStreaming.h */,
|
||||
B5AEDBB224744153007D8101 /* Info.plist */,
|
||||
);
|
||||
path = AudioStreaming;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5AEDBBB24744153007D8101 /* AudioStreamingTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5FFF5FD2549FA02006BBB7C /* AudioExample.xctestplan */,
|
||||
B5F883B72477CBC900D277C1 /* Core */,
|
||||
B55CEAB5248517170001C498 /* Streaming */,
|
||||
B5AEDBBE24744153007D8101 /* Info.plist */,
|
||||
);
|
||||
path = AudioStreamingTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5E1DE2924B7179E00955BFB /* AudioPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B54D876C2490E4A000C361A0 /* UnitDescriptions.swift */,
|
||||
B5B3B7CB248647ED00656828 /* AudioPlayerState.swift */,
|
||||
B5E1DE2424B70B4200955BFB /* AudioPlayerConfiguration.swift */,
|
||||
B55F77CE24D82ADE0057F431 /* AudioPlayerDelegate.swift */,
|
||||
B55CEABB24853CD20001C498 /* AudioPlayer.swift */,
|
||||
B5667A912499063D00D93F85 /* AudioPlayerContext.swift */,
|
||||
B54D876E2490E4DD00C361A0 /* AudioRendererContext.swift */,
|
||||
B55CEAC024855AA20001C498 /* Processors */,
|
||||
);
|
||||
path = AudioPlayer;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5EF954A247DA450003E8FF8 /* Network */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5EF954D247DA5AC003E8FF8 /* NetworkingClientTests.swift */,
|
||||
);
|
||||
path = Network;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5EF9553247E9235003E8FF8 /* Streaming */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5E1DE2924B7179E00955BFB /* AudioPlayer */,
|
||||
B58BD7FC255DB653005B756D /* Audio Source */,
|
||||
B55A7369247FCB160050C53D /* Audio Entry */,
|
||||
B55CEABF24855A900001C498 /* Helpers */,
|
||||
B55A736A247FCB310050C53D /* Parsers */,
|
||||
);
|
||||
path = Streaming;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5F883B42476DABE00D277C1 /* Core */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B55CE97624813BA10001C498 /* Extensions */,
|
||||
B592E11E2545FF33008866FB /* Structures */,
|
||||
B5276B70247D4D3D00D2F56A /* Network */,
|
||||
B592E13025460883008866FB /* Helpers */,
|
||||
);
|
||||
path = Core;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5F883B72477CBC900D277C1 /* Core */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5EF954A247DA450003E8FF8 /* Network */,
|
||||
B5F883B82477CBF600D277C1 /* AtomicTests.swift */,
|
||||
B51FE0C12488F96A00F2A4D2 /* QueueTests.swift */,
|
||||
B592E12825460146008866FB /* BiMapTests.swift */,
|
||||
B592E133254608B4008866FB /* DispatchTimerSourceTests.swift */,
|
||||
98DC00CD2B9726380068900A /* ByteBufferTests.swift */,
|
||||
);
|
||||
path = Core;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
B5AEDBA924744153007D8101 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5AEDBBF24744153007D8101 /* AudioStreaming.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
B5AEDBAD24744153007D8101 /* AudioStreaming */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = B5AEDBC224744153007D8101 /* Build configuration list for PBXNativeTarget "AudioStreaming" */;
|
||||
buildPhases = (
|
||||
B5AEDBA924744153007D8101 /* Headers */,
|
||||
B5AEDBAA24744153007D8101 /* Sources */,
|
||||
B5AEDBAB24744153007D8101 /* Frameworks */,
|
||||
B5AEDBAC24744153007D8101 /* Resources */,
|
||||
B57A4F7D24AB4E6C00D7EA51 /* Embed Frameworks */,
|
||||
B583864B2545858E0087A712 /* SwiftLint */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = AudioStreaming;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = AudioStreaming;
|
||||
productReference = B5AEDBAE24744153007D8101 /* AudioStreaming.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
B5AEDBB624744153007D8101 /* AudioStreamingTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = B5AEDBC524744153007D8101 /* Build configuration list for PBXNativeTarget "AudioStreamingTests" */;
|
||||
buildPhases = (
|
||||
B5AEDBB324744153007D8101 /* Sources */,
|
||||
B5AEDBB424744153007D8101 /* Frameworks */,
|
||||
B5AEDBB524744153007D8101 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
B5AEDBBA24744153007D8101 /* PBXTargetDependency */,
|
||||
);
|
||||
name = AudioStreamingTests;
|
||||
productName = AudioStreamingTests;
|
||||
productReference = B5AEDBB724744153007D8101 /* AudioStreamingTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
B5AEDBA524744153007D8101 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 1140;
|
||||
LastUpgradeCheck = 1620;
|
||||
ORGANIZATIONNAME = Decimal;
|
||||
TargetAttributes = {
|
||||
B5AEDBAD24744153007D8101 = {
|
||||
CreatedOnToolsVersion = 11.4;
|
||||
LastSwiftMigration = 1200;
|
||||
};
|
||||
B5AEDBB624744153007D8101 = {
|
||||
CreatedOnToolsVersion = 11.4;
|
||||
LastSwiftMigration = 1140;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = B5AEDBA824744153007D8101 /* Build configuration list for PBXProject "AudioStreaming" */;
|
||||
compatibilityVersion = "Xcode 11.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = B5AEDBA424744153007D8101;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = B5AEDBAF24744153007D8101 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
B5AEDBAD24744153007D8101 /* AudioStreaming */,
|
||||
B5AEDBB624744153007D8101 /* AudioStreamingTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
B5AEDBAC24744153007D8101 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B5AEDBB524744153007D8101 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B59CB4BB25421F3500F8CAD0 /* raw-stream-audio-normal-metadata in Resources */,
|
||||
B59CB4C225421F7A00F8CAD0 /* raw-stream-audio-empty-metadata in Resources */,
|
||||
B59CB4C625421FD400F8CAD0 /* raw-stream-audio-no-metadata in Resources */,
|
||||
B59CB4CE2542204D00F8CAD0 /* raw-stream-audio-normal-metadata-alt in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
B583864B2545858E0087A712 /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = SwiftLint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
B5AEDBAA24744153007D8101 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B58386382544A2C10087A712 /* EntryFrames.swift in Sources */,
|
||||
B5EF955D247ECBB1003E8FF8 /* RemoteAudioSource.swift in Sources */,
|
||||
B57829CF2548B32B00C78D36 /* Lock.swift in Sources */,
|
||||
B5838640254584A50087A712 /* ProcessedPackets.swift in Sources */,
|
||||
B54C3E56255F286D00B356F2 /* Retrier.swift in Sources */,
|
||||
B59DF10424916FD50043C498 /* DispatchQueue+Helpers.swift in Sources */,
|
||||
98CC396E28BD651E006C9FF9 /* Atomic.swift in Sources */,
|
||||
B5B3B7CC248647ED00656828 /* AudioPlayerState.swift in Sources */,
|
||||
B51B9F9A24DBE5BF00BDEAA2 /* AVAudioFormat+Convenience.swift in Sources */,
|
||||
B51FE0C624890CCB00F2A4D2 /* PlayerQueueEntries.swift in Sources */,
|
||||
B5EF9557247E9439003E8FF8 /* AudioStreamSource.swift in Sources */,
|
||||
B5D4A40925D9321400E1450C /* IcycastHeaderParser.swift in Sources */,
|
||||
B59DF1A32493E90C0043C498 /* AudioFileStream+Helpers.swift in Sources */,
|
||||
B54D876D2490E4A000C361A0 /* UnitDescriptions.swift in Sources */,
|
||||
B514657F248E3884005C03F7 /* DispatchTimerSource.swift in Sources */,
|
||||
B55CEABC24853CD20001C498 /* AudioPlayer.swift in Sources */,
|
||||
B5667B3E249BC43100D93F85 /* AudioPlayerRenderProcessor.swift in Sources */,
|
||||
B5276B6F247D21A000D2F56A /* NetworkingClient.swift in Sources */,
|
||||
B5EF955B247EBCB3003E8FF8 /* AudioFileType.swift in Sources */,
|
||||
B592E1252545FF9A008866FB /* BiMap.swift in Sources */,
|
||||
B5DB66E2255C2EAB00B8DF53 /* AudioEntryProvider.swift in Sources */,
|
||||
B5D4A41025D948EF00E1450C /* IcycastHeadersProcessor.swift in Sources */,
|
||||
B5667A902499018D00D93F85 /* AudioFileStreamProcessor.swift in Sources */,
|
||||
B59D0B6F255C904900D6CCE5 /* FileAudioSource.swift in Sources */,
|
||||
98DC00CC2B961F5E0068900A /* ByteBuffer.swift in Sources */,
|
||||
B5EF9555247E9393003E8FF8 /* AudioEntry.swift in Sources */,
|
||||
B5B36E432655A32200DC96F5 /* FrameFilterProcessor.swift in Sources */,
|
||||
B51FE0C02488F67C00F2A4D2 /* Queue.swift in Sources */,
|
||||
B5667A922499063D00D93F85 /* AudioPlayerContext.swift in Sources */,
|
||||
B55CE97124810DE20001C498 /* MetadataStreamProcessor.swift in Sources */,
|
||||
B55CEAB42485107C0001C498 /* Parser.swift in Sources */,
|
||||
B5FB6C0525516507002C0A37 /* AudioConverter+Helpers.swift in Sources */,
|
||||
B5E1DE2524B70B4200955BFB /* AudioPlayerConfiguration.swift in Sources */,
|
||||
B5F883C32477DC4400D277C1 /* NetworkDataStream.swift in Sources */,
|
||||
B54D876F2490E4DD00C361A0 /* AudioRendererContext.swift in Sources */,
|
||||
B55F77CF24D82ADE0057F431 /* AudioPlayerDelegate.swift in Sources */,
|
||||
B55A736C247FCB420050C53D /* HTTPHeaderParser.swift in Sources */,
|
||||
B55F77D124D82CD50057F431 /* AVAudioUnit+Convenience.swift in Sources */,
|
||||
B55CE96E248058B60001C498 /* MetadataParser.swift in Sources */,
|
||||
B5838644254584BE0087A712 /* AudioStreamState.swift in Sources */,
|
||||
B500732024D00BAC00BB4475 /* Logger.swift in Sources */,
|
||||
98C82AE62B8CA8BC00AED485 /* RemoteMp4Restructure.swift in Sources */,
|
||||
B5276B74247D4D9F00D2F56A /* NetworkSessionDelegate.swift in Sources */,
|
||||
B55F77D624DACE140057F431 /* BufferContext.swift in Sources */,
|
||||
B5838648254584D90087A712 /* SeekRequest.swift in Sources */,
|
||||
B5D82E65255DD562009EDAA4 /* NetStatusService.swift in Sources */,
|
||||
B55CE97824813BCA0001C498 /* UnsafeMutablePointer+Helpers.swift in Sources */,
|
||||
98ABF69E2BAB07A20059C441 /* Mp4Restructure.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B5AEDBB324744153007D8101 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5EF954E247DA5AC003E8FF8 /* NetworkingClientTests.swift in Sources */,
|
||||
B59CB46C25420B4D00F8CAD0 /* MetadataStreamProcessorTests.swift in Sources */,
|
||||
B51FE0C824892D1600F2A4D2 /* PlayerQueueEntriesTest.swift in Sources */,
|
||||
B55CEABA248530C00001C498 /* MetadataParser.swift in Sources */,
|
||||
B51FE0C22488F96A00F2A4D2 /* QueueTests.swift in Sources */,
|
||||
B5F883BA2477CEFC00D277C1 /* AtomicTests.swift in Sources */,
|
||||
B592E134254608B4008866FB /* DispatchTimerSourceTests.swift in Sources */,
|
||||
B55CEAB82485172D0001C498 /* HTTPHeaderParserTests.swift in Sources */,
|
||||
B592E12925460146008866FB /* BiMapTests.swift in Sources */,
|
||||
98DC00CE2B9726380068900A /* ByteBufferTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
B5AEDBBA24744153007D8101 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilters = (
|
||||
ios,
|
||||
tvos,
|
||||
);
|
||||
target = B5AEDBAD24744153007D8101 /* AudioStreaming */;
|
||||
targetProxy = B5AEDBB924744153007D8101 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
B5AEDBC024744153007D8101 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.2.8;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
B5AEDBC124744153007D8101 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.2.8;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
B5AEDBC324744153007D8101 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_MODULE_VERIFIER = YES;
|
||||
INFOPLIST_FILE = AudioStreaming/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.8;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
B5AEDBC424744153007D8101 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_MODULE_VERIFIER = YES;
|
||||
INFOPLIST_FILE = AudioStreaming/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.8;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreaming;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
B5AEDBC624744153007D8101 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
INFOPLIST_FILE = AudioStreamingTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreamingTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
B5AEDBC724744153007D8101 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
INFOPLIST_FILE = AudioStreamingTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.decimal.AudioStreamingTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
B5AEDBA824744153007D8101 /* Build configuration list for PBXProject "AudioStreaming" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
B5AEDBC024744153007D8101 /* Debug */,
|
||||
B5AEDBC124744153007D8101 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
B5AEDBC224744153007D8101 /* Build configuration list for PBXNativeTarget "AudioStreaming" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
B5AEDBC324744153007D8101 /* Debug */,
|
||||
B5AEDBC424744153007D8101 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
B5AEDBC524744153007D8101 /* Build configuration list for PBXNativeTarget "AudioStreamingTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
B5AEDBC624744153007D8101 /* Debug */,
|
||||
B5AEDBC724744153007D8101 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = B5AEDBA524744153007D8101 /* Project object */;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>classNames</key>
|
||||
<dict>
|
||||
<key>ProtectedTests</key>
|
||||
<dict>
|
||||
<key>testProtectedValuesAreAccessedSafely()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>4.7921</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testThatProtectedReadAndWriteAreSafe()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0067462</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>QueueTests</key>
|
||||
<dict>
|
||||
<key>testComplexity()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>3.7871</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>runDestinationsByUUID</key>
|
||||
<dict>
|
||||
<key>31DA71B1-4664-472C-BD35-0711EE94837A</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>400</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>8-Core Intel Core i9</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2300</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>16</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBookPro16,1</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>8</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone12,1</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,103 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B5AEDBAD24744153007D8101"
|
||||
BuildableName = "AudioStreaming.framework"
|
||||
BlueprintName = "AudioStreaming"
|
||||
ReferencedContainer = "container:AudioStreaming.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
enableThreadSanitizer = "YES"
|
||||
codeCoverageEnabled = "YES"
|
||||
onlyGenerateCoverageForSpecifiedTargets = "YES">
|
||||
<CodeCoverageTargets>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B5AEDBAD24744153007D8101"
|
||||
BuildableName = "AudioStreaming.framework"
|
||||
BlueprintName = "AudioStreaming"
|
||||
ReferencedContainer = "container:AudioStreaming.xcodeproj">
|
||||
</BuildableReference>
|
||||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B5AEDBB624744153007D8101"
|
||||
BuildableName = "AudioStreamingTests.xctest"
|
||||
BlueprintName = "AudioStreamingTests"
|
||||
ReferencedContainer = "container:AudioStreaming.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
disableMainThreadChecker = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "MallocStackLogging"
|
||||
value = ""
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
<AdditionalOption
|
||||
key = "PrefersMallocStackLoggingLite"
|
||||
value = ""
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B5AEDBAD24744153007D8101"
|
||||
BuildableName = "AudioStreaming.framework"
|
||||
BlueprintName = "AudioStreaming"
|
||||
ReferencedContainer = "container:AudioStreaming.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>AudioStreaming.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>B5AEDBAD24744153007D8101</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>B5AEDBB624744153007D8101</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>B5B3B7D22486993B00656828</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:AudioPlayer/AudioPlayer.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "container:AudioStreaming.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildLocationStyle</key>
|
||||
<string>UseAppPreferences</string>
|
||||
<key>CustomBuildLocationType</key>
|
||||
<string>RelativeToDerivedData</string>
|
||||
<key>DerivedDataLocationStyle</key>
|
||||
<string>Default</string>
|
||||
<key>IssueFilterStyle</key>
|
||||
<string>ShowActiveSchemeOnly</string>
|
||||
<key>LiveSourceIssuesEnabled</key>
|
||||
<true/>
|
||||
<key>ShowSharedSchemesAutomaticallyEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Bucket
|
||||
uuid = "FECE436A-3E7C-4680-91FF-008D425B5539"
|
||||
type = "0"
|
||||
version = "2.0">
|
||||
<Breakpoints>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.SwiftErrorBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "5041EB46-A677-4654-B257-5EE0996DB4C7"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "556803C5-B0C9-4A2B-BE87-2D41721D1F54"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
breakpointStackSelectionBehavior = "1"
|
||||
scope = "1"
|
||||
stopOnStyle = "0">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.RuntimeIssueBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "F3650228-D513-48BC-AB78-8A8A0F688628"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
breakpointStackSelectionBehavior = "1"
|
||||
type = "1">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "8E93EE2C-5F40-468B-9880-089C10EC6FE1"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "malloc_error_break"
|
||||
moduleName = "">
|
||||
<Locations>
|
||||
<Location
|
||||
uuid = "8E93EE2C-5F40-468B-9880-089C10EC6FE1 - 9d096611641b4e7c"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "malloc_error_break"
|
||||
moduleName = "libsystem_malloc.dylib"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
offsetFromSymbolStart = "0">
|
||||
</Location>
|
||||
</Locations>
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.RuntimeIssueBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "0BEDF6EA-D92A-448C-A0E4-FBDADBF90794"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
breakpointStackSelectionBehavior = "1"
|
||||
type = "65535">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
@@ -16,6 +16,7 @@ public enum AudioConverterError: CustomDebugStringConvertible, Sendable {
|
||||
case propertyNotSupported
|
||||
case requiresPacketDescriptionsError
|
||||
case unspecifiedError
|
||||
case cannotCreateConverter
|
||||
|
||||
init(osstatus: OSStatus) {
|
||||
switch osstatus {
|
||||
@@ -65,7 +66,9 @@ public enum AudioConverterError: CustomDebugStringConvertible, Sendable {
|
||||
case .requiresPacketDescriptionsError:
|
||||
return "Required packet descriptions (error)"
|
||||
case .unspecifiedError:
|
||||
return "Unspecified error "
|
||||
return "Unspecified error"
|
||||
case .cannotCreateConverter:
|
||||
return "Cannot create audio converter"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
import Foundation
|
||||
import AudioCodecs
|
||||
import AVFoundation
|
||||
|
||||
/// A simple decoder for Ogg Vorbis files using libvorbisfile
|
||||
final class VorbisFileDecoder {
|
||||
// Core properties
|
||||
private var stream: VFStreamRef?
|
||||
private var vf: VFFileRef?
|
||||
|
||||
// Audio format properties
|
||||
private(set) var sampleRate: Int = 0
|
||||
private(set) var channels: Int = 0
|
||||
private(set) var durationSeconds: Double = -1
|
||||
private(set) var totalPcmSamples: Int64 = -1
|
||||
private(set) var nominalBitrate: Int = 0
|
||||
private(set) var processingFormat: AVAudioFormat?
|
||||
|
||||
// Thread safety
|
||||
private let decoderLock = NSLock()
|
||||
|
||||
// Silent frame generation
|
||||
private var silentFrameBuffer: UnsafeMutablePointer<Float>?
|
||||
private var silentFrameSize = 0
|
||||
|
||||
/// Create the stream buffer with specified capacity
|
||||
/// - Parameter capacityBytes: Size of the ring buffer in bytes
|
||||
func create(capacityBytes: Int) {
|
||||
decoderLock.lock()
|
||||
defer { decoderLock.unlock() }
|
||||
|
||||
stream = VFStreamCreate(capacityBytes)
|
||||
}
|
||||
|
||||
/// Clean up resources
|
||||
func destroy() {
|
||||
decoderLock.lock()
|
||||
defer { decoderLock.unlock() }
|
||||
|
||||
if let vf = vf { VFClear(vf) }
|
||||
if let stream = stream { VFStreamDestroy(stream) }
|
||||
vf = nil
|
||||
stream = nil
|
||||
|
||||
if let silentFrameBuffer = silentFrameBuffer {
|
||||
silentFrameBuffer.deallocate()
|
||||
self.silentFrameBuffer = nil
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
destroy()
|
||||
}
|
||||
|
||||
/// Push data into the stream buffer
|
||||
/// - Parameter data: The Ogg Vorbis data to decode
|
||||
func push(_ data: Data) {
|
||||
decoderLock.lock()
|
||||
defer { decoderLock.unlock() }
|
||||
|
||||
data.withUnsafeBytes { rawBuf in
|
||||
guard let base = rawBuf.baseAddress?.assumingMemoryBound(to: UInt8.self),
|
||||
rawBuf.count > 0,
|
||||
let stream = stream else { return }
|
||||
|
||||
VFStreamPush(stream, base, rawBuf.count)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the number of bytes currently available in the stream buffer
|
||||
/// - Returns: Number of bytes available
|
||||
func availableBytes() -> Int {
|
||||
decoderLock.lock()
|
||||
defer { decoderLock.unlock() }
|
||||
|
||||
guard let stream = stream else { return 0 }
|
||||
return Int(VFStreamAvailableBytes(stream))
|
||||
}
|
||||
|
||||
/// Mark the end of the stream
|
||||
func markEOF() {
|
||||
decoderLock.lock()
|
||||
defer { decoderLock.unlock() }
|
||||
|
||||
if let stream = stream {
|
||||
VFStreamMarkEOF(stream)
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to open the Vorbis file if enough data is available
|
||||
/// - Throws: Error if opening fails
|
||||
func openIfNeeded() throws {
|
||||
decoderLock.lock()
|
||||
defer { decoderLock.unlock() }
|
||||
|
||||
guard vf == nil, let stream = stream else { return }
|
||||
|
||||
var outVF: VFFileRef?
|
||||
let rc = VFOpen(stream, &outVF)
|
||||
if rc < 0 {
|
||||
Logger.error("Failed to open Vorbis file", category: .audioRendering)
|
||||
throw NSError(domain: "VorbisFileDecoder", code: Int(rc),
|
||||
userInfo: [NSLocalizedDescriptionKey: "Failed to open Vorbis file"])
|
||||
}
|
||||
|
||||
vf = outVF
|
||||
|
||||
// Get stream info
|
||||
var info = VFStreamInfo()
|
||||
if VFGetInfo(outVF, &info) == 0 {
|
||||
sampleRate = Int(info.sample_rate)
|
||||
channels = Int(info.channels)
|
||||
totalPcmSamples = Int64(info.total_pcm_samples)
|
||||
durationSeconds = info.duration_seconds
|
||||
nominalBitrate = Int(info.bitrate_nominal)
|
||||
|
||||
// Create audio format
|
||||
let layoutTag: AudioChannelLayoutTag
|
||||
switch channels {
|
||||
case 1: layoutTag = kAudioChannelLayoutTag_Mono
|
||||
case 2: layoutTag = kAudioChannelLayoutTag_Stereo
|
||||
default: layoutTag = kAudioChannelLayoutTag_Unknown | UInt32(channels)
|
||||
}
|
||||
|
||||
let channelLayout = AVAudioChannelLayout(layoutTag: layoutTag)!
|
||||
|
||||
processingFormat = AVAudioFormat(
|
||||
commonFormat: .pcmFormatFloat32,
|
||||
sampleRate: Double(sampleRate),
|
||||
interleaved: false,
|
||||
channelLayout: channelLayout
|
||||
)
|
||||
|
||||
// Create silent frame buffer
|
||||
silentFrameSize = 1024 * channels
|
||||
silentFrameBuffer = UnsafeMutablePointer<Float>.allocate(capacity: silentFrameSize)
|
||||
for i in 0..<silentFrameSize {
|
||||
silentFrameBuffer?[i] = 0.0
|
||||
}
|
||||
} else {
|
||||
Logger.error("Failed to get stream info", category: .audioRendering)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read decoded frames into an AVAudioPCMBuffer
|
||||
/// - Parameters:
|
||||
/// - buffer: The buffer to fill with audio data
|
||||
/// - frameCount: Maximum number of frames to read
|
||||
/// - Returns: Number of frames read, 0 on EOF, negative on error
|
||||
func readFrames(into buffer: AVAudioPCMBuffer, frameCount: Int) -> Int {
|
||||
decoderLock.lock()
|
||||
defer { decoderLock.unlock() }
|
||||
|
||||
guard let vf = vf, buffer.format.channelCount > 0 else {
|
||||
return generateSilentFrames(into: buffer, frameCount: frameCount)
|
||||
}
|
||||
|
||||
// Get float channel data from buffer
|
||||
guard let floatChannelData = buffer.floatChannelData else {
|
||||
return generateSilentFrames(into: buffer, frameCount: frameCount)
|
||||
}
|
||||
|
||||
// Read deinterleaved frames directly
|
||||
let maxFrames = min(frameCount, Int(buffer.frameCapacity))
|
||||
var pcmChannels: UnsafeMutablePointer<UnsafeMutablePointer<Float>?>?
|
||||
let framesRead = Int(VFReadFloat(vf, &pcmChannels, Int32(maxFrames)))
|
||||
|
||||
// If no frames were read, generate silent frames instead of returning 0
|
||||
if framesRead <= 0 {
|
||||
return generateSilentFrames(into: buffer, frameCount: frameCount)
|
||||
}
|
||||
|
||||
// Copy deinterleaved data directly (no conversion needed!)
|
||||
guard let pcm = pcmChannels else {
|
||||
return generateSilentFrames(into: buffer, frameCount: frameCount)
|
||||
}
|
||||
|
||||
let channelCount = min(Int(buffer.format.channelCount), channels)
|
||||
for ch in 0..<channelCount {
|
||||
guard let input = pcm[ch] else { continue }
|
||||
let output = floatChannelData[ch]
|
||||
memcpy(output, input, framesRead * MemoryLayout<Float>.stride)
|
||||
}
|
||||
|
||||
return framesRead
|
||||
}
|
||||
|
||||
/// Generate silent frames when no real audio data is available
|
||||
/// This prevents EOF detection by never returning 0 frames
|
||||
private func generateSilentFrames(into buffer: AVAudioPCMBuffer, frameCount: Int) -> Int {
|
||||
guard let floatChannelData = buffer.floatChannelData,
|
||||
channels > 0 else { return 1 }
|
||||
|
||||
// Use a small frame count to ensure we keep checking for real data
|
||||
let framesToGenerate = min(128, frameCount)
|
||||
|
||||
// Fill buffer with zeros
|
||||
for ch in 0..<min(Int(buffer.format.channelCount), channels) {
|
||||
let dst = floatChannelData[ch]
|
||||
for frame in 0..<framesToGenerate {
|
||||
dst[frame] = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
return framesToGenerate
|
||||
}
|
||||
|
||||
/// Reset the decoder state
|
||||
func reset() {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,27 @@
|
||||
|
||||
import AVFoundation
|
||||
|
||||
struct OggVorbisStreamInfo {
|
||||
var serialNumber: UInt32 = 0
|
||||
var pageCount: UInt64 = 0
|
||||
var totalSamples: UInt64 = 0
|
||||
var sampleRate: UInt32 = 0
|
||||
var channels: UInt8 = 0
|
||||
var bitRate: UInt32 = 0
|
||||
var nominalBitrate: UInt32 = 0
|
||||
var minBitrate: UInt32 = 0
|
||||
var maxBitrate: UInt32 = 0
|
||||
var blocksize0: Int = 0
|
||||
var blocksize1: Int = 0
|
||||
var commentHeader: [String: String] = [:]
|
||||
|
||||
// For seeking
|
||||
var granulePosition: Int64 = 0
|
||||
var pageOffsets: [Int64] = []
|
||||
var pageGranules: [Int64] = []
|
||||
}
|
||||
|
||||
|
||||
final class AudioStreamState {
|
||||
var processedDataFormat: Bool = false
|
||||
var dataOffset: UInt64 = 0
|
||||
@@ -13,4 +34,12 @@ final class AudioStreamState {
|
||||
var dataPacketCount: Double = 0
|
||||
var streamFormat = AudioStreamBasicDescription()
|
||||
var bitRate: Double?
|
||||
|
||||
// Flag to indicate when the audio format is ready for decoding
|
||||
var readyForDecoding: Bool = false
|
||||
|
||||
// Add Ogg Vorbis-specific metadata
|
||||
var oggVorbisStreamInfo: OggVorbisStreamInfo?
|
||||
var hasAttemptedOggVorbisParse: Bool = false
|
||||
var initialOggBytes: Data?
|
||||
}
|
||||
|
||||
@@ -31,7 +31,10 @@ open class AudioPlayer {
|
||||
/// result in the audio being exhausted before it could fetch new data.
|
||||
public var rate: Float {
|
||||
get { rateNode.rate }
|
||||
set { rateNode.rate = newValue }
|
||||
set {
|
||||
rateNode.rate = newValue
|
||||
rateNode.bypass = (newValue == 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The player's current state.
|
||||
@@ -90,6 +93,31 @@ open class AudioPlayer {
|
||||
guard let entry = playingEntry else { return 0 }
|
||||
return entry.framesPlayed
|
||||
}
|
||||
|
||||
/// Indicates whether seeking is supported for the currently playing audio
|
||||
///
|
||||
/// Returns `false` if:
|
||||
/// - The audio format doesn't support seeking (e.g., Ogg Vorbis streams)
|
||||
/// - The stream has no valid duration (e.g., live radio streams)
|
||||
///
|
||||
/// Use this property to enable/disable seek controls in your UI
|
||||
public var isSeekable: Bool {
|
||||
guard playerContext.internalState != .pendingNext else { return true }
|
||||
|
||||
playerContext.entriesLock.lock()
|
||||
let playingEntry = playerContext.audioPlayingEntry
|
||||
playerContext.entriesLock.unlock()
|
||||
guard let entry = playingEntry else { return true }
|
||||
|
||||
// Check if format supports seeking (Ogg Vorbis doesn't)
|
||||
if entry.audioFileHint == kAudioFileOggType {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if stream has a valid duration (live streams don't)
|
||||
let entryDuration = entry.duration()
|
||||
return entryDuration > 0
|
||||
}
|
||||
|
||||
public private(set) var customAttachedNodes = [AVAudioNode]()
|
||||
|
||||
@@ -176,6 +204,7 @@ open class AudioPlayer {
|
||||
)
|
||||
configPlayerContext()
|
||||
configPlayerNode()
|
||||
configureRateNode()
|
||||
setupEngine()
|
||||
}
|
||||
|
||||
@@ -510,6 +539,16 @@ open class AudioPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
// Add this new method
|
||||
private func configureRateNode() {
|
||||
// Set overlap to a lower value for faster transitions (default is 8.0)
|
||||
rateNode.overlap = 4.0
|
||||
// Ensure pitch is not shifted
|
||||
rateNode.pitch = 0
|
||||
// Bypass by default since rate starts at 1.0
|
||||
rateNode.bypass = true
|
||||
}
|
||||
|
||||
/// Creates and configures an `AVAudioUnit` with an output configuration
|
||||
/// and assigns it to the `player` variable.
|
||||
private func configPlayerNode() {
|
||||
|
||||
@@ -106,6 +106,7 @@ public enum AudioSystemError: LocalizedError, Equatable, Sendable {
|
||||
case playerStartError
|
||||
case fileStreamError(AudioFileStreamError)
|
||||
case converterError(AudioConverterError)
|
||||
case codecError
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
@@ -116,6 +117,8 @@ public enum AudioSystemError: LocalizedError, Equatable, Sendable {
|
||||
return "Audio file stream error'd: \(error)"
|
||||
case let .converterError(error):
|
||||
return "Audio converter error'd: \(error)"
|
||||
case .codecError:
|
||||
return "Audio codec error"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,13 @@ final class AudioFileStreamProcessor {
|
||||
private let playerContext: AudioPlayerContext
|
||||
private let rendererContext: AudioRendererContext
|
||||
private let outputAudioFormat: AudioStreamBasicDescription
|
||||
|
||||
// Add Ogg Vorbis processor
|
||||
private lazy var oggVorbisProcessor = OggVorbisStreamProcessor(
|
||||
playerContext: playerContext,
|
||||
rendererContext: rendererContext,
|
||||
outputAudioFormat: outputAudioFormat
|
||||
)
|
||||
|
||||
var audioFileStream: AudioFileStreamID?
|
||||
var audioConverter: AudioConverterRef?
|
||||
@@ -42,9 +49,12 @@ final class AudioFileStreamProcessor {
|
||||
|
||||
var currentFileFormat: String = ""
|
||||
let fileFormatsForDelayedConverterCreation: Set = ["fa4m", "f4pm"]
|
||||
|
||||
// Track if we're processing Ogg Vorbis
|
||||
private var isProcessingOggVorbis: Bool = false
|
||||
|
||||
var isFileStreamOpen: Bool {
|
||||
audioFileStream != nil
|
||||
audioFileStream != nil || isProcessingOggVorbis
|
||||
}
|
||||
|
||||
init(playerContext: AudioPlayerContext,
|
||||
@@ -54,6 +64,11 @@ final class AudioFileStreamProcessor {
|
||||
self.playerContext = playerContext
|
||||
self.rendererContext = rendererContext
|
||||
self.outputAudioFormat = outputAudioFormat
|
||||
|
||||
// Set up Ogg Vorbis processor callback
|
||||
oggVorbisProcessor.processorCallback = { [weak self] effect in
|
||||
self?.fileStreamCallback?(effect)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the `AudioFileStream`
|
||||
@@ -63,12 +78,25 @@ final class AudioFileStreamProcessor {
|
||||
/// - Returns: An `OSStatus` value indicating if an error occurred or not.
|
||||
|
||||
func openFileStream(with fileHint: AudioFileTypeID) -> OSStatus {
|
||||
let data = UnsafeMutableRawPointer.from(object: self)
|
||||
return AudioFileStreamOpen(data, _propertyListenerProc, _propertyPacketsProc, fileHint, &audioFileStream)
|
||||
// Check if this is an Ogg Vorbis file
|
||||
if fileHint == kAudioFileOggType {
|
||||
isProcessingOggVorbis = true
|
||||
return noErr
|
||||
} else {
|
||||
isProcessingOggVorbis = false
|
||||
let data = UnsafeMutableRawPointer.from(object: self)
|
||||
return AudioFileStreamOpen(data, _propertyListenerProc, _propertyPacketsProc, fileHint, &audioFileStream)
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the currently open `AudioFileStream` instance, if opened.
|
||||
func closeFileStreamIfNeeded() {
|
||||
if isProcessingOggVorbis {
|
||||
isProcessingOggVorbis = false
|
||||
oggVorbisProcessor.cleanup()
|
||||
return
|
||||
}
|
||||
|
||||
guard let fileStream = audioFileStream else {
|
||||
Logger.debug("audio file stream not opened", category: .generic)
|
||||
return
|
||||
@@ -83,8 +111,14 @@ final class AudioFileStreamProcessor {
|
||||
///
|
||||
/// - Returns: An `OSStatus` value indicating if an error occurred or not.
|
||||
func parseFileStreamBytes(data: Data) -> OSStatus {
|
||||
guard let stream = audioFileStream else { return 0 }
|
||||
guard !data.isEmpty else { return 0 }
|
||||
|
||||
// Check if we're processing Ogg Vorbis
|
||||
if isProcessingOggVorbis {
|
||||
return oggVorbisProcessor.parseOggVorbisData(data: data)
|
||||
}
|
||||
|
||||
guard let stream = audioFileStream else { return 0 }
|
||||
let flags: AudioFileStreamParseFlags = discontinuous ? .discontinuity : .init()
|
||||
return data.withUnsafeBytes { buffer -> OSStatus in
|
||||
AudioFileStreamParseBytes(stream, UInt32(buffer.count), buffer.baseAddress, flags)
|
||||
@@ -92,10 +126,17 @@ final class AudioFileStreamProcessor {
|
||||
}
|
||||
|
||||
func processSeek() {
|
||||
guard let stream = audioFileStream else { return }
|
||||
guard let readingEntry = playerContext.audioReadingEntry else {
|
||||
return
|
||||
}
|
||||
|
||||
// If processing Ogg Vorbis, use the Ogg Vorbis processor
|
||||
if isProcessingOggVorbis {
|
||||
oggVorbisProcessor.processSeek()
|
||||
return
|
||||
}
|
||||
|
||||
guard let stream = audioFileStream else { return }
|
||||
|
||||
guard readingEntry.calculatedBitrate() > 0.0 || (playerContext.audioPlayingEntry?.length ?? 0) > 0 else {
|
||||
return
|
||||
|
||||
@@ -0,0 +1,489 @@
|
||||
//
|
||||
// OggVorbisStreamProcessor.swift
|
||||
// AudioStreaming
|
||||
//
|
||||
// Created on 25/10/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
import CoreAudio
|
||||
|
||||
/// A processor for Ogg Vorbis audio streams using libvorbisfile
|
||||
final class OggVorbisStreamProcessor {
|
||||
/// The callback to notify when processing is complete or an error occurs
|
||||
var processorCallback: ((FileStreamProcessorEffect) -> Void)?
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
/// Correction factor for Ogg container overhead in bitrate-based duration calculation.
|
||||
/// Ogg containers add 3-4% overhead (page headers, packet headers, metadata).
|
||||
/// The nominal bitrate only accounts for audio data, not container overhead.
|
||||
/// By reducing the bitrate slightly, we increase the calculated duration to match reality.
|
||||
private let oggContainerOverheadFactor: Double = 0.96 // 4% overhead
|
||||
|
||||
/// Fallback bitrate estimates when nominal bitrate is unavailable
|
||||
private let fallbackBitrateStereo: Double = 160_000 // 160 kbps for stereo
|
||||
private let fallbackBitrateMono: Double = 96_000 // 96 kbps for mono
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let playerContext: AudioPlayerContext
|
||||
private let rendererContext: AudioRendererContext
|
||||
private let outputAudioFormat: AudioStreamBasicDescription
|
||||
|
||||
private let vfDecoder = VorbisFileDecoder()
|
||||
private var isInitialized = false
|
||||
|
||||
// Audio converter for format conversion
|
||||
private var audioConverter: AVAudioConverter?
|
||||
|
||||
// Buffer for PCM conversion
|
||||
private var pcmBuffer: AVAudioPCMBuffer?
|
||||
private let frameCount = 1024
|
||||
|
||||
// Seeking state (currently unused - seeking not fully supported)
|
||||
// Future enhancement: implement proper seeking for local files
|
||||
|
||||
// Debug logging
|
||||
private var totalFramesProcessed = 0
|
||||
private var dataChunkCount = 0
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initialize the OggVorbisStreamProcessor
|
||||
/// - Parameters:
|
||||
/// - playerContext: The audio player context
|
||||
/// - rendererContext: The audio renderer context
|
||||
/// - outputAudioFormat: The output audio format
|
||||
init(playerContext: AudioPlayerContext,
|
||||
rendererContext: AudioRendererContext,
|
||||
outputAudioFormat: AudioStreamBasicDescription) {
|
||||
self.playerContext = playerContext
|
||||
self.rendererContext = rendererContext
|
||||
self.outputAudioFormat = outputAudioFormat
|
||||
}
|
||||
|
||||
deinit {
|
||||
cleanup()
|
||||
}
|
||||
|
||||
/// Clean up all resources and reset state
|
||||
func cleanup() {
|
||||
cleanupBuffers()
|
||||
|
||||
audioConverter = nil
|
||||
|
||||
// Destroy and reset the decoder
|
||||
vfDecoder.destroy()
|
||||
isInitialized = false
|
||||
totalFramesProcessed = 0
|
||||
}
|
||||
|
||||
// MARK: - Data Processing
|
||||
|
||||
/// Parse Ogg Vorbis data
|
||||
/// - Parameter data: The Ogg Vorbis data to parse
|
||||
/// - Returns: An OSStatus indicating success or failure
|
||||
func parseOggVorbisData(data: Data) -> OSStatus {
|
||||
guard let entry = playerContext.audioReadingEntry else { return 0 }
|
||||
|
||||
dataChunkCount += 1
|
||||
|
||||
if !isInitialized {
|
||||
vfDecoder.create(capacityBytes: 2_097_152)
|
||||
isInitialized = true
|
||||
totalFramesProcessed = 0
|
||||
}
|
||||
|
||||
vfDecoder.push(data)
|
||||
|
||||
if !entry.audioStreamState.processedDataFormat {
|
||||
let availableBytes = vfDecoder.availableBytes()
|
||||
|
||||
if availableBytes >= 16384 {
|
||||
do {
|
||||
try vfDecoder.openIfNeeded()
|
||||
|
||||
if vfDecoder.sampleRate > 0 && vfDecoder.channels > 0 {
|
||||
setupAudioFormat()
|
||||
|
||||
if pcmBuffer == nil, let processingFormat = vfDecoder.processingFormat {
|
||||
pcmBuffer = AVAudioPCMBuffer(pcmFormat: processingFormat, frameCapacity: UInt32(frameCount))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return noErr
|
||||
}
|
||||
} else {
|
||||
return noErr
|
||||
}
|
||||
}
|
||||
|
||||
guard entry.audioStreamState.processedDataFormat else {
|
||||
return noErr
|
||||
}
|
||||
|
||||
// Handle seek requests
|
||||
if let playingEntry = playerContext.audioPlayingEntry,
|
||||
playingEntry.seekRequest.requested, playingEntry.calculatedBitrate() > 0 {
|
||||
// This is the correct usage of .processSource - only for seek requests
|
||||
processorCallback?(.processSource)
|
||||
if rendererContext.waiting.value {
|
||||
rendererContext.packetsSemaphore.signal()
|
||||
}
|
||||
return noErr
|
||||
}
|
||||
|
||||
// Decode frames continuously - matching AudioFileStreamProcessor behavior
|
||||
// Wait for renderer buffer space if needed, just like regular audio processing
|
||||
var consecutiveNoFrames = 0
|
||||
var totalDecoded = 0
|
||||
|
||||
decodeLoop: while true {
|
||||
// Check player state
|
||||
if playerContext.internalState == .disposed
|
||||
|| playerContext.internalState == .pendingNext
|
||||
|| playerContext.internalState == .stopped {
|
||||
break
|
||||
}
|
||||
|
||||
// Check if there's space in the buffer
|
||||
rendererContext.lock.lock()
|
||||
let totalFrames = rendererContext.bufferContext.totalFrameCount
|
||||
let usedFrames = rendererContext.bufferContext.frameUsedCount
|
||||
rendererContext.lock.unlock()
|
||||
|
||||
guard usedFrames <= totalFrames else {
|
||||
break decodeLoop
|
||||
}
|
||||
|
||||
var framesLeft = totalFrames - usedFrames
|
||||
|
||||
if framesLeft == 0 {
|
||||
while true {
|
||||
rendererContext.lock.lock()
|
||||
let totalFrames = rendererContext.bufferContext.totalFrameCount
|
||||
let usedFrames = rendererContext.bufferContext.frameUsedCount
|
||||
rendererContext.lock.unlock()
|
||||
|
||||
if usedFrames > totalFrames {
|
||||
break decodeLoop
|
||||
}
|
||||
|
||||
framesLeft = totalFrames - usedFrames
|
||||
|
||||
if framesLeft > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if playerContext.internalState == .disposed
|
||||
|| playerContext.internalState == .pendingNext
|
||||
|| playerContext.internalState == .stopped {
|
||||
break decodeLoop
|
||||
}
|
||||
|
||||
if let playingEntry = playerContext.audioPlayingEntry,
|
||||
playingEntry.seekRequest.requested, playingEntry.calculatedBitrate() > 0 {
|
||||
processorCallback?(.processSource)
|
||||
if rendererContext.waiting.value {
|
||||
rendererContext.packetsSemaphore.signal()
|
||||
}
|
||||
break decodeLoop
|
||||
}
|
||||
|
||||
rendererContext.waiting.write { $0 = true }
|
||||
rendererContext.packetsSemaphore.wait()
|
||||
rendererContext.waiting.write { $0 = false }
|
||||
}
|
||||
}
|
||||
|
||||
let availableBytes = vfDecoder.availableBytes()
|
||||
if availableBytes < 4096 {
|
||||
consecutiveNoFrames += 1
|
||||
if consecutiveNoFrames >= 3 {
|
||||
break decodeLoop
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
let status = decodeAndFillBuffer()
|
||||
if status != noErr {
|
||||
consecutiveNoFrames += 1
|
||||
if consecutiveNoFrames >= 3 {
|
||||
break decodeLoop
|
||||
}
|
||||
} else {
|
||||
consecutiveNoFrames = 0
|
||||
totalDecoded += 1
|
||||
}
|
||||
}
|
||||
|
||||
if totalDecoded > 0 && rendererContext.waiting.value {
|
||||
rendererContext.packetsSemaphore.signal()
|
||||
}
|
||||
|
||||
return noErr
|
||||
}
|
||||
|
||||
/// Decode audio and fill the renderer buffer
|
||||
/// - Returns: noErr if frames were decoded, otherwise an error/no-data status
|
||||
private func decodeAndFillBuffer() -> OSStatus {
|
||||
guard let pcmBuffer = pcmBuffer else {
|
||||
return OSStatus(-1)
|
||||
}
|
||||
|
||||
let framesRead = vfDecoder.readFrames(into: pcmBuffer, frameCount: frameCount)
|
||||
|
||||
if framesRead <= 0 {
|
||||
return OSStatus(-1)
|
||||
}
|
||||
|
||||
pcmBuffer.frameLength = UInt32(framesRead)
|
||||
processDecodedAudio(pcmBuffer: pcmBuffer, framesRead: framesRead)
|
||||
totalFramesProcessed += framesRead
|
||||
|
||||
return noErr
|
||||
}
|
||||
|
||||
// MARK: - Audio Format Setup
|
||||
|
||||
// Setup audio format using the processingFormat from VorbisFileDecoder
|
||||
private func setupAudioFormat() {
|
||||
guard let entry = playerContext.audioReadingEntry,
|
||||
let processingFormat = vfDecoder.processingFormat else { return }
|
||||
|
||||
entry.lock.lock()
|
||||
|
||||
// Use the decoder's deinterleaved format directly
|
||||
var asbd = processingFormat.streamDescription.pointee
|
||||
|
||||
// Store the format in the entry
|
||||
entry.audioStreamFormat = asbd
|
||||
entry.sampleRate = Float(vfDecoder.sampleRate)
|
||||
entry.packetDuration = Double(1) / Double(vfDecoder.sampleRate)
|
||||
|
||||
// For streaming Ogg files, totalPcmSamples may not be available (returns error code)
|
||||
// In that case, use bitrate-based duration calculation with container overhead correction
|
||||
if vfDecoder.totalPcmSamples > 0 {
|
||||
// We have total samples - use packet offset for accurate duration
|
||||
entry.audioStreamState.dataPacketOffset = UInt64(vfDecoder.totalPcmSamples)
|
||||
} else {
|
||||
// Streaming - use bitrate for duration estimation
|
||||
if vfDecoder.nominalBitrate > 0 {
|
||||
entry.audioStreamState.bitRate = Double(vfDecoder.nominalBitrate) * oggContainerOverheadFactor
|
||||
} else {
|
||||
// Fallback: use typical bitrates for Vorbis quality
|
||||
let estimatedBitrate = vfDecoder.channels == 2 ? fallbackBitrateStereo : fallbackBitrateMono
|
||||
entry.audioStreamState.bitRate = estimatedBitrate * oggContainerOverheadFactor
|
||||
}
|
||||
}
|
||||
entry.audioStreamState.processedDataFormat = true
|
||||
entry.audioStreamState.readyForDecoding = true
|
||||
entry.lock.unlock()
|
||||
|
||||
// Create audio converter from decoder format to output format
|
||||
createAudioConverter(from: processingFormat, to: outputAudioFormat)
|
||||
}
|
||||
|
||||
/// Create audio converter from decoder format to output format
|
||||
private func createAudioConverter(from sourceFormat: AVAudioFormat, to destFormat: AudioStreamBasicDescription) {
|
||||
audioConverter = nil
|
||||
|
||||
var dest = destFormat
|
||||
|
||||
guard let destAVFormat = AVAudioFormat(streamDescription: &dest) else {
|
||||
Logger.error("Failed to create output AVAudioFormat", category: .audioRendering)
|
||||
return
|
||||
}
|
||||
|
||||
guard let converter = AVAudioConverter(from: sourceFormat, to: destAVFormat) else {
|
||||
Logger.error("Failed to create AVAudioConverter", category: .audioRendering)
|
||||
return
|
||||
}
|
||||
|
||||
audioConverter = converter
|
||||
}
|
||||
|
||||
// MARK: - Audio Processing
|
||||
|
||||
/// Process decoded audio using AVAudioConverter
|
||||
/// - Parameters:
|
||||
/// - pcmBuffer: The PCM buffer containing decoded audio
|
||||
/// - framesRead: Number of frames read
|
||||
private func processDecodedAudio(pcmBuffer: AVAudioPCMBuffer, framesRead: Int) {
|
||||
guard let entry = playerContext.audioReadingEntry,
|
||||
let converter = audioConverter else { return }
|
||||
|
||||
// Set the input buffer's frame length
|
||||
pcmBuffer.frameLength = UInt32(framesRead)
|
||||
|
||||
// Create output buffer with converter's output format
|
||||
guard let outputBuffer = AVAudioPCMBuffer(
|
||||
pcmFormat: converter.outputFormat,
|
||||
frameCapacity: UInt32(framesRead)
|
||||
) else { return }
|
||||
|
||||
// Process through AudioConverter
|
||||
rendererContext.lock.lock()
|
||||
let bufferContext = rendererContext.bufferContext
|
||||
let used = bufferContext.frameUsedCount
|
||||
let totalFrames = bufferContext.totalFrameCount
|
||||
let end = (bufferContext.frameStartIndex + bufferContext.frameUsedCount) % bufferContext.totalFrameCount
|
||||
rendererContext.lock.unlock()
|
||||
|
||||
guard used <= totalFrames else {
|
||||
return
|
||||
}
|
||||
|
||||
var framesLeft = totalFrames - used
|
||||
|
||||
// Wait for buffer space if needed
|
||||
if framesLeft == 0 {
|
||||
while true {
|
||||
rendererContext.lock.lock()
|
||||
let currentUsed = rendererContext.bufferContext.frameUsedCount
|
||||
let currentTotal = rendererContext.bufferContext.totalFrameCount
|
||||
rendererContext.lock.unlock()
|
||||
|
||||
if currentUsed > currentTotal {
|
||||
return
|
||||
}
|
||||
|
||||
framesLeft = currentTotal - currentUsed
|
||||
if framesLeft > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if playerContext.internalState == .disposed
|
||||
|| playerContext.internalState == .pendingNext
|
||||
|| playerContext.internalState == .stopped {
|
||||
return
|
||||
}
|
||||
|
||||
if let playingEntry = playerContext.audioPlayingEntry,
|
||||
playingEntry.seekRequest.requested, playingEntry.calculatedBitrate() > 0 {
|
||||
processorCallback?(.processSource)
|
||||
if rendererContext.waiting.value {
|
||||
rendererContext.packetsSemaphore.signal()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
rendererContext.waiting.write { $0 = true }
|
||||
rendererContext.packetsSemaphore.wait()
|
||||
rendererContext.waiting.write { $0 = false }
|
||||
}
|
||||
}
|
||||
|
||||
var error: NSError?
|
||||
var inputConsumed = false
|
||||
|
||||
let status = converter.convert(to: outputBuffer, error: &error) { inNumPackets, outStatus in
|
||||
if inputConsumed {
|
||||
outStatus.pointee = .noDataNow
|
||||
return nil
|
||||
}
|
||||
inputConsumed = true
|
||||
outStatus.pointee = .haveData
|
||||
return pcmBuffer
|
||||
}
|
||||
|
||||
guard status != .error, outputBuffer.frameLength > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
rendererContext.lock.lock()
|
||||
let start = rendererContext.bufferContext.frameStartIndex
|
||||
let currentEnd = (rendererContext.bufferContext.frameStartIndex + rendererContext.bufferContext.frameUsedCount) % rendererContext.bufferContext.totalFrameCount
|
||||
let totalFrameCount = rendererContext.bufferContext.totalFrameCount
|
||||
let currentUsed = rendererContext.bufferContext.frameUsedCount
|
||||
rendererContext.lock.unlock()
|
||||
|
||||
// Calculate actual space available
|
||||
let actualFramesLeft = totalFrameCount - currentUsed
|
||||
let framesToCopy = min(UInt32(outputBuffer.frameLength), actualFramesLeft)
|
||||
|
||||
guard let sourceData = outputBuffer.audioBufferList.pointee.mBuffers.mData?.assumingMemoryBound(to: UInt8.self) else { return }
|
||||
let bytesPerFrame = Int(rendererContext.bufferContext.sizeInBytes)
|
||||
let destData = rendererContext.audioBuffer.mData?.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
if currentEnd >= start {
|
||||
// Ring buffer wraps
|
||||
let framesToEnd = totalFrameCount - currentEnd
|
||||
let firstChunkFrames = min(framesToCopy, framesToEnd)
|
||||
let firstChunkBytes = Int(firstChunkFrames) * bytesPerFrame
|
||||
let firstChunkOffset = Int(currentEnd) * bytesPerFrame
|
||||
|
||||
// Copy first chunk to end of buffer
|
||||
memcpy(destData?.advanced(by: firstChunkOffset), sourceData, firstChunkBytes)
|
||||
|
||||
// Copy second chunk to start of buffer if needed
|
||||
if firstChunkFrames < framesToCopy {
|
||||
let secondChunkFrames = framesToCopy - firstChunkFrames
|
||||
let secondChunkBytes = Int(secondChunkFrames) * bytesPerFrame
|
||||
memcpy(destData, sourceData.advanced(by: firstChunkBytes), secondChunkBytes)
|
||||
}
|
||||
} else {
|
||||
// No wrap
|
||||
let chunkBytes = Int(framesToCopy) * bytesPerFrame
|
||||
let offset = Int(currentEnd) * bytesPerFrame
|
||||
memcpy(destData?.advanced(by: offset), sourceData, chunkBytes)
|
||||
}
|
||||
|
||||
fillUsedFrames(framesCount: framesToCopy)
|
||||
updateProcessedPackets(inNumberPackets: framesToCopy)
|
||||
}
|
||||
|
||||
/// Process a seek request
|
||||
///
|
||||
/// Seeking is not supported for Ogg Vorbis streams.
|
||||
/// For HTTP streams, seeking is extremely difficult because:
|
||||
/// 1. Need to find Ogg page boundaries
|
||||
/// 2. Need Vorbis headers to initialize decoder
|
||||
/// 3. Headers are only at the beginning of the file
|
||||
///
|
||||
/// Note: Future enhancement could support seeking in local files
|
||||
/// by fetching headers and using libvorbisfile's built-in seeking.
|
||||
func processSeek() {
|
||||
// Seeking not supported - UI should check AudioPlayer.isSeekable
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
/// Update the processed packets information
|
||||
/// - Parameter inNumberPackets: The number of packets processed
|
||||
private func updateProcessedPackets(inNumberPackets: UInt32) {
|
||||
guard let readingEntry = playerContext.audioReadingEntry else { return }
|
||||
let processedPackCount = readingEntry.processedPacketsState.count
|
||||
let maxPackets = 4096
|
||||
|
||||
if processedPackCount < maxPackets {
|
||||
let count = min(Int(inNumberPackets), maxPackets - Int(processedPackCount))
|
||||
let packetSize: UInt32 = UInt32(readingEntry.audioStreamFormat.mBytesPerFrame)
|
||||
|
||||
readingEntry.lock.lock()
|
||||
readingEntry.processedPacketsState.sizeTotal += (packetSize * UInt32(count))
|
||||
readingEntry.processedPacketsState.count += UInt32(count)
|
||||
readingEntry.lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the processed frames for buffer and reading entry
|
||||
/// - Parameter frameCount: The number of frames to advance
|
||||
@inline(__always)
|
||||
private func fillUsedFrames(framesCount: UInt32) {
|
||||
rendererContext.lock.lock()
|
||||
rendererContext.bufferContext.frameUsedCount += framesCount
|
||||
rendererContext.lock.unlock()
|
||||
|
||||
playerContext.audioReadingEntry?.lock.lock()
|
||||
playerContext.audioReadingEntry?.framesState.queued += Int(framesCount)
|
||||
playerContext.audioReadingEntry?.lock.unlock()
|
||||
}
|
||||
|
||||
/// Clean up allocated buffers
|
||||
private func cleanupBuffers() {
|
||||
pcmBuffer = nil
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import AudioToolbox
|
||||
import Foundation
|
||||
|
||||
/// mapping from mime types to `AudioFileTypeID`
|
||||
// Custom file type for Ogg Vorbis
|
||||
let kAudioFileOggType: AudioFileTypeID = 0x6F676720 // 'ogg '
|
||||
|
||||
let fileTypesFromMimeType: [String: AudioFileTypeID] =
|
||||
[
|
||||
"audio/mp3": kAudioFileMP3Type,
|
||||
@@ -33,7 +36,10 @@ let fileTypesFromMimeType: [String: AudioFileTypeID] =
|
||||
"video/3gpp": kAudioFile3GPType,
|
||||
"audio/3gp2": kAudioFile3GP2Type,
|
||||
"video/3gp2": kAudioFile3GP2Type,
|
||||
"audio/flac": kAudioFileFLACType
|
||||
"audio/flac": kAudioFileFLACType,
|
||||
"audio/ogg": kAudioFileOggType,
|
||||
"audio/vorbis": kAudioFileOggType,
|
||||
"application/ogg": kAudioFileOggType
|
||||
]
|
||||
|
||||
/// Method that converts mime type to AudioFileTypeID
|
||||
@@ -58,6 +64,8 @@ let fileTypesFromFileExtension: [String: AudioFileTypeID] =
|
||||
"ac3": kAudioFileAC3Type,
|
||||
"3gp": kAudioFile3GPType,
|
||||
"flac": kAudioFileFLACType,
|
||||
"ogg": kAudioFileOggType,
|
||||
"oga": kAudioFileOggType,
|
||||
]
|
||||
|
||||
func audioFileType(fileExtension: String) -> AudioFileTypeID {
|
||||
|
||||
+4
-6
@@ -13,8 +13,6 @@ import XCTest
|
||||
class MetadataStreamProcessorTests: XCTestCase {
|
||||
var metadataDelegateSpy = MetadataDelegateSpy()
|
||||
|
||||
let bundle = Bundle(for: MetadataStreamProcessorTests.self)
|
||||
|
||||
func test_Processor_SendsCorrectValues_IfItCanProcessMetadata() throws {
|
||||
let parser = MetadataParser()
|
||||
let processor = MetadataStreamProcessor(parser: parser.eraseToAnyParser())
|
||||
@@ -36,7 +34,7 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func test_Processor_Outputs_Correct_Metadata_ForStep_WithEmptyMetadata() throws {
|
||||
let url = bundle.url(forResource: "raw-stream-audio-empty-metadata", withExtension: nil)!
|
||||
let url = Bundle.module.url(forResource: "raw-audio-streams/raw-stream-audio-empty-metadata", withExtension: nil)!
|
||||
|
||||
let data = try Data(contentsOf: url)
|
||||
|
||||
@@ -54,7 +52,7 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func test_Processor_Outputs_Correct_Metadata_ForStep_WithMetadata() throws {
|
||||
let url = bundle.url(forResource: "raw-stream-audio-normal-metadata", withExtension: nil)!
|
||||
let url = Bundle.module.url(forResource: "raw-audio-streams/raw-stream-audio-normal-metadata", withExtension: nil)!
|
||||
|
||||
let data = try Data(contentsOf: url)
|
||||
|
||||
@@ -72,7 +70,7 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func test_Processor_Outputs_Correct_Metadata_ForStep_WithMetadata_Alt() throws {
|
||||
let url = bundle.url(forResource: "raw-stream-audio-normal-metadata-alt", withExtension: nil)!
|
||||
let url = Bundle.module.url(forResource: "raw-audio-streams/raw-stream-audio-normal-metadata-alt", withExtension: nil)!
|
||||
|
||||
let data = try Data(contentsOf: url)
|
||||
|
||||
@@ -94,7 +92,7 @@ class MetadataStreamProcessorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func test_Processor_Outputs_Correct_Metadata_ForStep_NoMetadata() throws {
|
||||
let url = bundle.url(forResource: "raw-stream-audio-no-metadata", withExtension: nil)!
|
||||
let url = Bundle.module.url(forResource: "raw-audio-streams/raw-stream-audio-no-metadata", withExtension: nil)!
|
||||
|
||||
let data = try Data(contentsOf: url)
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"originHash" : "68edf8cb05464ad5cb63fbf7d9ab8c54c5bfd1afcf71a774b97ff4f35f7e3fd1",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "ogg-binary-xcframework",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sbooth/ogg-binary-xcframework",
|
||||
"state" : {
|
||||
"revision" : "c0e822e18738ad913864e98d9614927ac1e9337c",
|
||||
"version" : "0.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "vorbis-binary-xcframework",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sbooth/vorbis-binary-xcframework",
|
||||
"state" : {
|
||||
"revision" : "842020eabcebe410e698c68545d6597b2d232e51",
|
||||
"version" : "0.1.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
+39
-9
@@ -1,24 +1,55 @@
|
||||
// swift-tools-version:5.9
|
||||
// swift-tools-version:5.10
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "AudioStreaming",
|
||||
platforms: [
|
||||
.iOS(.v12),
|
||||
.iOS(.v15),
|
||||
.macOS(.v13),
|
||||
.tvOS(.v16)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "AudioStreaming",
|
||||
targets: ["AudioStreaming"]
|
||||
targets: ["AudioCodecs", "AudioStreaming"]
|
||||
),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/sbooth/ogg-binary-xcframework", exact: "0.1.2"),
|
||||
.package(url: "https://github.com/sbooth/vorbis-binary-xcframework", exact: "0.1.2")
|
||||
],
|
||||
targets: [
|
||||
// C target for audio codec bridges
|
||||
.target(
|
||||
name: "AudioCodecs",
|
||||
dependencies: [
|
||||
.product(name: "ogg", package: "ogg-binary-xcframework"),
|
||||
.product(name: "vorbis", package: "vorbis-binary-xcframework")
|
||||
],
|
||||
path: "AudioCodecs",
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [
|
||||
.headerSearchPath("."),
|
||||
.headerSearchPath("include")
|
||||
],
|
||||
linkerSettings: [
|
||||
.linkedFramework("AudioToolbox"),
|
||||
.linkedFramework("Foundation")
|
||||
]
|
||||
),
|
||||
|
||||
// Main Swift target
|
||||
.target(
|
||||
name: "AudioStreaming",
|
||||
path: "AudioStreaming"
|
||||
dependencies: [
|
||||
"AudioCodecs",
|
||||
.product(name: "ogg", package: "ogg-binary-xcframework"),
|
||||
.product(name: "vorbis", package: "vorbis-binary-xcframework")
|
||||
],
|
||||
path: "AudioStreaming",
|
||||
exclude: ["AudioStreaming.h", "Streaming/OggVorbis", "Info.plist"],
|
||||
swiftSettings: []
|
||||
),
|
||||
.testTarget(
|
||||
name: "AudioStreamingTests",
|
||||
@@ -26,12 +57,11 @@ let package = Package(
|
||||
"AudioStreaming"
|
||||
],
|
||||
path: "AudioStreamingTests",
|
||||
exclude: ["Info.plist", "Streaming/output"],
|
||||
resources: [
|
||||
.copy("Streaming/Metadata Stream Processor/raw-audio-streams/raw-stream-audio-empty-metadata"),
|
||||
.copy("Streaming/Metadata Stream Processor/raw-audio-streams/raw-stream-audio-no-metadata"),
|
||||
.copy("Streaming/Metadata Stream Processor/raw-audio-streams/raw-stream-audio-normal-metadata"),
|
||||
.copy("Streaming/Metadata Stream Processor/raw-audio-streams/raw-stream-audio-normal-metadata-alt")
|
||||
]
|
||||
// Test resources for metadata stream processor tests
|
||||
.copy("Streaming/Metadata Stream Processor/raw-audio-streams")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -8,16 +8,23 @@ Under the hood `AudioStreaming` uses `AVAudioEngine` and `CoreAudio` for playbac
|
||||
#### Supported audio
|
||||
- Online streaming (Shoutcast/ICY streams) with metadata parsing
|
||||
- AIFF, AIFC, WAVE, CAF, NeXT, ADTS, MPEG Audio Layer 3, AAC audio formats
|
||||
- M4A
|
||||
- M4A (optimized and non-optimized) from v1.2.0
|
||||
- **Ogg Vorbis** (both local and remote files) ✨
|
||||
|
||||
As of 1.2.0 version, there's support for non-optimized M4A, please report any issues
|
||||
#### Known limitations
|
||||
|
||||
Known limitations:
|
||||
~~- As described above non-optimised M4A files are not supported this is a limitation of [AudioFileStream Services](https://developer.apple.com/documentation/audiotoolbox/audio_file_stream_services?language=swift)~~
|
||||
**Ogg Vorbis Seeking:**
|
||||
- Seeking is **not supported** for Ogg Vorbis files in the current release
|
||||
- This is due to technical challenges with the Ogg container format over HTTP streaming:
|
||||
- Seeking requires finding precise Ogg page boundaries in the stream
|
||||
- The Vorbis decoder needs the full headers (identification, comment, and setup packets) to initialize, which are only available at the beginning of the file
|
||||
- HTTP range requests need to be carefully orchestrated to fetch headers and seek to the correct position
|
||||
- Your UI can check `player.isSeekable` to determine if seeking is available for the currently playing file
|
||||
- Future releases may add experimental support for seeking using progressive download or intelligent header caching
|
||||
|
||||
|
||||
# Requirements
|
||||
- iOS 13.0+
|
||||
- iOS 15.0+
|
||||
- macOS 13.0+
|
||||
- tvOS 16.0+
|
||||
- Swift 5.x
|
||||
@@ -170,6 +177,33 @@ Under the hood the concrete class for frame filters, `FrameFilterProcessor` inst
|
||||
On Xcode 11.0+ you can add a new dependency by going to **File / Swift Packages / Add Package Dependency...**
|
||||
and enter package repository URL https://github.com/dimitris-c/AudioStreaming.git, then follow the instructions.
|
||||
|
||||
# Development
|
||||
|
||||
### Testing
|
||||
|
||||
This package uses Swift Package Manager for development and testing. To run tests:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
swift test
|
||||
|
||||
# Run tests in parallel for faster execution
|
||||
swift test --parallel
|
||||
|
||||
# Build the package
|
||||
swift build
|
||||
```
|
||||
|
||||
### Opening in Xcode
|
||||
|
||||
You can open the package directly in Xcode:
|
||||
|
||||
```bash
|
||||
open Package.swift
|
||||
```
|
||||
|
||||
Or simply double-click the `Package.swift` file. Xcode will automatically resolve dependencies and make the package ready for development.
|
||||
|
||||
# Licence
|
||||
|
||||
AudioStreaming is available under the MIT license. See the LICENSE file for more info.
|
||||
|
||||
Reference in New Issue
Block a user