UNPKG

tonelisten-react-native

Version:

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

731 lines (572 loc) 22.3 kB
# 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 ```bash npm install tonelisten-react-native ``` ### Peer Dependencies ```bash npm install react-native-audio-recorder-player react-native-permissions ``` ### iOS Setup After installation, run: ```bash 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 ```typescript 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: ```typescript 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: ```typescript 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) ```typescript // 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) ```typescript 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 ```typescript 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 ```typescript 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 ```typescript interface InAppContentModalProps { visible: boolean; // Whether modal is visible content?: InAppContentPayload | null; // Content to display onClose: () => void; // Close handler } ``` ### InAppContentPayload ```typescript 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: ```ts { 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 ```typescript { actionType: 'image', actionUrl: 'https://example.com/image.jpg', title: 'Image Title' } ``` ### Video Content ```typescript { actionType: 'video', actionUrl: 'https://example.com/video.mp4', title: 'Video Title' } ``` ### Webpage Content ```typescript { actionType: 'webpage', actionUrl: 'https://example.com/page', title: 'Webpage Title' } ``` ### Text Content ```typescript { 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) ```xml <!-- 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) ```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: ```typescript 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: ```javascript // 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: ```javascript // 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 ```bash # 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. ```xml <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](https://github.com/tonetelegenics/tonelisten-react-native/issues) page.