{"_id":"@ashwin_droid/airecording","_rev":"6-76d5c3296f8ed21abd2cfee3a7aeec06","name":"@ashwin_droid/airecording","dist-tags":{"latest":"1.3.1"},"versions":{"1.0.0":{"name":"@ashwin_droid/airecording","version":"1.0.0","author":{"name":"Ashwin"},"license":"ISC","_id":"@ashwin_droid/airecording@1.0.0","maintainers":[{"name":"ashwin_droid","email":"ash6chin8+bk0fzh8p@gmail.com"}],"homepage":"https://github.com/Socria-ByteFoundry/AIRecorder#readme","bugs":{"url":"https://github.com/Socria-ByteFoundry/AIRecorder/issues"},"dist":{"shasum":"4e3b494d4f8d728c1748345ba1cd00b7896ed6c7","tarball":"https://registry.npmjs.org/@ashwin_droid/airecording/-/airecording-1.0.0.tgz","fileCount":7,"integrity":"sha512-b761ZybGNaDdvEbEjyf4cAf2hA8T2czUQwGeY7uqD/2S2AqPHFKbH3nOOJR/uFluh6sTLq4x5DAPsPkmKGH/XQ==","signatures":[{"sig":"MEUCIC7s3hJJLjHbK6uHEG67vk5j6jOezL1nvPemeQILUOiaAiEA7zKrb3dx+BYKGrgDU2FhBA1LVrh2LE5ELqz/sm7SHZE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":26857},"main":"index.js","type":"module","exports":{".":"./index.js","./client":"./client/index.js","./worker":"./worker/index.js","./worker/api":"./worker/api.js","./client/realtime-recorder":"./client/realtime-recorder.js","./worker/recording-session":"./worker/recording-session.js"},"gitHead":"7cb74e5e3d0f32eeac25d833c3d85364a43eaee0","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"_npmUser":{"name":"ashwin_droid","email":"ash6chin8+bk0fzh8p@gmail.com"},"repository":{"url":"git+https://github.com/Socria-ByteFoundry/AIRecorder.git","type":"git"},"_npmVersion":"10.9.0","description":"Recording with CF DOs and R2","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/airecording_1.0.0_1763792403549_0.18134630436819843","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@ashwin_droid/airecording","version":"1.1.0","author":{"name":"Ashwin"},"license":"ISC","_id":"@ashwin_droid/airecording@1.1.0","maintainers":[{"name":"ashwin_droid","email":"ash6chin8+bk0fzh8p@gmail.com"}],"homepage":"https://github.com/Socria-ByteFoundry/AIRecorder#readme","bugs":{"url":"https://github.com/Socria-ByteFoundry/AIRecorder/issues"},"dist":{"shasum":"bfeebc2069190461af0c559dd4ade86f1a587be7","tarball":"https://registry.npmjs.org/@ashwin_droid/airecording/-/airecording-1.1.0.tgz","fileCount":7,"integrity":"sha512-z4PvZ68H9XtmsPmaOz8COgsHd+JMe8B3wY0vbukaOGj5WLPiahll0MjmJU3B1YTWz201zU1lPAYMCsGR/W1QwQ==","signatures":[{"sig":"MEQCICI7JNABeQJ2heogv/yWlkKZbzs0aHmjiC86dvNI330DAiASnT5BM++NQJoCotmzrtG4wOhftqQq4BdT/3LVuWBb4A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":39136},"main":"index.js","type":"module","exports":{".":"./index.js","./client":"./client/index.js","./worker":"./worker/index.js","./worker/api":"./worker/api.js","./client/realtime-recorder":"./client/realtime-recorder.js","./worker/recording-session":"./worker/recording-session.js"},"gitHead":"e8ed217cd0227b89338108e86ebbce9100505a8c","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"_npmUser":{"name":"ashwin_droid","email":"ash6chin8+bk0fzh8p@gmail.com"},"repository":{"url":"git+https://github.com/Socria-ByteFoundry/AIRecorder.git","type":"git"},"_npmVersion":"10.9.0","description":"Recording with CF DOs and R2","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/airecording_1.1.0_1763963373669_0.7098989348925178","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@ashwin_droid/airecording","version":"1.2.0","author":{"name":"Ashwin"},"license":"ISC","_id":"@ashwin_droid/airecording@1.2.0","maintainers":[{"name":"ashwin_droid","email":"ash6chin8+bk0fzh8p@gmail.com"}],"homepage":"https://github.com/Socria-ByteFoundry/AIRecorder#readme","bugs":{"url":"https://github.com/Socria-ByteFoundry/AIRecorder/issues"},"dist":{"shasum":"745e62a1d3745be2b774d348c119b2917b729544","tarball":"https://registry.npmjs.org/@ashwin_droid/airecording/-/airecording-1.2.0.tgz","fileCount":8,"integrity":"sha512-DcYFpF2IYdjmu8Ux2BPm1zArQHnjx49D+Gx1HN3KCAFI4UCcQdjcIjboVfg3/LYBBQpm6WAyrD5tol0BmjD9tQ==","signatures":[{"sig":"MEUCIFFXxWEIDxo5DIDWfaa/2aPCYGSTrVMyfd7QKgmAxWV4AiEA7jtyu+quy++LWEgfASD/rNVY6WuqINdIQQKd6Ix7kPg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":79155},"main":"index.js","type":"module","exports":{".":"./index.js","./client":"./client/index.js","./worker":"./worker/index.js","./worker/api":"./worker/api.js","./client/realtime-recorder":"./client/realtime-recorder.js","./worker/recording-session":"./worker/recording-session.js"},"gitHead":"3210a4e9e8dcaf3ab2e9287dd55770f8da3dc436","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"_npmUser":{"name":"ashwin_droid","email":"ash6chin8+bk0fzh8p@gmail.com"},"repository":{"url":"git+https://github.com/Socria-ByteFoundry/AIRecorder.git","type":"git"},"_npmVersion":"10.9.0","description":"Recording with CF DOs and R2","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/airecording_1.2.0_1764596215864_0.5676072043850171","host":"s3://npm-registry-packages-npm-production"}},"1.3.1":{"name":"@ashwin_droid/airecording","version":"1.3.1","description":"Recording with CF DOs and R2","type":"module","main":"index.js","exports":{".":"./index.js","./worker":"./worker/index.js","./client":"./client/index.js","./worker/recording-session":"./worker/recording-session.js","./worker/api":"./worker/api.js","./client/realtime-recorder":"./client/realtime-recorder.js"},"scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"repository":{"type":"git","url":"git+https://github.com/Socria-ByteFoundry/AIRecorder.git"},"author":{"name":"Ashwin"},"license":"ISC","bugs":{"url":"https://github.com/Socria-ByteFoundry/AIRecorder/issues"},"homepage":"https://github.com/Socria-ByteFoundry/AIRecorder#readme","_id":"@ashwin_droid/airecording@1.3.1","gitHead":"aa0b73b85a09e834d6084c357929f94fe1ea0cf6","_nodeVersion":"22.12.0","_npmVersion":"10.9.0","dist":{"integrity":"sha512-gbk2hzlYXTDZmaqtoyO3uG9h76GgA15/sNbwRa6uPr0PmG64Buo5WAtZG7y0DMkt84i5WL4vzi3W4bzqNjcWSQ==","shasum":"3dbc8cc8ae19bba410cc76a68cd667c177911964","tarball":"https://registry.npmjs.org/@ashwin_droid/airecording/-/airecording-1.3.1.tgz","fileCount":8,"unpackedSize":91672,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEPXXc1oeT5egvgXtnstcpVSlcrsdVTs1KNRn1G0cvSlAiEA7Vo66MEcursnZdHn75W9p4KHRhgBvS8zENVJvEqHqoU="}]},"_npmUser":{"name":"ashwin_droid","email":"ash6chin8+bk0fzh8p@gmail.com"},"directories":{},"maintainers":[{"name":"ashwin_droid","email":"ash6chin8+bk0fzh8p@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/airecording_1.3.1_1765194201840_0.6161567028236892"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-22T06:20:03.486Z","modified":"2025-12-08T11:43:22.171Z","1.0.0":"2025-11-22T06:20:03.744Z","1.1.0":"2025-11-24T05:49:33.879Z","1.2.0":"2025-12-01T13:36:56.045Z","1.3.0":"2025-12-08T08:43:23.912Z","1.3.1":"2025-12-08T11:43:21.987Z"},"bugs":{"url":"https://github.com/Socria-ByteFoundry/AIRecorder/issues"},"author":{"name":"Ashwin"},"license":"ISC","homepage":"https://github.com/Socria-ByteFoundry/AIRecorder#readme","repository":{"type":"git","url":"git+https://github.com/Socria-ByteFoundry/AIRecorder.git"},"description":"Recording with CF DOs and R2","maintainers":[{"name":"ashwin_droid","email":"ash6chin8+bk0fzh8p@gmail.com"}],"readme":"# AIRecorder\r\n\r\nReal-time audio recording library that streams audio to Cloudflare R2 storage.\r\n\r\n## Overview\r\n\r\nAIRecorder captures real-time audio from the browser microphone and streams it to Cloudflare R2 via WebSocket communication. It provides dual-stream output: raw PCM audio for AI processing and compressed WebM/Opus for efficient storage.\r\n\r\n## Features\r\n\r\n- **Real-time Audio Streaming** - Capture and stream audio with low latency\r\n- **Dual-Stream Architecture** - Raw PCM (24kHz) for AI + compressed WebM/Opus for storage\r\n- **Network Resilience** - Automatic reconnection with 30-minute grace period and buffering\r\n- **Pause & Resume** - Pause recording for up to 5 hours or pause the whole session and resume with a fresh link\r\n- **Cloudflare Integration** - Uses Durable Objects for session management and R2 for storage\r\n- **FFT Analysis** - Built-in frequency analysis for audio visualization\r\n- **Backpressure Handling** - Buffers data during network interruptions\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @ashwin_droid/airecording\r\n```\r\n\r\n## Quick Start\r\n\r\n### Browser Client\r\n\r\n```javascript\r\nimport { createRealtimeRecorderInstance, resumeRealtimeRecorderInstance } from '@ashwin_droid/airecording/client';\r\n\r\n// Create a recording session via your worker API\r\nconst { wsUrl, sessionId } = await fetch('/api/create-recording-session')\r\n  .then(r => r.json());\r\n\r\n// Start recording\r\nconst recorder = await createRealtimeRecorderInstance(wsUrl, {\r\n  chunkMs: 250,              // PCM chunk interval (default: 250ms)\r\n  compressedChunkMs: 5000,   // Compressed chunk interval (default: 5000ms)\r\n  includeVideo: false        // Add dummy video track (optional)\r\n});\r\n\r\n// Listen for network events\r\nrecorder.onNetworkError((err) => console.log('Network lost:', err));\r\nrecorder.onNetworkRestored(() => console.log('Network restored'));\r\n\r\n// Get FFT data for visualization\r\nconst freqData = recorder.fft({ buckets: 50 });\r\n\r\n// Stop recording\r\nawait recorder.finalise();\r\n```\r\n\r\n### Pause / Resume\r\n\r\n```javascript\r\n// Pause just the recording (socket stays up); auto times out after ~5h\r\nawait recorder.pauseRecording();\r\n\r\n// Resume recording (reattaches AI if it was enabled)\r\nawait recorder.resumeRecording();\r\n\r\n// Pause the whole session (intentionally drop WS; 30m resume window)\r\nawait recorder.pauseSession();\r\n\r\n// Later, on any device, mint a fresh wsUrl from your backend resume endpoint\r\nconst { wsUrl } = await fetch('/api/resume-recording-session?sid=...').then(r => r.json());\r\nconst { controller, state } = await resumeRealtimeRecorderInstance(wsUrl);\r\n// state includes recordedMs, bytesWritten, fileKey, timestamps, flags\r\n```\r\n\r\n### Cloudflare Worker\r\n\r\n```javascript\r\nimport {\r\n  createRealtimeRecordingSession,\r\n  handleRealtimeRecordingWebSocket,\r\n  RecordingSession\r\n} from '@ashwin_droid/airecording/worker';\r\n\r\nexport default {\r\n  async fetch(request, env) {\r\n    const url = new URL(request.url);\r\n\r\n    // Session initialization\r\n    if (url.pathname === '/api/create-recording-session') {\r\n      const { wsUrl, sessionId, expectedFileLocation } = await createRealtimeRecordingSession(\r\n        'RECORDINGS',              // R2 binding name\r\n        env.RECORDING_SESSIONS,    // Durable Object namespace\r\n        request.url,\r\n        {\r\n          basePrefix: 'sessions',\r\n          metadata: { userId: 'user123' },\r\n          format: 'webm'           // 'webm' or 'mp3'\r\n        }\r\n      );\r\n      return new Response(JSON.stringify({ wsUrl, sessionId, expectedFileLocation }));\r\n    }\r\n\r\n    // WebSocket upgrade\r\n    if (url.pathname === '/realtime-recording') {\r\n      return handleRealtimeRecordingWebSocket(env.RECORDING_SESSIONS, request);\r\n    }\r\n\r\n    return new Response('Not Found', { status: 404 });\r\n  }\r\n};\r\n\r\nexport { RecordingSession };\r\n```\r\n\r\n### Wrangler Configuration\r\n\r\n```toml\r\n[[durable_objects.bindings]]\r\nname = \"RECORDING_SESSIONS\"\r\nclass_name = \"RecordingSession\"\r\n\r\n[[r2_buckets]]\r\nbinding = \"RECORDINGS\"\r\nbucket_name = \"my-recordings-bucket\"\r\n```\r\n\r\n## Project Structure\r\n\r\n```\r\n├── index.js                    # Main entry point\r\n├── package.json\r\n├── client/\r\n│   ├── index.js               # Client exports\r\n│   └── realtime-recorder.js   # Browser recorder implementation\r\n└── worker/\r\n    ├── index.js               # Worker exports\r\n    ├── api.js                 # API handlers\r\n    └── recording-session.js   # Durable Object session manager\r\n```\r\n\r\n## Module Exports\r\n\r\nImport specific submodules as needed:\r\n\r\n```javascript\r\n// Full library\r\nimport { ... } from '@ashwin_droid/airecording';\r\n\r\n// Client only\r\nimport {\r\n  createRealtimeRecorderInstance,\r\n  resumeRealtimeRecorderInstance,\r\n} from '@ashwin_droid/airecording/client';\r\n\r\n// Worker only\r\nimport { createRealtimeRecordingSession, handleRealtimeRecordingWebSocket } from '@ashwin_droid/airecording/worker';\r\nimport { RecordingSession } from '@ashwin_droid/airecording/worker/recording-session';\r\n```\r\n\r\n## Architecture\r\n\r\n### Dual-Stream Processing\r\n\r\n| Stream | Format | Sample Rate | Use Case |\r\n|--------|--------|-------------|----------|\r\n| PCM | Base64 JSON | 24kHz | AI processing, speech-to-text |\r\n| Compressed | WebM/Opus | Native | Storage in R2 |\r\n\r\n### Network Resilience\r\n\r\n- Automatic reconnection on network loss\r\n- 30-minute grace period for incomplete sessions\r\n- Backlog queue buffers data during disconnection\r\n- Automatic flush on network restoration\r\n\r\n### R2 Upload Strategy\r\n\r\n- Multipart upload for large files (>5MB parts)\r\n- Lazy initialization to avoid empty uploads\r\n- Session state persisted in Durable Object storage\r\n- Automatic cleanup on session expiration\r\n\r\n## API Reference\r\n\r\n### Client\r\n\r\n#### `createRealtimeRecorderInstance(wsUrl, options)`\r\n\r\nCreates a new recorder instance and returns a controller with:\r\n`{ sessionId, wsUrl, fft(), onNetworkError(), onNetworkRestored(), pauseRecording(), resumeRecording(), pauseSession(), finalise() }`.\r\n\r\n**Options:**\r\n- `chunkMs` (number) - PCM chunk interval in milliseconds (default: 250)\r\n- `compressedChunkMs` (number) - Compressed chunk interval in milliseconds (default: 5000)\r\n- `includeVideo` (boolean) - Include dummy video track (default: false)\r\n- `pauseDurationOptimization` ('conservative' | 'strict' | 'off')\r\n\r\n#### `resumeRealtimeRecorderInstance(wsUrl, options)`\r\n\r\nReconnects to an existing session (using a fresh WS URL with nonce) and returns\r\n`{ controller, state }`, where `state` is a snapshot (recordedMs, bytesWritten, fileKey, timestamps, flags).\r\n\r\n### Error Codes (surfaced to the client)\r\n\r\n- `INVALID_NONCE` — tried to connect with a stale WS URL\r\n- `PAUSE_DURATION_LIMIT_REACHED` — recording pause exceeded ~5 hours\r\n- `SESSION_EXPIRED` — session not resumed within ~30 minutes\r\n\r\n### Worker\r\n\r\n#### `createRealtimeRecordingSession(r2BindingName, doNamespace, requestUrl, options)`\r\n\r\nInitializes a new recording session.\r\n\r\n**Options:**\r\n- `basePrefix` (string) - R2 key prefix for recordings\r\n- `metadata` (object) - Custom metadata to store with recording\r\n- `format` (string) - Output format: 'webm' or 'mp3'\r\n\r\n**Returns:** `{ wsUrl, sessionId, expectedFileLocation }`\r\n\r\n#### `handleRealtimeRecordingWebSocket(doNamespace, request)`\r\n\r\nHandles WebSocket upgrade requests for recording sessions.\r\n\r\n## License\r\n\r\nISC\r\n\r\n## Repository\r\n\r\nhttps://github.com/Socria-ByteFoundry/AIRecorder\r\n","readmeFilename":"README.md"}