Skip to content

VasilyPolyuhovich/MeditationPlayer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

32 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

ProsperPlayer ๐ŸŽต

Modern audio player SDK for iOS with advanced playback features, overlay audio system, sound effects, and production-grade stability

๐Ÿš€ Quick Start

// Initialize service (setup is automatic!)
let service = try await AudioPlayerService()

// Configure playback
let config = PlayerConfiguration(
    crossfadeDuration: 10.0,
    volume: 0.8,  // 0.0-1.0
    repeatMode: .playlist
)

// Start playlist playback
try await service.loadPlaylist(trackURLs, configuration: config)

// Control playback
try await service.pause()
try await service.resume()
try await service.skipToNext()
try await service.skip(forward: 15.0)

// Overlay audio (ambient sounds, voiceovers)
let overlayConfig = OverlayConfiguration.ambient
try await service.playOverlay(url: rainURL, configuration: overlayConfig)

// Sound effects
let bell = SoundEffect(url: bellURL, volume: 0.8)
await service.preloadSoundEffects([bell])
await service.playSoundEffect(bell)

๐ŸŽฏ Key Features

Audio Playback

  • โœ… High-quality audio with AVAudioEngine (8192-sample buffers for stability)
  • โœ… Dual-player crossfade architecture with Equal-Power algorithm
  • โœ… Playlist management with auto-advance and cyclic navigation
  • โœ… 5 fade curve types (Equal-Power, Linear, Logarithmic, Exponential, S-Curve)
  • โœ… Loop playback with seamless crossfade
  • โœ… Production-grade stability (optimized for Bluetooth/AirPods)
  • โœ… Type-safe Track model

Overlay Audio System

  • โœ… Independent audio layer - plays alongside main track
  • โœ… Unified API - playOverlay() for start/replace operations
  • โœ… Dynamic configuration - adjust volume and loop settings in runtime
  • โœ… Configurable delays - adjust timing between iterations (0-30s)
  • โœ… Preset configurations - .default, .ambient, .bell()

Sound Effects ๐Ÿ†•

  • โœ… LRU cache - auto-manages up to 10 effects
  • โœ… Instant playback - <5ms latency for preloaded effects
  • โœ… Master volume - adjust all effects without reload
  • โœ… Batch operations - preload/unload multiple effects
  • โœ… Auto-preload - smart loading with warnings

Platform Integration

  • โœ… Swift 6 strict concurrency compliance
  • โœ… Background audio & Lock Screen controls
  • โœ… Customizable remote commands - delegate for lock screen/Control Center
  • โœ… Skip forward/backward (configurable intervals)
  • โœ… Click-free seek with fade
  • โœ… Advanced AudioSession configuration (minimizes interruptions)

๐Ÿ—๏ธ Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚      AudioPlayerService (Actor)         โ”‚
โ”‚  - State management                     โ”‚
โ”‚  - Playlist logic                       โ”‚
โ”‚  - Overlay coordination                 โ”‚
โ”‚  - Sound effects management             โ”‚
โ”‚  - Public API                           โ”‚
โ”‚  - Observer pattern                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
             โ”‚
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚                โ”‚              โ”‚
โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚AudioEngine  โ”‚  โ”‚PlaylistMgr   โ”‚  โ”‚SoundEffects   โ”‚
โ”‚Actor        โ”‚  โ”‚(Actor)       โ”‚  โ”‚Actor ๐Ÿ†•       โ”‚
โ”‚             โ”‚  โ”‚              โ”‚  โ”‚               โ”‚
โ”‚- Dual-playerโ”‚  โ”‚- Track queue โ”‚  โ”‚- LRU cache    โ”‚
โ”‚- Crossfade  โ”‚  โ”‚- Auto-advanceโ”‚  โ”‚- Master vol   โ”‚
โ”‚- Buffers    โ”‚  โ”‚- Navigation  โ”‚  โ”‚- Batch ops    โ”‚
โ”‚- Overlay โœจ โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
      โ”‚
      โ””โ”€โ”€โ–บ OverlayPlayerActor
           - Independent lifecycle
           - Loop with delay
           - Configurable fades
           - Volume control

