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

tonelisten-react-native

v1.0.65

Published

ToneListen React Native Framework - Audio tone detection for React Native apps

Readme

ToneListen React Native Framework

A React Native framework for audio tone detection and in-app content delivery, based on patented tone technology. This library provides real-time dual-tone multi-frequency (DTMF) detection, bridge tone recognition, and automatic content display using the Goertzel algorithm, optimized for React Native applications.

Features

  • Real-time Audio Processing: Uses Web Audio API for high-performance audio analysis
  • Goertzel Algorithm: Efficient frequency detection with Hanning windowing
  • Dual Tone Detection: Recognizes DTMF tone pairs with configurable tolerance
  • Bridge Tone Detection: Identifies bridge tones for sequence validation with local peak analysis
  • Sequence State Machine: Tracks and emits complete tone sequences with timer-based emissions
  • In-App Content Delivery: Automatic content display with customizable UI
  • API Integration: Built-in API service for content retrieval
  • React Native Integration: Easy-to-use hooks, components, and automatic modal
  • TypeScript Support: Full TypeScript definitions included
  • Cross-Platform: Works on both iOS and Android React Native apps

Installation

npm install tonelisten-react-native

Peer Dependencies

npm install react-native-audio-recorder-player react-native-permissions

iOS Setup

After installation, run:

cd ios && pod install

If you updated to version 1.0.61 or later (iOS playback decode support), rebuild your iOS app:

  1. Open Xcode, stop any running build
  2. Product → Clean Build Folder (Cmd+Shift+K)
  3. Build & Run (Cmd+R)

This is required because native iOS code changed.

Quick Start

1. Basic Tone Detection

import React, { useEffect } from 'react';
import { ToneListenReactNative } from 'tonelisten-react-native';

const App = () => {
  useEffect(() => {
    const toneListener = new ToneListenReactNative({
      clientId: '562', // Your client ID
      apiKey: 'your-user-specific-api-key', // Get this from your admin dashboard
      baseURL: 'https://api.toneadmin.com',
      // CORS proxy not needed if API has proper CORS headers
      debug: true,
      enableInAppContent: true, // Enable automatic content display
    });

    // Initialize and start
    toneListener.initialize().then(() => {
      toneListener.start();
    });

    return () => {
      toneListener.destroy();
    };
  }, []);

  return <YourAppComponent />;
};

2. Automatic Content Display (Recommended)

The framework includes an automatic content modal that displays images, videos, and other content when tone sequences are detected:

import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import ToneListenReactNative, { 
  InAppContentModal, 
  InAppContentPayload,
  NotificationCenter 
} from 'tonelisten-react-native';

const App = () => {
  const [modalVisible, setModalVisible] = useState(false);
  const [modalContent, setModalContent] = useState<InAppContentPayload | null>(null);

  useEffect(() => {
    // Set up automatic content display
    const notificationCenter = NotificationCenter.getInstance();
    notificationCenter.addObserver('InAppContentReceived', (content) => {
      console.log('Content received:', content);
      setModalContent(content);
      setModalVisible(true);
    });

    // Initialize ToneListen
    const toneListener = new ToneListenReactNative({
      clientId: '562',
      apiKey: 'your-api-key',
      baseURL: 'https://api.toneadmin.com',
      enableInAppContent: true, // This enables automatic content fetching
    });

    toneListener.initialize().then(() => {
      toneListener.start();
    });

    return () => {
      toneListener.destroy();
      notificationCenter.removeObserver('InAppContentReceived');
    };
  }, []);

  return (
    <View style={{ flex: 1 }}>
      {/* Your app content */}
      
      {/* Automatic content modal */}
      <InAppContentModal 
        visible={modalVisible} 
        content={modalContent} 
        onClose={() => setModalVisible(false)} 
      />
    </View>
  );
};

3. Custom UI Implementation

If you prefer to build your own UI instead of using the automatic modal, you can listen for notifications and create custom components:

import React, { useEffect, useState } from 'react';
import { View, Text, Image, Modal, TouchableOpacity } from 'react-native';
import ToneListenReactNative, { 
  NotificationCenter,
  InAppContentPayload 
} from 'tonelisten-react-native';

const CustomContentDisplay = () => {
  const [content, setContent] = useState<InAppContentPayload | null>(null);
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const notificationCenter = NotificationCenter.getInstance();
    
    notificationCenter.addObserver('InAppContentReceived', (receivedContent) => {
      console.log('Custom UI received content:', receivedContent);
      setContent(receivedContent);
      setVisible(true);
    });

    return () => {
      notificationCenter.removeObserver('InAppContentReceived');
    };
  }, []);

  const renderContent = () => {
    if (!content) return null;

    switch (content.actionType) {
      case 'image':
        return (
          <View style={{ padding: 20 }}>
            <Text style={{ fontSize: 18, marginBottom: 10 }}>
              {content.title}
            </Text>
            <Image 
              source={{ uri: content.actionUrl }} 
              style={{ width: 300, height: 200 }}
              resizeMode="contain"
            />
          </View>
        );
      
      case 'video':
        return (
          <View style={{ padding: 20 }}>
            <Text style={{ fontSize: 18, marginBottom: 10 }}>
              {content.title}
            </Text>
            <Text>Video: {content.actionUrl}</Text>
          </View>
        );
      
      case 'webpage':
        return (
          <View style={{ padding: 20 }}>
            <Text style={{ fontSize: 18, marginBottom: 10 }}>
              {content.title}
            </Text>
            <Text>{content.actionUrl}</Text>
          </View>
        );
      
      default:
        return (
          <View style={{ padding: 20 }}>
            <Text>{content.title || 'Content received'}</Text>
          </View>
        );
    }
  };

  return (
    <Modal visible={visible} transparent animationType="slide">
      <View style={{ 
        flex: 1, 
        backgroundColor: 'rgba(0,0,0,0.8)', 
        justifyContent: 'center', 
        alignItems: 'center' 
      }}>
        <View style={{ 
          backgroundColor: 'white', 
          borderRadius: 10, 
          padding: 20,
          maxWidth: '90%',
          maxHeight: '80%'
        }}>
          {renderContent()}
          
          <TouchableOpacity 
            style={{ 
              backgroundColor: '#007AFF', 
              padding: 10, 
              borderRadius: 5, 
              marginTop: 20 
            }}
            onPress={() => setVisible(false)}
          >
            <Text style={{ color: 'white', textAlign: 'center' }}>Close</Text>
          </TouchableOpacity>
        </View>
      </View>
    </Modal>
  );
};

const App = () => {
  useEffect(() => {
    const toneListener = new ToneListenReactNative({
      clientId: '562',
      apiKey: 'your-api-key',
      baseURL: 'https://api.toneadmin.com',
      enableInAppContent: true,
    });

    toneListener.initialize().then(() => {
      toneListener.start();
    });

    return () => {
      toneListener.destroy();
    };
  }, []);

  return (
    <View style={{ flex: 1 }}>
      {/* Your app content */}
      <CustomContentDisplay />
    </View>
  );
};

2b. Playback Decode (loud media + accurate detection)

To detect tones embedded in your app’s media at full playback volume, use playback decode.

  • iOS and Android supported (Android supported in 1.0.62+)
  • Automatically stops the mic engine while decoding
  • Works with local or remote media URLs (remote URLs are downloaded to a temp file under the hood)
// When starting media playback in your app
await toneListener.startPlaybackDecode(mediaUrl); // returns true/false

// ...start your AV playback (video/audio) as normal...

// When pausing/stopping media
await toneListener.stopPlaybackDecode(); // Restores mic-based detection

Debug logs to expect when working:

  • iOS: "PlaybackDecode: audio session set to category=playback, mode=moviePlayback"
  • iOS: "PlaybackDecode: reader started → rate=48000.0Hz, channels=…"
  • Android: playback decode running on background thread (no session log)
  • Both: "NativeAudioPipeline: AudioFrame source -> playback" (frames come from media)

If frames show "source -> mic" during playback, playback decode isn’t active.

4. Using the Hook (Alternative Approach)

import React from 'react';
import { View, Text, Button } from 'react-native';
import { useToneDetection } from 'tonelisten-react-native';

const ToneDetectionScreen = () => {
  const {
    isListening,
    isInitialized,
    lastSequence,
    startListening,
    stopListening
  } = useToneDetection({
    clientId: '562',
    apiKey: 'your-api-key',
    baseURL: 'https://api.toneadmin.com',
    enableInAppContent: true,
    onSequence: (event) => {
      console.log('Sequence detected:', event.sequence);
    }
  });

  return (
    <View style={{ padding: 20 }}>
      <Text>Status: {isListening ? 'Listening' : 'Stopped'}</Text>
      {lastSequence && <Text>Last Sequence: {lastSequence}</Text>}
      <Button
        title={isListening ? 'Stop' : 'Start'}
        onPress={isListening ? stopListening : startListening}
      />
    </View>
  );
};

User-Specific API Key Authentication

The framework uses user-specific API keys to ensure only authorized users can access tone detection endpoints. Each user has their own unique API key that must be obtained from your admin dashboard.

