JSPM

z-web-audio-stream

1.4.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 7
  • Score
    100M100P100Q44069F
  • License MIT

iOS Safari-safe Web Audio streaming with separated download/storage optimization, instant playback, and memory management

Package Exports

  • z-web-audio-stream
  • z-web-audio-stream/worklet

Readme

web-audio-stream

iOS Safari-safe Web Audio streaming with separated download/storage optimization, instant playback and memory management.

✨ Features

  • 🚀 Instant Playback: Start playing within 100-500ms using separated download strategy
  • 📡 Download Optimization: Network-optimized chunks (64KB-512KB) separate from storage chunks (1-3MB)
  • 🔄 Streaming Assembly: Real-time chunk assembly for seamless playback transitions
  • 🍎 iOS Safari Compatibility: Fixes pitch/speed issues and prevents page reloads
  • ⚡ Progressive Loading: Background streaming with seamless buffer replacement
  • 🎯 Range Request Support: Parallel downloads with configurable concurrency
  • 💾 Smart Caching: IndexedDB storage with automatic cleanup
  • 🔊 AudioWorklet: High-performance audio processing
  • 📱 Memory Safe: Adaptive chunk sizing to prevent iOS crashes
  • 📊 Performance Monitoring: Real-time metrics and adaptive optimization
  • 🎵 Audio Management: Full audio state tracking with duration, cache, and memory info
  • 🔧 Easy Setup: Simple API with TypeScript support

🚨 iOS Safari Issues This Fixes

  1. Sample Rate Mismatches: Causes high-pitched/fast audio playback
  2. Memory Pressure: Large audio files cause page reloads on iOS
  3. IndexedDB Failures: Safari iOS fails IndexedDB operations randomly
  4. AudioContext Bugs: Broken state detection and recovery

📦 Installation

npm install z-web-audio-stream
# or
pnpm add z-web-audio-stream
# or  
yarn add z-web-audio-stream

🚀 Quick Start

1. Copy the AudioWorklet file

First, copy the AudioWorklet processor to your public directory:

# Using CLI (recommended)
npx z-web-audio-stream-cli deploy

# Or manually copy from node_modules
cp node_modules/z-web-audio-stream/dist/audio-worklet-processor.js public/
import { setupInstantAudio } from 'z-web-audio-stream';

// Initialize with separated download/storage optimization
const manager = await setupInstantAudio({
  downloadChunkSize: 256 * 1024,    // 256KB downloads for network optimization
  storageChunkSize: 2 * 1024 * 1024, // 2MB chunks for IndexedDB efficiency
  playbackChunkSize: 384 * 1024,     // 384KB for instant playback start
  enablePerformanceLogging: true,     // See detailed performance metrics
  onTimeUpdate: (currentTime, duration) => {
    console.log(`Playing: ${currentTime}s / ${duration}s`);
  },
  onProgressiveLoadingStatus: (status, data) => {
    if (status === 'STARTED') {
      console.log('🚀 Separated instant playback started!');
      console.log(`Strategy: ${data.strategy}`);
    }
  }
});

// Start playing instantly - audio begins within 500ms
await manager.playInstantly('/audio/song.mp3', 'song-1', 'My Song');

🏗️ Separated Download/Storage Architecture

WebAudioStream v1.2.0+ uses a sophisticated three-layer architecture that separates concerns for optimal performance:

📡 Layer 1: Download Manager

  • Purpose: Network transfer optimization
  • Chunk Size: 64KB-512KB (optimized for HTTP/2 and mobile networks)
  • Features: Parallel downloads, range requests, connection speed adaptation
  • Benefits: Faster initial response, better network utilization

🔄 Layer 2: Streaming Assembler

  • Purpose: Real-time chunk assembly and playback preparation
  • Chunk Size: 256KB-384KB for first playback chunk, larger for storage
  • Features: Streaming assembly, immediate playback readiness detection
  • Benefits: Sub-500ms playback start, seamless transitions

💾 Layer 3: Storage Manager

  • Purpose: IndexedDB optimization and memory management
  • Chunk Size: 1-3MB (optimized for browser storage efficiency)
  • Features: iOS Safari retry logic, automatic cleanup, obfuscation
  • Benefits: Reliable caching, memory safety, privacy protection