Design principles:

  • Actor isolation (Swift 6 data race prevention)
  • Dual-player pattern (seamless crossfades)
  • Sample-accurate synchronization (AVAudioTime)
  • Equal-Power algorithm (constant perceived loudness)
  • Large buffers (8192 samples) for Bluetooth stability
  • SDK-level playlist management
  • Singleton AudioSession (shared across all player instances)

Multiple Instances Support ๐Ÿ†•

You can create multiple AudioPlayerService instances with different configurations:

// Component 1: Meditation player
let meditationPlayer = try await AudioPlayerService()
let config1 = PlayerConfiguration(crossfadeDuration: 10.0, volume: 0.8)
try await meditationPlayer.loadPlaylist(meditationTracks, configuration: config1)

// Component 2: Music player
let musicPlayer = try await AudioPlayerService()
let config2 = PlayerConfiguration(crossfadeDuration: 5.0, volume: 1.0)
try await musicPlayer.loadPlaylist(musicTracks, configuration: config2)

How it works:

  • AudioSessionManager is a singleton (shared across all instances)
  • AVAudioSession is configured once globally (first instance wins)
  • Each player has its own state, playlist, and audio engine
  • No manual setup() needed - initialization is automatic!

๐Ÿ› ๏ธ Tech Stack

  • Language: Swift 6.0
  • UI Framework: SwiftUI
  • Concurrency: Swift Concurrency (async/await, actors)
  • Audio: AVFoundation (AVAudioEngine, AVAudioSession)
  • Package Manager: Swift Package Manager
  • Platform: iOS 15+

๐Ÿ“ฆ Modules

AudioServiceCore

Core domain models and protocols:

  • PlayerConfiguration - Immutable playback configuration
  • OverlayConfiguration - Overlay audio settings (loop, delay, fades)
  • Track ๐Ÿ†• - Type-safe track model
  • SoundEffect ๐Ÿ†• - Sound effect descriptor
  • AudioPlayerError - Error types
  • PlayerState / OverlayState - State machine states

AudioServiceKit

Main implementation:

  • AudioPlayerService - Public API (actor-isolated)
  • PlaylistManager - Playlist management
  • AudioEngineActor - AVAudioEngine wrapper with enhanced stability
  • OverlayPlayerActor - Independent overlay audio system
  • SoundEffectsPlayerActor ๐Ÿ†• - Sound effects with LRU cache
  • AudioSessionManager - Advanced session configuration
  • RemoteCommandManager - Lock Screen controls

๐Ÿšฆ Installation

Requirements

  • Xcode 15.0+
  • iOS 15.0+
  • Swift 6.0+
  • Physical device recommended for audio testing (especially Bluetooth)

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/VasilyPolyuhovich/ProsperPlayer.git", branch: "main")
]

Manual

git clone [repository-url]
cd ProsperPlayer
swift build

๐Ÿ“š Examples

Basic Playlist

let service = try await AudioPlayerService()

let config = PlayerConfiguration(
    crossfadeDuration: 10.0,
    fadeCurve: .equalPower,
    repeatMode: .playlist,
    volume: 0.8  // 0.0-1.0
)

// Load with URLs
try await service.loadPlaylist(trackURLs, configuration: config)

// Or load with Track models (type-safe)
let tracks = trackURLs.map { Track(url: $0) }
try await service.loadPlaylist(tracks, configuration: config)

Overlay Audio (Ambient Sounds)

// Start continuous rain sound
let config = OverlayConfiguration.ambient  // Infinite loop, 30% volume
try await service.playOverlay(url: rainURL, configuration: config)

// Or with Track model
let rainTrack = Track(url: rainURL)
try await service.playOverlay(rainTrack, configuration: config)

// Adjust settings in runtime
await service.setOverlayVolume(0.5)
await service.setOverlayConfiguration(.default)

// Replace with ocean sound (reuses configuration)
try await service.playOverlay(url: oceanURL)

// Stop overlay (main track continues)
await service.stopOverlay()

Timer Bell (Periodic Sound)

// Bell rings 3 times with 5-minute intervals
let config = OverlayConfiguration.bell(times: 3, interval: 300)
try await service.playOverlay(url: bellURL, configuration: config)

