npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

z-web-audio-stream

v1.4.0

Published

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

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/

2. Instant Playback with Separated Optimization (Recommended)

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.