Download (256KB) → Assembly (384KB) → Storage (2MB) → Playback
     ↓               ↓                 ↓            ↓
Fast Network    Instant Start    Efficient Cache  Smooth Audio

3. Basic Usage

import { setupWebAudio } from 'z-web-audio-stream';

// Initialize with iOS-safe defaults
const manager = await setupWebAudio({
  enableInstantPlayback: true, // Enable instant playback
  onTimeUpdate: (currentTime, duration) => {
    console.log(`Playing: ${currentTime}s / ${duration}s`);
  },
  onEnded: () => {
    console.log('Playback finished');
  },
  onError: (error) => {
    console.error('Playback error:', error);
  }
});

// Traditional loading (waits for full download)
await manager.loadAndPlay('/audio/song.mp3', 'song-1', 'My Song');

// OR use instant playback (recommended)
await manager.playInstantly('/audio/song.mp3', 'song-1', 'My Song');

// Control playback
await manager.pause();
await manager.resume();
await manager.seek(30); // Seek to 30 seconds
manager.setVolume(0.8); // 80% volume

4. Audio Management (v1.3.0+)

import { setupInstantAudio } from 'z-web-audio-stream';

const manager = await setupInstantAudio();

// Load and play some tracks
await manager.playInstantly('/audio/song1.mp3', 'song-1', 'First Song');
await manager.playInstantly('/audio/song2.mp3', 'song-2', 'Second Song');

// Check audio state and get metadata
const isLoaded = await manager.isAudioLoaded('song-1');
console.log('Song 1 loaded:', isLoaded); // true

const duration = manager.getBufferDuration('song-1');
console.log('Song 1 duration:', duration); // e.g., 240.5 seconds

// Get all cached tracks with metadata
const cachedTracks = await manager.getCachedTracks();
cachedTracks.forEach(track => {
  console.log(`Track: ${track.name}`);
  console.log(`Duration: ${track.duration}s`);
  console.log(`Size: ${(track.size / 1024 / 1024).toFixed(1)}MB`);
  console.log(`Loaded in memory: ${track.isLoaded}`);
  console.log(`Last accessed: ${track.lastAccessed.toLocaleString()}`);
});

// Perfect for building audio players with:
// - Track duration display
// - Cache management
// - Memory usage optimization
// - Playlist state tracking

5. Privacy & Custom Storage Keys (v1.4.0+)

import { setupInstantAudio, setupWebAudio } from 'z-web-audio-stream';

// Use your own obfuscation key for privacy
const manager = await setupInstantAudio({
  obfuscationKey: 'my-app-secret-key-2024'
});

// Or with basic setup
const basicManager = await setupWebAudio({
  obfuscationKey: 'custom-privacy-key'
});

// Benefits:
// - Your app's cached audio data is obfuscated with your key
// - Prevents other apps from easily reading your cached data
// - Each app/environment can use different keys for data isolation
// - Backward compatible - existing data works with default key

6. Advanced Configuration

import { WebAudioManager, AudioChunkStore } from 'z-web-audio-stream';

const manager = new WebAudioManager({
  workletPath: '/audio-worklet-processor.js',
  enableCache: true,
  enableInstantPlayback: true,
  instantPlaybackConfig: {
    initialChunkSize: 384 * 1024,     // 384KB for instant start
    subsequentChunkSize: 2 * 1024 * 1024,  // 2MB for streaming
    maxInitialWaitTime: 500,          // 500ms max wait
    strategy: 'auto'                  // auto, always, never
  },
  maxCacheSize: 1024 * 1024 * 1024, // 1GB
  onProgressiveLoadingStatus: (status, data) => {
    console.log('Status:', status, data);
  }
});

await manager.initialize();

// Get performance recommendations
const strategy = manager.getPlaybackStrategy('/audio/song.mp3', {
  estimatedFileSize: 5 * 1024 * 1024,
  connectionSpeed: 'medium',
  deviceType: 'mobile'
});
console.log('Recommended strategy:', strategy);