// Timeline:
// 0:00  โ†’ fadeIn โ†’ DING โ†’ fadeOut โ†’ [5 min silence]
// 5:00  โ†’ fadeIn โ†’ DING โ†’ fadeOut โ†’ [5 min silence]
// 10:00 โ†’ fadeIn โ†’ DING โ†’ fadeOut

Sound Effects ๐Ÿ†•

// Create sound effects
let bell = SoundEffect(url: bellURL, volume: 1.0, fadeIn: 0.1, fadeOut: 0.3)
let gong = SoundEffect(url: gongURL, volume: 1.0, fadeIn: 0.1, fadeOut: 0.3)

// Batch preload (recommended)
await service.preloadSoundEffects([bell, gong])

// Play instantly (<5ms latency)
await service.playSoundEffect(bell, fadeDuration: 0.1)

// Master volume control (no reload needed!)
await service.setSoundEffectVolume(0.5)  // 50% of original volume

// Manual cleanup (optional - LRU handles this)
await service.unloadSoundEffects([bell])

Track Navigation

// Auto-advance enabled by default
// Manual navigation:
try await service.skipToNext()     // Cyclic (wraps to first)
try await service.skipToPrevious() // Cyclic (wraps to last)

// Skip within track
try await service.skip(forward: 15.0)
try await service.skip(backward: 15.0)

// Seek with fade
try await service.seek(to: 60.0, fadeDuration: 0.5)

Remote Command Customization ๐Ÿ†•

Customize lock screen and Control Center behavior:

// Create custom delegate
@MainActor
class MyRemoteDelegate: RemoteCommandDelegate {
    
    // Configure which commands are enabled
    func remoteCommandEnabledCommands() -> RemoteCommandOptions {
        [.play, .pause, .skipForward, .skipBackward]
    }
    
    // Custom skip intervals (default: 15s)
    func remoteCommandSkipIntervals() -> (forward: TimeInterval, backward: TimeInterval) {
        (forward: 30.0, backward: 30.0)
    }
    
    // Custom Now Playing info
    func remoteCommandNowPlayingInfo(
        for track: Track.Metadata,
        position: PlaybackPosition
    ) -> [String: Any]? {
        return [
            MPMediaItemPropertyTitle: "Custom Title",
            MPMediaItemPropertyArtist: "My App",
            MPMediaItemPropertyPlaybackDuration: position.duration,
            MPNowPlayingInfoPropertyElapsedPlaybackTime: position.currentTime,
            MPNowPlayingInfoPropertyPlaybackRate: 1.0
        ]
    }
    
    // Custom skip handler (e.g., chapter navigation)
    func remoteCommandShouldHandleSkipForward(_ interval: TimeInterval) async -> Bool {
        await chapterManager.next()
        return false  // Handled - skip SDK default behavior
    }
}

// Set delegate
let delegate = MyRemoteDelegate()
await service.setRemoteCommandDelegate(delegate)

// Reset to defaults
await service.setRemoteCommandDelegate(nil)

Available options:

  • RemoteCommandOptions: .play, .pause, .stop, .skipForward, .skipBackward, .nextTrack, .previousTrack, .seekTo, .changePlaybackRate
  • Presets: .playbackOnly, .standard, .full

State Observation

actor Observer: AudioPlayerObserver {
    func playerStateDidChange(_ state: PlayerState) async {
        print("State: \(state)")
    }

    func playbackPositionDidUpdate(_ position: PlaybackPosition) async {
        print("Position: \(position.currentTime)")
    }
}

await service.addObserver(Observer())

๐ŸŽฎ Full API Reference

Playlist Management

// Load playlist
try await service.loadPlaylist([url1, url2, url3], configuration: config)
try await service.loadPlaylist([track1, track2], configuration: config)

// Replace playlist
try await service.replacePlaylist([url4, url5])
try await service.replacePlaylist([track4, track5])

// Query
let playlist = await service.playlist  // Property, not getter!

Playback Control

try await service.startPlaying(fadeDuration: 3.0)
try await service.pause()
try await service.resume()
await service.stop(fadeDuration: 3.0)  // Non-optional parameter

Navigation