Getting User-Specific API Keys

  1. Access Admin Dashboard: Log into your ToneListen admin dashboard
  2. Navigate to Users: Go to the Users section
  3. Find Target User: Locate the user you want to create an app for
  4. Generate API Key: Click the key icon (🔑) next to the user to regenerate their API key
  5. Copy API Key: Copy the generated API key (it won't be shown again)
  6. Configure App: Use the API key in your React Native app configuration

API Key Security

  • User-Specific: Each API key is tied to a specific user account
  • Company Validation: The framework validates that the clientId matches the user's company
  • Automatic Logging: All API requests are logged with user information
  • Key Rotation: API keys can be regenerated at any time for security

Example Configuration

const toneListener = new ToneListenReactNative({
  clientId: '562', // Must match the user's company ID
  apiKey: 'a1b2c3d4e5f6...', // User-specific API key from admin dashboard
  baseURL: 'https://api.toneadmin.com',
  debug: true,
});

Configuration

ToneListenReactNative Configuration

interface ToneListenReactNativeConfig {
  // Audio Configuration
  sampleRate?: number;          // Audio sample rate (default: 44100)
  bufferSize?: number;          // Audio buffer size (default: 4800)
  tolerance?: number;           // Frequency matching tolerance in Hz (default: 5)
  bridgeTolerance?: number;     // Bridge tone tolerance in Hz (default: 10)
  minPower?: number;            // Minimum power threshold (default: 0.01)
  
  // API Configuration
  clientId?: string;           // Your client ID
  apiKey?: string;             // Your API key
  baseURL?: string;            // API base URL (default: 'https://api.toneadmin.com')
  corsProxyBaseURL?: string;   // CORS proxy URL for web platforms
  useProxyOnWeb?: boolean;     // Use proxy on web platforms (default: true)
  
  // Behavior Configuration
  autoStart?: boolean;         // Start automatically (default: false)
  debug?: boolean;             // Enable debug logging (default: false)
  enableInAppContent?: boolean; // Enable automatic content display (default: false)
  
  // Callbacks
  onSequence?: (event: ToneSequenceEvent) => void;
  onDualTone?: (result: DualToneResult) => void;
  onBridgeTone?: (result: BridgeToneResult) => void;
  onError?: (error: Error) => void;
  onPermissionDenied?: () => void;
  onPresentContent?: (content: InAppContentPayload) => void; // Custom content handler
}

InAppContentModal Configuration

interface InAppContentModalProps {
  visible: boolean;                    // Whether modal is visible
  content?: InAppContentPayload | null; // Content to display
  onClose: () => void;                // Close handler
}

InAppContentPayload

interface InAppContentPayload {
  actionType: 'image' | 'video' | 'webpage' | 'text'; // Content type
  actionUrl?: string;                 // URL for the content
  actionData?: string;                // Additional data
  title?: string;                     // Content title
}

API Reference

ToneListenReactNative

Main class for tone detection and content delivery.

Methods

  • initialize(): Initialize the tone detection system
  • start(): Start listening for tones
  • stop(): Stop listening for tones
  • startPlaybackDecode(url: string): iOS only. Start decoding media for detection while keeping device playback loud
  • stopPlaybackDecode(): iOS only. Stop media decoding and return to mic-based detection
  • updateConfig(config): Update configuration
  • getState(): Get current detection state
  • destroy(): Clean up resources

Events

  • TLRN_AudioFrame payload now includes an optional source field on iOS:
{
  sampleRate: number;
  buffer: number[]; // Float32 PCM
  source?: 'mic' | 'playback';
}

NotificationCenter

Singleton class for handling notifications between framework and app.

Methods

  • getInstance(): Get singleton instance
  • addObserver(name, callback): Add notification observer
  • removeObserver(name, callback?): Remove notification observer
  • post(name, object?, userInfo?): Post notification

Notification Names

  • 'InAppContentReceived': Fired when content is received from API

useToneDetection Hook

React hook for easy integration.

Returns

  • isListening: Whether currently listening
  • isInitialized: Whether system is initialized
  • error: Current error state
  • lastSequence: Last detected sequence
  • lastDualTone: Last detected dual tone
  • lastBridgeTone: Last detected bridge tone
  • startListening(): Start listening function
  • stopListening(): Stop listening function
  • updateConfig(config): Update configuration function
  • getState(): Get current state function

Content Types

The framework supports various content types that can be displayed automatically:

Image Content

{
  actionType: 'image',
  actionUrl: 'https://example.com/image.jpg',
  title: 'Image Title'
}

Video Content

{
  actionType: 'video',
  actionUrl: 'https://example.com/video.mp4',
  title: 'Video Title'
}

Webpage Content

{
  actionType: 'webpage',
  actionUrl: 'https://example.com/page',
  title: 'Webpage Title'
}

Text Content

{
  actionType: 'text',
  actionData: 'Your text content here',
  title: 'Text Title'
}

Permissions

The framework requires microphone and location permissions for full functionality. Make sure to add the necessary permissions to your app:

iOS (Info.plist)

<!-- Microphone permission for tone detection -->
<key>NSMicrophoneUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your microphone</string>

<!-- Location permissions for enhanced tone detection features -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to provide location-based content when tones are detected.</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs location access to provide location-based content when tones are detected.</string>

Android (AndroidManifest.xml)

<!-- Microphone permission for tone detection -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />

<!-- Location permissions for enhanced tone detection features -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- Internet permission for API calls -->
<uses-permission android:name="android.permission.INTERNET" />

Permission Descriptions

  • NSMicrophoneUsageDescription: Required for audio recording and tone detection
  • NSLocationWhenInUseUsageDescription: Allows location access while the app is in use
  • NSLocationAlwaysAndWhenInUseUsageDescription: Allows location access both when in use and in background
  • ACCESS_COARSE_LOCATION: Provides approximate location (network-based)
  • ACCESS_FINE_LOCATION: Provides precise location (GPS-based)
  • RECORD_AUDIO: Required for microphone access on Android
  • INTERNET: Required for API calls to the ToneListen backend

CORS and Web Support

What is CORS?

CORS (Cross-Origin Resource Sharing) is a browser security feature that blocks requests between different domains. When your React Native app runs on web platforms, browsers enforce CORS policies that can prevent API calls to your ToneListen server.

CORS Proxy Solutions

Option 1: Use Provided CORS Proxy (Recommended)

The framework includes built-in CORS proxy support. Simply configure your proxy URL:

const toneListener = new ToneListenReactNative({
  clientId: '562',
  apiKey: 'your-api-key',
  baseURL: 'https://api.toneadmin.com',
  corsProxyBaseURL: 'https://proxy.toneadmin.com', // Public CORS proxy
  useProxyOnWeb: true, // Only use proxy on web platforms
});

Option 2: Set Up Your Own CORS Proxy

If you prefer to run your own proxy, you can use a simple Node.js server:

// cors-proxy.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const cors = require('cors');

const app = express();

// Enable CORS for all routes
app.use(cors({
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'x-api-key']
}));

// Proxy requests to your API
app.use('/api', createProxyMiddleware({
  target: 'https://api.toneadmin.com',
  changeOrigin: true,
  pathRewrite: {
    '^/api': '' // Remove /api prefix
  }
}));

app.listen(3001, () => {
  console.log('CORS proxy running on port 3001');
});

Option 3: Fix CORS at API Level (Best Long-term Solution)

Configure your API server to include proper CORS headers:

// Express.js example
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*'); // Or specific domains
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, x-api-key');
  
  if (req.method === 'OPTIONS') {
    res.sendStatus(200);
  } else {
    next();
  }
});

Platform-Specific Behavior

  • iOS/Android: No CORS proxy needed - direct API calls work
  • Web: CORS proxy required unless API has proper CORS headers
  • React Native Web: CORS proxy required

The framework automatically detects the platform and only uses the proxy when running on web platforms.

Platform Support

  • iOS: 11.0+
  • Android: API level 21+
  • React Native: 0.60+
  • Web: Supported with CORS proxy

Development

# Install dependencies
npm install

# Build the library
npm run build

# Run tests
npm test

# Lint code
npm run lint

Troubleshooting

Content Not Displaying

  1. Check API Configuration: Ensure clientId, apiKey, and baseURL are correct
  2. Verify NotificationCenter: Make sure you're using NotificationCenter.getInstance() (singleton)
  3. Check Content Type: Ensure your API returns valid actionType and actionUrl
  4. Enable Debug Logging: Set debug: true to see detailed logs

Common Issues

  • Modal not appearing: Check that enableInAppContent: true is set
  • API calls failing: Verify your API key and client ID
  • Permissions denied: Ensure microphone permissions are granted
  • Web compatibility: Use CORS proxy for web applications
  • Playback sounds quiet on iOS: Use startPlaybackDecode(url) before starting media, and stopPlaybackDecode() when stopping. Check logs for "AudioFrame source -> playback". Ensure you performed a clean Xcode rebuild after installing 1.0.61+.

iOS App Transport Security (ATS) for media URLs

Playback decode downloads remote media with URLSession. Prefer HTTPS media URLs. If you must use HTTP or older TLS, add an ATS exception in your app Info.plist for the media domain.

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>your-media-domain.com</key>
    <dict>
      <key>NSIncludesSubdomains</key><true/>
      <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key><true/>
      <key>NSTemporaryExceptionMinimumTLSVersion</key><string>TLSv1.2</string>
    </dict>
  </dict>
  <!-- Keep arbitrary loads off unless required -->
  <key>NSAllowsArbitraryLoads</key><false/>
  <key>NSAllowsArbitraryLoadsForMedia</key><false/>
  <key>NSAllowsArbitraryLoadsInWebContent</key><false/>
  <key>NSAllowsLocalNetworking</key><true/>
  
</dict>

Based On

This React Native framework is based on the iOS ToneListen framework version 19, maintaining compatibility with the same frequency tables and detection algorithms while adapting to React Native's cross-platform constraints and capabilities.

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues and questions, please use the GitHub Issues page.