// Use instant playback with progress tracking
await manager.playInstantly('/audio/song.mp3', 'song-1', 'My Song', {
  onChunkLoaded: (loaded, total) => {
    console.log(`Chunks loaded: ${loaded}/${total}`);
  },
  onFullyLoaded: () => {
    console.log('Full audio loaded in background');
  }
});

// Preload for smooth transitions
await manager.preloadAudio('/audio/next-song.mp3', 'song-2', 'Next Song');

// Get performance metrics
const metrics = manager.getInstantPlaybackMetrics();
console.log('Performance metrics:', metrics);

🚀 Instant Playback

How It Works

  1. Smart Chunking: Loads small initial chunk (256-384KB) for instant start
  2. Background Streaming: Continues loading larger chunks (1-2MB) in background
  3. Seamless Replacement: Replaces buffer without interrupting playback
  4. Range Request Support: Uses HTTP Range requests when server supports them
  5. Adaptive Strategy: Automatically chooses best approach based on conditions

Performance Targets

  • Start Time: < 500ms on 3G, < 200ms on WiFi
  • Buffer Switch: < 50ms seamless transitions
  • Memory Usage: Optimized for iOS Safari limits
  • Error Recovery: Graceful fallback to standard loading

Configuration Options

const config: InstantPlaybackConfig = {
  initialChunkSize: 384 * 1024,      // First chunk size (384KB)
  subsequentChunkSize: 2 * 1024 * 1024, // Streaming chunks (2MB)
  predictiveLoadingThreshold: 0.75,   // Start next chunk at 75%
  maxInitialWaitTime: 500,           // Max wait for first chunk
  strategy: 'auto'                   // auto | always | never
};

manager.enableInstantMode(config);

🍎 iOS Safari Optimizations

Automatic Features

  • Sample Rate Monitoring: Detects and fixes iOS sample rate bugs
  • Memory-Safe Chunks: 256KB-1MB chunks on iOS vs 2-8MB on desktop
  • IndexedDB Retry Logic: 3-attempt retry with delays for Safari
  • Broken State Detection: Plays dummy buffer to reset AudioContext
  • Instant Playback Limits: Smaller chunks to prevent iOS memory pressure

iOS-Specific Behavior

import { isIOSSafari } from 'z-web-audio-stream';

if (isIOSSafari()) {
  console.log('iOS Safari detected - optimizations active');
  // All optimizations are automatic:
  // - Smaller chunk sizes
  // - Retry logic
  // - Sample rate monitoring
  // - Memory pressure handling
}

📋 API Reference

WebAudioManager

The main class for audio management.

class WebAudioManager {
  constructor(options?: WebAudioManagerOptions)
  
  // Core methods
  async initialize(): Promise<void>
  async loadAudio(url: string, trackId: string, progressCallback?: Function): Promise<AudioBuffer>
  async loadAndPlay(url: string, trackId: string, name?: string): Promise<void>
  async preloadAudio(url: string, trackId: string, name?: string): Promise<void>
  
  // Instant playback methods
  async playInstantly(url: string, trackId: string, name: string, options?: {
    forceInstant?: boolean;
    onChunkLoaded?: (chunkIndex: number, totalChunks: number) => void;
    onFullyLoaded?: () => void;
  }): Promise<void>
  
  getPlaybackStrategy(url: string, options?: {
    estimatedFileSize?: number;
    connectionSpeed?: 'slow' | 'medium' | 'fast';
    deviceType?: 'mobile' | 'desktop';
  }): { strategy: 'instant' | 'progressive' | 'standard'; reasoning: string; }
  
  enableInstantMode(config?: Partial<InstantPlaybackConfig>): void
  disableInstantMode(): void
  getInstantPlaybackMetrics(): InstantPlaybackMetrics
  
  // Playback control
  async play(trackId: string): Promise<void>
  async pause(): Promise<void>
  async resume(): Promise<void>
  async seek(time: number): Promise<void>
  setVolume(volume: number): void
  getCurrentTime(): number
  
  // Audio management methods (v1.3.0+)
  getBufferDuration(trackId: string): number | null
  async isAudioLoaded(trackId: string): Promise<boolean>
  async getCachedTracks(): Promise<Array<{
    trackId: string;
    name?: string;
    duration?: number;
    size: number;
    lastAccessed: Date;
    isLoaded: boolean;
  }>>
  