try await service.skipToNext()
try await service.skipToPrevious()
try await service.skip(forward: 15.0)
try await service.skip(backward: 15.0)
try await service.seek(to: position, fadeDuration: 0.5)

Configuration

await service.setVolume(0.8)
let repeatMode = await service.repeatMode  // Property
let repeatCount = await service.repeatCount // Property

Overlay API

// Start/Replace (unified API)
try await service.playOverlay(url: URL, configuration: OverlayConfiguration)
try await service.playOverlay(track: Track, configuration: OverlayConfiguration)
await service.stopOverlay(fadeDuration: 1.0)

// Playback control
await service.pauseOverlay()
await service.resumeOverlay()

// Dynamic configuration
await service.setOverlayVolume(0.5)
await service.setOverlayConfiguration(.ambient)
await service.setOverlayLoopDelay(10.0)

// Query
let state = await service.overlayState  // Property
let config = await service.getOverlayConfiguration()

Sound Effects API ๐Ÿ†•

// Batch preload
await service.preloadSoundEffects([effect1, effect2, effect3])

// Play (auto-preloads if not in cache)
await service.playSoundEffect(effect, fadeDuration: 0.1)

// Stop current
await service.stopSoundEffect(fadeDuration: 0.3)

// Master volume (dynamic, no reload!)
await service.setSoundEffectVolume(0.7)

// Manual cleanup
await service.unloadSoundEffects([effect1, effect2])

// Query
let current = await service.currentSoundEffect  // Property

Global Control

// Pause/Resume main + overlay + sound effects
await service.pauseAll()
await service.resumeAll()

// Emergency stop
await service.stopAll()

๐ŸŽ›๏ธ Audio Stability Configuration

ProsperPlayer includes production-grade audio stability optimizations:

Advanced AudioSession Setup

// Automatically configured by AudioSessionManager:
- preferredIOBufferDuration: 20ms (smooth, low-latency)
- preferredSampleRate: 44100 Hz (avoid resampling)
- prefersNoInterruptionsFromSystemAlerts: true (iOS 14.5+)
- Validation warnings for hardware mismatches

Larger Audio Buffers

  • 8192 samples (186ms at 44.1kHz) - prevents artifacts with:
    • Bluetooth headphones / AirPods
    • Heavy UI operations (scrolling, animations)
    • System load and multi-app audio conflicts

Trade-offs

  • Latency: +93ms vs smaller buffers (acceptable for meditation/ambient apps)
  • Stability: Zero audio artifacts under normal conditions
  • CPU Usage: Minimal increase (<1%)

๐Ÿงช Testing

๐ŸŽš๏ธ Audio Session Management

App developer configures AVAudioSession before creating the player. SDK validates session state, warns about incompatibilities, and recovers its own AVAudioEngine.

Setup (Required)

// 1. Configure audio session (app responsibility)
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playback)
try session.setActive(true)

// 2. Create player โ€” SDK validates session at init
let service = try await AudioPlayerService()

SDK behavior:

  • โœ… Validates session category is compatible (.playback, .playAndRecord, .multiRoute)
  • โœ… Warns in console if Bluetooth or speaker routing is suboptimal
  • โœ… Recovers AVAudioEngine if iOS stops it due to interruption or category change
  • โŒ Does NOT set category or options โ€” that's your responsibility

Session Delegate

Register a delegate to receive notifications when session state changes:

class MyHandler: AudioPlayerSessionDelegate {
    func audioPlayerSessionCategoryDidChange(
        validation: SessionValidationResult
    ) async {
        // Restore session and resume if needed
        try? AVAudioSession.sharedInstance().setCategory(.playback)
        try? await player.resume()
    }
}

player.sessionDelegate = handler

Recording + Playback

If you need recording capability, use .playAndRecord with .defaultToSpeaker:

try session.setCategory(
    .playAndRecord,
    options: [
        .defaultToSpeaker,    // โš ๏ธ REQUIRED: Routes to speaker, not earpiece
        .allowBluetoothA2DP
    ]
)

Why .defaultToSpeaker?

  • Without it: audio routes to earpiece (quiet, low quality)
  • With it: audio routes to speaker (proper volume, high quality)

Validation & Debugging

External mode includes comprehensive validation:

4 Validation Checks:

  1. Category compatibility - throws error if incompatible (e.g., .record category)
  2. Session active - warns if not activated
  3. .playAndRecord needs .defaultToSpeaker - warns if missing
  4. Bluetooth support - shows error if missing (uses Logger.error() - always visible!)

Example error output (missing Bluetooth):

โš ๏ธโš ๏ธ BLUETOOTH NOT ENABLED โš ๏ธโš ๏ธ

Your audio session does NOT support Bluetooth devices!

Issues you may experience:
  โŒ Audio won't route to Bluetooth headphones/speakers
  โŒ Connecting Bluetooth device won't switch audio
  โŒ User will only hear audio from iPhone speaker

To fix, choose one of these configurations:

Option 1: .playback category (recommended for music/meditation)
  [exact code provided]

Option 2: .playAndRecord category (if you need recording)
  [exact code with .defaultToSpeaker]

๐Ÿ’ก Tip: Check ExternalModeDemo in ProsperPlayerDemo for interactive examples!


๐Ÿงช Testing

Run tests on physical device recommended (simulator lacks full audio support):

# Run all tests
swift test

# Run specific test
swift test --filter AudioPlayerServiceTests

# With Thread Sanitizer
swift test -Xswiftc -sanitize=thread

Manual Testing:

  1. Open Examples/ProsperPlayerDemo
  2. Run on physical iOS device
  3. Test with Bluetooth headphones / AirPods
  4. Verify zero audio artifacts during:
    • Heavy UI scrolling
    • App switching
    • Phone calls (interruption handling)
    • System alerts

๐Ÿ“Š Performance

Memory Footprint

  • Single track: ~10MB (typical 5min @ 128kbps)
  • During crossfade: ~20MB (dual-player)
  • With overlay: ~30MB (triple-player)
  • Sound effects cache: ~5-50MB (10 effects max)
  • Post-crossfade: ~10MB (old track released)

Sound Effects Performance ๐Ÿ†•

  • Preloaded latency: <5ms (instant)
  • Auto-preload latency: 50-200ms (disk read)
  • LRU cache: 10 effects (configurable)
  • Memory per effect: ~50-500KB (depends on duration)

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ“ฎ Contact

๐Ÿ™ Acknowledgments

  • Equal-Power crossfade algorithm based on AES standards
  • Swift 6 strict concurrency patterns
  • AVFoundation best practices (WWDC 2014-2024)
  • Audio stability optimizations based on production feedback

๐Ÿ”„ Migration to v4.1.4

Breaking Changes

Async Throws Initialization

v4.1.1-4.1.3:

let player = AudioPlayerService()

v4.1.4:

let player = try await AudioPlayerService()

Why? Proper error propagation from audio engine setup. If stereo format creation fails, you'll now receive a clear error instead of silent failure.

Migration steps:

  1. Add try await to all AudioPlayerService initializations:
// Before
let audioService = AudioPlayerService()

// After
let audioService = try await AudioPlayerService()
  1. Update dependency injection containers:
// Before (Factory DI)
var audioPlayerService: Factory<AudioPlayerService> {
    self { AudioPlayerService() }
}

// After
@MainActor
func createAudioPlayerService() async throws -> AudioPlayerService {
    try await AudioPlayerService(configuration: .default)
}
  1. Handle errors in SwiftUI:
struct ContentView: View {
    @State private var audioService: AudioPlayerService?
    @State private var error: Error?
    
    var body: some View {
        if let audioService = audioService {
            PlayerView(audioService: audioService)
        } else if let error = error {
            ErrorView(error: error)
        } else {
            ProgressView()
                .task {
                    do {
                        audioService = try await AudioPlayerService()
                    } catch {
                        self.error = error
                    }
                }
        }
    }
}

Audio Routing Fix

What changed: Added .defaultToSpeaker to default audio session options.

Impact: Audio now plays through loudspeaker instead of ear speaker when using .playAndRecord category.

No migration needed - this fix is automatic!


๐Ÿ”„ Migration from v4.1.0 to v4.1.1

Breaking Changes

1. No Manual setup() Required

OLD (v4.1.0):