  // Cleanup
  async cleanup(): Promise<void>
}

AudioChunkStore

IndexedDB-based storage with iOS Safari compatibility.

class AudioChunkStore {
  constructor(audioContext: AudioContext, instantConfig?: Partial<InstantChunkConfig>)
  
  async initialize(): Promise<void>
  async storeAudio(url: string, trackId: string, name: string, progressCallback?: ProgressCallback): Promise<AudioMetadata>
  async storeAudioStreaming(url: string, trackId: string, name: string, options?: {
    initialChunkSize?: number;
    subsequentChunkSize?: number;
    useRangeRequests?: boolean;
    progressCallback?: ProgressCallback;
  }): Promise<AudioMetadata>
  
  async getAudioBuffer(trackId: string, startChunk?: number, chunkCount?: number): Promise<AudioBuffer | null>
  async getFirstChunk(trackId: string): Promise<AudioBuffer | null>
  async getProgressiveChunks(trackId: string, startChunk?: number, maxChunks?: number): Promise<AudioBuffer | null>
  
  async isStored(trackId: string): Promise<boolean>
  async removeTrack(trackId: string): Promise<void>
  async cleanup(): Promise<void>
  
  configureInstantMode(config: Partial<InstantChunkConfig>): void
  getInstantPlaybackMetrics(): InstantPlaybackMetrics
  async getStorageInfo(): Promise<StorageInfo>
}

🔧 Framework Integrations

Astro

Install the Astro integration:

npm install z-astro-web-audio-stream
// astro.config.mjs
import { defineConfig } from 'astro/config';
import webAudioStream from 'z-astro-web-audio-stream';

export default defineConfig({
  integrations: [
    webAudioStream({
      // Automatically copies worklet file to public/
      workletPath: '/audio-worklet-processor.js'
    })
  ]
});

React/Vue/Svelte

// hooks/useWebAudio.ts
import { useEffect, useState } from 'react';
import { setupWebAudio, WebAudioManager } from 'z-web-audio-stream';

export function useWebAudio() {
  const [manager, setManager] = useState<WebAudioManager | null>(null);
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    setupInstantAudio({
      onTimeUpdate: (currentTime, duration) => {
        // Handle time updates
      },
      onEnded: () => {
        // Handle playback end
      }
    }).then(audioManager => {
      setManager(audioManager);
      setIsReady(true);
    });

    return () => {
      manager?.cleanup();
    };
  }, []);

  return { manager, isReady };
}

🐛 Troubleshooting

Common Issues

Audio plays too fast/high-pitched on iOS

  • ✅ Fixed automatically by sample rate monitoring and iOS-safe AudioContext

Page reloads when loading large audio files on iOS

  • ✅ Fixed by adaptive chunk sizing (1-2MB max on iOS)

IndexedDB errors on Safari

  • ✅ Fixed by retry logic with exponential backoff

No audio on first interaction

  • Ensure you're calling initialize() after user gesture (click/touch)

Debug Mode

// Enable verbose logging for iOS issues
const manager = new WebAudioManager({
  // iOS debugging will automatically log sample rates, chunk sizes, etc.
});

📊 Performance

Memory Usage

  • Desktop: 3-8MB chunks, up to 1GB cache
  • iOS Safari: 1-2MB chunks, intelligent cleanup
  • Automatic: Cache cleanup based on age and storage limits

Network Optimization

  • Progressive loading starts playback with first chunk
  • Adaptive chunk sizing based on connection speed
  • Preloading for seamless track transitions

🤝 Contributing

Issues and PRs welcome! This package specifically targets iOS Safari audio bugs.

Common iOS Safari Issues We Fix

  1. Sample Rate Bug: AudioContext.sampleRate changes unexpectedly
  2. Memory Pressure: Large audio files cause page reloads
  3. IndexedDB Reliability: Random failures on first connection
  4. Broken AudioContext: Requires dummy buffer to reset state

📄 License

MIT License - feel free to use in your projects!

🙏 Credits

Built by the StreamFi team to solve iOS Safari audio streaming issues. Based on research into iOS Web Audio API bugs and memory management.