let player = AudioPlayerService()
await player.setup()  // โŒ Required manual call
try await player.loadPlaylist(tracks, configuration: config)

v4.1.1:

let player = AudioPlayerService()
// โœ… No setup() needed - automatic!
try await player.loadPlaylist(tracks, configuration: config)

NEW (v4.1.4):

let player = try await AudioPlayerService()
// โœ… Async throws init with proper error handling
try await player.loadPlaylist(tracks, configuration: config)

Migration: Simply remove all await service.setup() calls from your code.


2. Multiple Instances Now Supported

Problem in v4.1.0: Creating multiple instances caused error -50.

Fixed in v4.1.1:

// Both instances work correctly now!
let player1 = try await AudioPlayerService()
let player2 = try await AudioPlayerService()  // โœ… No error!

// Each with different configurations
try await player1.loadPlaylist(tracks1, configuration: config1)
try await player2.loadPlaylist(tracks2, configuration: config2)

How it works:

  • AudioSessionManager is now a singleton (shared globally)
  • AVAudioSession configured once (first instance wins)
  • Each player has independent state and playlist

3. AudioSession Options Conflicts

โš ๏ธ Important: If different instances use different audioSessionOptions, the first one wins:

// Player 1 - sets options
let config1 = PlayerConfiguration(
    audioSessionOptions: [.mixWithOthers, .duckOthers]
)
try await player1.loadPlaylist(tracks1, configuration: config1)  // โœ… Applied

// Player 2 - different options
let config2 = PlayerConfiguration(
    audioSessionOptions: []  // โš ๏ธ Ignored! Player1's options used
)
try await player2.loadPlaylist(tracks2, configuration: config2)
// Console: [AudioSession] โš ๏ธ WARNING: Attempting to reconfigure with different options!

Best Practice: Use same options for all instances:

let sharedOptions: [AVAudioSession.CategoryOptions] = [.mixWithOthers, .duckOthers]
let config1 = PlayerConfiguration(audioSessionOptions: sharedOptions)
let config2 = PlayerConfiguration(audioSessionOptions: sharedOptions)

๐Ÿ†• What's New in v4.4

Remote Command Customization ๐Ÿ†•

  • RemoteCommandDelegate - customize lock screen and Control Center behavior
  • Custom Now Playing info - full control over displayed metadata
  • Configurable commands - enable/disable specific remote commands
  • Custom handlers - intercept commands for custom behavior (e.g., chapter navigation)
  • Adjustable skip intervals - change from default 15s to any value

What's New in v4.1.4

Error Handling

  • Throwing initialization - AudioPlayerService.init() now async throws
  • Proper error propagation - audio engine setup errors are caught and reported
  • Clear error messages - AudioPlayerError.engineStartFailed with detailed reasons
  • No silent failures - stereo format creation errors are visible

Audio Routing Fix

  • Loudspeaker routing - added .defaultToSpeaker option
  • Correct speaker selection - uses loudspeaker (music) instead of ear speaker (calls)
  • Better audio quality - proper volume levels with .playAndRecord category

๐Ÿ†• What's New in v4.1

Sound Effects System

  • LRU cache with automatic management (10 effects limit)
  • Master volume control - adjust all effects instantly without reload
  • Batch operations - preload/unload multiple effects at once
  • Auto-preload - smart loading with console warnings
  • Instant playback - <5ms latency for preloaded effects

API Improvements

  • Unified Overlay API - playOverlay() replaces start/replace
  • Track model - type-safe audio file handling
  • Properties - repeatMode, playlist, currentSoundEffect instead of getters
  • Renamed methods - skip(forward:), skip(backward:), seek(to:fadeDuration:)
  • Reduced API surface - removed 15 deprecated methods (-25%)

Bug Fixes

  • Fixed telephone call interruption handling
  • Fixed Bluetooth route change crashes (300ms debounce)
  • Fixed media services reset position preservation
  • Fixed AVAudioEngine overlay node crashes
  • Fixed state oscillation during crossfade pause

Version: 4.4.0 Platform: iOS 15+ Build: Swift License

About

Modern audio player SDK for iOS with advanced playback features, overlay audio system, sound effects, and production-grade stability

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages