feat: add expo-two-way-audio module to workspace
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["universe/native", "universe/web"],
|
||||
ignorePatterns: ["build"],
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Publish to NPM
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: ⎔ Get Node Version
|
||||
id: node_version
|
||||
run: echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: ⎔ Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "${{ steps.node_version.outputs.NVMRC }}"
|
||||
cache: npm
|
||||
|
||||
- name: 📦 Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: ⬆️ Publish
|
||||
run: npm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}" && npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
|
||||
@@ -0,0 +1,36 @@
|
||||
name: 🧪 Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: ⎔ Get Node Version
|
||||
id: node_version
|
||||
run: echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: ⎔ Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "${{ steps.node_version.outputs.NVMRC }}"
|
||||
cache: npm
|
||||
|
||||
- name: 📦 Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: 🧪 Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: 🧪 Format
|
||||
run: npm run format
|
||||
|
||||
- name: 🧪 Typecheck
|
||||
run: npm run typecheck
|
||||
@@ -0,0 +1,56 @@
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
jsconfig.json
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# Android/IJ
|
||||
#
|
||||
.classpath
|
||||
.cxx
|
||||
.gradle
|
||||
.idea
|
||||
.project
|
||||
.settings
|
||||
local.properties
|
||||
android.iml
|
||||
android/app/libs
|
||||
android/keystores/debug.keystore
|
||||
|
||||
# Ruby
|
||||
examples/*/vendor/
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Expo
|
||||
.expo/*
|
||||
|
||||
examples/*/ios
|
||||
examples/*/android
|
||||
@@ -0,0 +1,14 @@
|
||||
# Exclude all top-level hidden directories by convention
|
||||
/.*/
|
||||
|
||||
# Exclude tarballs generated by `npm pack`
|
||||
/*.tgz
|
||||
|
||||
__mocks__
|
||||
__tests__
|
||||
|
||||
/babel.config.js
|
||||
/android/src/androidTest/
|
||||
/android/src/test/
|
||||
/android/build/
|
||||
/examples/
|
||||
@@ -0,0 +1 @@
|
||||
20.17
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
devx@speechmatics.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Contributing
|
||||
|
||||
Welcome to the Speechmatics Expo Two Way Audio Module! We're open to contributions from anyone. We hope you can find everything you need in here to get started contributing to this repo.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Useful Links](#useful-links)
|
||||
- [How to Submit Changes](#how-to-submit-changes)
|
||||
- [How to Report a Bug](#how-to-report-a-bug)
|
||||
- [How to Request a Feature](#how-to-request-a-feature)
|
||||
- [Style Guide](#style-guide)
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [Speechmatics Website](https://www.speechmatics.com/)
|
||||
- [Portal (for generating API keys)](https://portal.speechmatics.com/manage-access/)
|
||||
- [Docs](https://docs.speechmatics.com/)
|
||||
|
||||
### Installing dependencies
|
||||
|
||||
After cloning this repo, run
|
||||
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
|
||||
## How to Submit Changes
|
||||
|
||||
We try not to be too prescreptive about how people work, but we also believe in helping make things easier by following a couple of basic steps. If you follow these recommendations, it should make the review process simpler and quicker:
|
||||
|
||||
1. If your change is large, consider reaching out to us beforehand and discussing the issue. This could save you a lot of time if there are good reasons why we haven't done something.
|
||||
2. Fork the repo and work on a branch on your fork. Try to give your branches short, descriptive names in the format {type}/{description} e.g. bugfix/missing-try-catch.
|
||||
3. Make sure your changes are tested - ideally both manually and in the unit tests.
|
||||
4. When opening a PR back into our repo, provide some simple descriptive comments that list the changes being made in the PR.
|
||||
5. Give your PR a short, descriptive title.
|
||||
|
||||
## How to Report a Bug
|
||||
|
||||
If you are experiencing a bug, you can report it via the [issues](https://github.com/speechmatics/expo-two-way-audio/issues) page. The more details you give us, the better we can understand and fix your problem!
|
||||
|
||||
## How to Request a Feature
|
||||
|
||||
If you want a feature, you can open a discussion via the [issues](https://github.com/speechmatics/expo-two-way-audio/issues) page. Try to tag your issue with the most appropriate label available so that we can track it more easily.
|
||||
|
||||
## Style Guide
|
||||
|
||||
We use Biome for linting and formatting: https://www.npmjs.com/package/@biomejs/biome
|
||||
|
||||
You can run linting and formatting using the scripts:
|
||||
|
||||
```
|
||||
npm run lint
|
||||
npm run format
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023, Cantab Research Ltd.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Speechmatics Two Way Audio
|
||||
|
||||
Expo module for capturing and playing pcm audio data in react-native apps (iOS and Android).
|
||||
|
||||
The aim of the module is to facilitate creating real-time conversational apps. The following features are provided:
|
||||
|
||||
- Request audio recording permissions
|
||||
- Get clean (applying Acoustic Echo Cancelling) microphone samples in PCM format (1 channel 16 bit at 16kHz)
|
||||
- Play audio samples in PCM format (1 channel 16 bit at 16kHz). Playback happens through main speaker unless external audio sources are connected.
|
||||
- Provide volume level both for the input and output samples. Float between 0 and 1.
|
||||
- [iOS only] Get microphone mode and prompt user to select a microphone mode.
|
||||
|
||||
Check out our [examples/](./examples) to see the module in action.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm i @speechmatics/expo-two-way-audio
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Please check out our [examples/](./examples) to get full sample code.
|
||||
|
||||
1. Request permissions for recording audio
|
||||
|
||||
```JSX
|
||||
import {useMicrophonePermissions} from "@speechmatics/expo-two-way-audio";
|
||||
|
||||
const [micPermission, requestMicPermission] = useMicrophonePermissions();
|
||||
console.log(micPermission);
|
||||
```
|
||||
|
||||
1. Initialize the module before calling any audio functionality.
|
||||
|
||||
```JSX
|
||||
useEffect(() => {
|
||||
const initializeAudio = async () => {
|
||||
await initialize();
|
||||
};
|
||||
initializeAudio();
|
||||
}, []);
|
||||
|
||||
```
|
||||
|
||||
1. Play audio
|
||||
|
||||
> [!NOTE]
|
||||
> The sample below uses the `buffer` module:
|
||||
> `npm install buffer`
|
||||
|
||||
```JSX
|
||||
import { Buffer } from "buffer";
|
||||
|
||||
// As an example, let's play pcm data hardcoded in a variable.
|
||||
// The examples/basic-usage does this. Check it out for real base64 data.
|
||||
const audioChunk = "SOME PCM DATA BASE64 ENCODED HERE"
|
||||
const buffer = Buffer.from(audioChunk, "base64");
|
||||
const pcmData = new Uint8Array(buffer);
|
||||
playPCMData(pcmData);
|
||||
```
|
||||
|
||||
1. Get microphone samples
|
||||
|
||||
```JSX
|
||||
// Set up a function to deal with microphone sample events.
|
||||
// In this case just print the data in the console.
|
||||
useExpoTwoWayAudioEventListener(
|
||||
"onMicrophoneData",
|
||||
useCallback<MicrophoneDataCallback>((event) => {
|
||||
console.log(`MIC DATA: ${event.data}`);
|
||||
}, []),
|
||||
);
|
||||
|
||||
// Unmute the microphone to get microphone data events
|
||||
toggleRecording(true);
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Some audio features of expo-two-way-audio like Acoustic Echo Cancelling, noise reduction or microphone modes (iOS) don't work on simulator. Run the example on a real device to test these features.
|
||||
|
||||
```bash
|
||||
# iOS
|
||||
npx expo run:ios --device --configuration Release
|
||||
|
||||
# Android
|
||||
npx expo run:android --device --variant release
|
||||
```
|
||||
|
||||
For Android, the following permissions are needed: `RECORD_AUDIO`, `MODIFY_AUDIO_SETTINGS`. In Expo apps they can bee added in your `app.json` file:
|
||||
|
||||
```javascript
|
||||
expo.android.permissions: ["RECORD_AUDIO", "MODIFY_AUDIO_SETTINGS"]
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
group = 'expo.modules.twowayaudio'
|
||||
version = '0.1.0'
|
||||
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
useCoreDependencies()
|
||||
useExpoPublishing()
|
||||
|
||||
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
|
||||
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
|
||||
// Most of the time, you may like to manage the Android SDK versions yourself.
|
||||
def useManagedAndroidSdkVersions = false
|
||||
if (useManagedAndroidSdkVersions) {
|
||||
useDefaultAndroidSdkVersions()
|
||||
} else {
|
||||
buildscript {
|
||||
// Simple helper that allows the root project to override versions declared by this library.
|
||||
ext.safeExtGet = { prop, fallback ->
|
||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||
}
|
||||
}
|
||||
project.android {
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet("minSdkVersion", 21)
|
||||
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "expo.modules.twowayaudio"
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "0.1.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest>
|
||||
</manifest>
|
||||
@@ -0,0 +1,425 @@
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioDeviceInfo
|
||||
import android.media.AudioFocusRequest
|
||||
import android.media.AudioFormat
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioRecord
|
||||
import android.media.AudioTrack
|
||||
import android.media.MediaRecorder
|
||||
import android.media.audiofx.AcousticEchoCanceler
|
||||
import android.media.audiofx.NoiseSuppressor
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.util.LinkedList
|
||||
import java.util.Queue
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
class AudioEngine (context: Context) {
|
||||
private val SAMPLE_RATE = 16000
|
||||
private val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
|
||||
private val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
|
||||
|
||||
private lateinit var audioRecord: AudioRecord
|
||||
private lateinit var audioManager: AudioManager
|
||||
private lateinit var audioTrack: AudioTrack
|
||||
private var audioFocusRequest: AudioFocusRequest? = null
|
||||
private val audioSampleQueue: Queue<ByteArray> = LinkedList()
|
||||
private var echoCanceler: AcousticEchoCanceler? = null
|
||||
private var noiseSuppressor: NoiseSuppressor? = null
|
||||
private val executorServiceMicrophone = Executors.newFixedThreadPool(1)
|
||||
private val executorServicePlayback = Executors.newFixedThreadPool(1)
|
||||
private var speakerDevice: AudioDeviceInfo? = null
|
||||
private var bridgeWindowStartedAtMs = System.currentTimeMillis()
|
||||
private var micEvents = 0
|
||||
private var micBytes = 0L
|
||||
private var playbackEvents = 0
|
||||
private var playbackQueuedBytes = 0L
|
||||
private var playbackWrites = 0
|
||||
private var playbackWriteBytes = 0L
|
||||
|
||||
var isRecording = false
|
||||
private var isRecordingBeforePause = false
|
||||
var isPlaying = false
|
||||
|
||||
// Callbacks
|
||||
var onMicDataCallback: ((ByteArray) -> Unit)? = null
|
||||
var onInputVolumeCallback: ((Float) -> Unit)? = null
|
||||
var onOutputVolumeCallback: ((Float) -> Unit)? = null
|
||||
var onAudioInterruptionCallback: ((String) -> Unit)? = null
|
||||
|
||||
init {
|
||||
initializeAudio(context)
|
||||
}
|
||||
|
||||
private fun flushBridgeStats(reason: String) {
|
||||
val now = System.currentTimeMillis()
|
||||
val elapsedMs = now - bridgeWindowStartedAtMs
|
||||
if (elapsedMs < 1000) {
|
||||
return
|
||||
}
|
||||
Log.d(
|
||||
"AudioEngine",
|
||||
"[bridge] $reason mic=$micEvents" +
|
||||
"ev/${micBytes}B playbackQueue=$playbackEvents" +
|
||||
"ev/${playbackQueuedBytes}B playbackWrites=$playbackWrites" +
|
||||
"ev/${playbackWriteBytes}B windowMs=$elapsedMs"
|
||||
)
|
||||
bridgeWindowStartedAtMs = now
|
||||
micEvents = 0
|
||||
micBytes = 0
|
||||
playbackEvents = 0
|
||||
playbackQueuedBytes = 0
|
||||
playbackWrites = 0
|
||||
playbackWriteBytes = 0
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private fun initializeAudio(context:Context) {
|
||||
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
||||
requestAudioFocus()
|
||||
|
||||
// Route audio to external device if connected, otherwise route to speaker
|
||||
updateAudioRouting()
|
||||
|
||||
// Listen for changes in audio routing
|
||||
audioManager.registerAudioDeviceCallback(object:android.media.AudioDeviceCallback(){
|
||||
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
|
||||
Log.d("AudioEngine", "onAudioDevicesAdded")
|
||||
super.onAudioDevicesAdded(addedDevices)
|
||||
updateAudioRouting()
|
||||
}
|
||||
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
|
||||
Log.d("AudioEngine", "onAudioDevicesRemoved")
|
||||
super.onAudioDevicesRemoved(removedDevices)
|
||||
updateAudioRouting()
|
||||
}
|
||||
}, null)
|
||||
|
||||
val bufferSize = AudioTrack.getMinBufferSize(
|
||||
SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AUDIO_FORMAT
|
||||
)
|
||||
|
||||
audioTrack = AudioTrack(
|
||||
AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||
.build(),
|
||||
AudioFormat.Builder()
|
||||
.setEncoding(AUDIO_FORMAT)
|
||||
.setSampleRate(SAMPLE_RATE)
|
||||
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
||||
.build(),
|
||||
bufferSize,
|
||||
AudioTrack.MODE_STREAM,
|
||||
audioManager.generateAudioSessionId()
|
||||
).apply {
|
||||
play()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAudioRouting() {
|
||||
val devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
||||
var isExternalDeviceConnected = false
|
||||
var selectedDevice: AudioDeviceInfo? = null
|
||||
|
||||
for (device in devices) {
|
||||
if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
|
||||
speakerDevice = device
|
||||
}
|
||||
if (device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
|
||||
device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
|
||||
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
|
||||
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
|
||||
isExternalDeviceConnected = true
|
||||
selectedDevice = device
|
||||
break
|
||||
} else if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
|
||||
selectedDevice = device
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
// Use the modern API for Android S and above
|
||||
try {
|
||||
selectedDevice?.let {
|
||||
audioManager.setCommunicationDevice(it)
|
||||
}
|
||||
}catch (e:Exception){
|
||||
Log.e("AudioEngine", "Error setting communication device. Using speaker")
|
||||
speakerDevice?.let {
|
||||
audioManager.setCommunicationDevice(it)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fall back to deprecated method for older Android versions
|
||||
@Suppress("DEPRECATION")
|
||||
audioManager.isSpeakerphoneOn = !isExternalDeviceConnected
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private fun requestAudioFocus() {
|
||||
val focusRequest =
|
||||
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
|
||||
.setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||
.build()
|
||||
)
|
||||
.setAcceptsDelayedFocusGain(true)
|
||||
.setOnAudioFocusChangeListener { focusChange ->
|
||||
when (focusChange) {
|
||||
AudioManager.AUDIOFOCUS_LOSS -> {
|
||||
Log.d("AudioEngine", "Audio focus lost")
|
||||
onAudioInterruptionCallback?.let { it("blocked") }
|
||||
}
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
audioFocusRequest = focusRequest
|
||||
val result = audioManager.requestAudioFocus(focusRequest)
|
||||
|
||||
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
throw RuntimeException("Audio focus request failed")
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun startRecording(){
|
||||
val bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT)
|
||||
audioRecord = AudioRecord(
|
||||
MediaRecorder.AudioSource.VOICE_COMMUNICATION,
|
||||
SAMPLE_RATE,
|
||||
CHANNEL_CONFIG,
|
||||
AUDIO_FORMAT,
|
||||
bufferSize
|
||||
)
|
||||
|
||||
if (audioRecord.state != AudioRecord.STATE_INITIALIZED) {
|
||||
throw RuntimeException("Audio Record can't initialize!")
|
||||
}
|
||||
|
||||
if (AcousticEchoCanceler.isAvailable()){
|
||||
echoCanceler = AcousticEchoCanceler.create(audioRecord.audioSessionId)
|
||||
if (echoCanceler != null) {
|
||||
echoCanceler?.enabled = true
|
||||
Log.i("AudioEngine", "Echo Canceler enabled")
|
||||
}
|
||||
}
|
||||
|
||||
if (NoiseSuppressor.isAvailable()){
|
||||
noiseSuppressor = NoiseSuppressor.create(audioRecord.audioSessionId)
|
||||
if (noiseSuppressor != null) {
|
||||
noiseSuppressor?.enabled = true
|
||||
Log.i("AudioEngine", "Noise Suppressor enabled")
|
||||
}
|
||||
}
|
||||
|
||||
audioRecord.startRecording()
|
||||
isRecording = true
|
||||
startMicSampleTap()
|
||||
}
|
||||
|
||||
private fun startMicSampleTap(){
|
||||
executorServiceMicrophone.execute {
|
||||
val buffer = ByteArray(1024)
|
||||
try {
|
||||
while (isRecording) {
|
||||
val read = audioRecord.read(buffer, 0, buffer.size)
|
||||
if (read > 0) {
|
||||
val data = buffer.copyOf(read)
|
||||
micEvents += 1
|
||||
micBytes += data.size.toLong()
|
||||
flushBridgeStats("mic")
|
||||
val micVolume = calculateRMSLevel(data)
|
||||
onInputVolumeCallback?.invoke(micVolume)
|
||||
onMicDataCallback?.invoke(data)
|
||||
}
|
||||
}
|
||||
Log.d("AudioEngine", "Mic sample tap stopped.")
|
||||
}catch (e: Exception){
|
||||
Log.e("AudioEngine", "Error reading mic sample data", e)
|
||||
isRecording = false
|
||||
tearDown()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopRecording() {
|
||||
if (!isRecording) return
|
||||
isRecording = false
|
||||
if (audioRecord.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
|
||||
audioRecord.stop()
|
||||
audioRecord.release()
|
||||
}
|
||||
onInputVolumeCallback?.invoke(0.0F)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
fun toggleRecording(value: Boolean): Boolean {
|
||||
if (value == isRecording) return isRecording
|
||||
|
||||
if (value) {
|
||||
startRecording()
|
||||
} else {
|
||||
stopRecording()
|
||||
}
|
||||
|
||||
isRecording = value
|
||||
return isRecording
|
||||
}
|
||||
|
||||
fun playPCMData(data: ByteArray) {
|
||||
audioSampleQueue.add(data)
|
||||
playbackEvents += 1
|
||||
playbackQueuedBytes += data.size.toLong()
|
||||
Log.d(
|
||||
"AudioEngine",
|
||||
"playPCMData queued bytes=${data.size} queueSize=${audioSampleQueue.size} " +
|
||||
"head=${data.take(12).joinToString(" ") { byte -> "%02x".format(byte.toInt() and 0xff) }}"
|
||||
)
|
||||
flushBridgeStats("queue")
|
||||
if (!isPlaying) {
|
||||
playAudioFromSampleQueue()
|
||||
}
|
||||
}
|
||||
|
||||
private fun playAudioFromSampleQueue() {
|
||||
executorServicePlayback.execute{
|
||||
isPlaying = true
|
||||
try {
|
||||
while (audioSampleQueue.isNotEmpty()){
|
||||
val data = audioSampleQueue.poll()
|
||||
if (data != null){
|
||||
playSample(data)
|
||||
val audioVolume = calculateRMSLevel(data)
|
||||
onOutputVolumeCallback?.invoke(audioVolume)
|
||||
}else{
|
||||
break
|
||||
}
|
||||
}
|
||||
}catch (e: Exception){
|
||||
Log.e("AudioEngine", "Error playing audio", e)
|
||||
e.printStackTrace()
|
||||
}finally {
|
||||
isPlaying = false
|
||||
onOutputVolumeCallback?.invoke(0.0F)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun playSample(data: ByteArray) {
|
||||
val written = audioTrack.write(data, 0, data.size)
|
||||
playbackWrites += 1
|
||||
playbackWriteBytes += written.coerceAtLeast(0).toLong()
|
||||
Log.d(
|
||||
"AudioEngine",
|
||||
"playSample wrote=$written requested=${data.size} playState=${audioTrack.playState} " +
|
||||
"queueSize=${audioSampleQueue.size}"
|
||||
)
|
||||
flushBridgeStats("write")
|
||||
}
|
||||
|
||||
fun bypassVoiceProcessing(bypass: Boolean) {
|
||||
if (bypass) {
|
||||
echoCanceler?.enabled = false
|
||||
noiseSuppressor?.enabled = false
|
||||
} else {
|
||||
echoCanceler?.enabled = true
|
||||
noiseSuppressor?.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
fun pauseRecordingAndPlayer() {
|
||||
isRecordingBeforePause = isRecording
|
||||
isRecording = toggleRecording(false)
|
||||
audioTrack.pause()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
fun resumeRecordingAndPlayer() {
|
||||
requestAudioFocus()
|
||||
isRecording = toggleRecording(isRecordingBeforePause)
|
||||
audioTrack.play()
|
||||
}
|
||||
|
||||
fun stopPlayback() {
|
||||
audioSampleQueue.clear()
|
||||
audioTrack.pause()
|
||||
audioTrack.flush()
|
||||
isPlaying = false
|
||||
onOutputVolumeCallback?.invoke(0.0F)
|
||||
Log.d("AudioEngine", "Playback stopped")
|
||||
}
|
||||
|
||||
fun pausePlayback() {
|
||||
audioTrack.pause()
|
||||
Log.d("AudioEngine", "Playback paused")
|
||||
}
|
||||
|
||||
fun resumePlayback() {
|
||||
audioTrack.play()
|
||||
Log.d("AudioEngine", "Playback resumed")
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
fun tearDown() {
|
||||
stopRecording()
|
||||
audioTrack.stop()
|
||||
audioManager.mode = AudioManager.MODE_NORMAL
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
audioManager.clearCommunicationDevice()
|
||||
}
|
||||
audioFocusRequest?.let { request ->
|
||||
audioManager.abandonAudioFocusRequest(request)
|
||||
}
|
||||
executorServiceMicrophone.shutdownNow()
|
||||
}
|
||||
|
||||
|
||||
private fun calculateRMSLevel(buffer: ByteArray): Float {
|
||||
val epsilon = 1e-5f // To avoid log(0)
|
||||
|
||||
// Convert ByteArray to FloatArray by treating each pair of bytes as a single 16-bit PCM sample
|
||||
val floatBuffer = FloatArray(buffer.size / 2)
|
||||
for (i in floatBuffer.indices) {
|
||||
// Combine two bytes into a 16-bit signed integer
|
||||
val sample = (buffer[i * 2].toInt() or (buffer[i * 2 + 1].toInt() shl 8)).toShort()
|
||||
// Normalize sample to -1.0 to 1.0 range for FloatArray
|
||||
floatBuffer[i] = sample / 32768.0f
|
||||
}
|
||||
|
||||
// Calculate RMS value
|
||||
val rmsValue = kotlin.math.sqrt(floatBuffer.fold(0f) { acc, sample -> acc + sample * sample } / floatBuffer.size)
|
||||
|
||||
// Convert to decibels
|
||||
val dbValue = 20 * kotlin.math.log10(maxOf(rmsValue, epsilon))
|
||||
|
||||
// Normalize decibel value to 0-1 range
|
||||
// Assuming minimum audible is -80dB and maximum is 0dB
|
||||
val minDb = -80.0f
|
||||
val normalizedValue = maxOf(0.0f, minOf(1.0f, (dbValue - minDb) / kotlin.math.abs(minDb)))
|
||||
|
||||
// Optional: Apply exponential factor to push smaller values down
|
||||
val expFactor = 2.0f // Adjust this value to change the curve
|
||||
val adjustedValue = normalizedValue.pow(expFactor)
|
||||
|
||||
return adjustedValue
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package expo.modules.twowayaudio
|
||||
|
||||
import android.app.Activity
|
||||
import expo.modules.core.interfaces.ReactActivityLifecycleListener
|
||||
|
||||
class ExpoTwoWayAudioLifeCycleListener : ReactActivityLifecycleListener {
|
||||
override fun onPause(activity: Activity?) {
|
||||
super.onPause(activity)
|
||||
// At the moment background audio recording and playback is not supported
|
||||
ExpoTwoWayAudioModule.audioEngine?.pauseRecordingAndPlayer()
|
||||
}
|
||||
|
||||
override fun onResume(activity: Activity?) {
|
||||
super.onResume(activity)
|
||||
// At the moment background audio recording and playback is not supported
|
||||
ExpoTwoWayAudioModule.audioEngine?.resumeRecordingAndPlayer()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package expo.modules.twowayaudio
|
||||
|
||||
import AudioEngine
|
||||
import androidx.core.os.bundleOf
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
import expo.modules.kotlin.Promise
|
||||
import expo.modules.interfaces.permissions.Permissions
|
||||
|
||||
class ExpoTwoWayAudioModule : Module() {
|
||||
companion object {
|
||||
private const val ON_MIC_DATA_EVENT = "onMicrophoneData"
|
||||
private const val ON_INPUT_VOLUME_LEVEL_EVENT = "onInputVolumeLevelData"
|
||||
private const val ON_OUTPUT_VOLUME_LEVEL_EVENT = "onOutputVolumeLevelData"
|
||||
private const val ON_RECORDING_CHANGE_EVENT = "onRecordingChange"
|
||||
private const val ON_AUDIO_INTERRUPTION_EVENT = "onAudioInterruption"
|
||||
var audioEngine: AudioEngine? = null
|
||||
}
|
||||
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name("ExpoTwoWayAudio")
|
||||
AsyncFunction("initialize") { promise: Promise ->
|
||||
try {
|
||||
if (audioEngine != null) {
|
||||
promise.resolve(true)
|
||||
return@AsyncFunction
|
||||
}
|
||||
audioEngine = appContext.reactContext?.let { AudioEngine(it) }
|
||||
setupCallbacks()
|
||||
promise.resolve(true)
|
||||
} catch (e: Exception) {
|
||||
promise.resolve(false)
|
||||
}
|
||||
}
|
||||
|
||||
Function("isRecording") {
|
||||
audioEngine?.isRecording ?: false
|
||||
}
|
||||
|
||||
Function("toggleRecording") { value: Boolean ->
|
||||
audioEngine?.let { engine ->
|
||||
val isRecording = engine.toggleRecording(value)
|
||||
sendEvent(ON_RECORDING_CHANGE_EVENT, mapOf("data" to isRecording))
|
||||
isRecording
|
||||
} ?: false
|
||||
}
|
||||
|
||||
Function("tearDown") {
|
||||
audioEngine?.tearDown()
|
||||
audioEngine = null
|
||||
null
|
||||
}
|
||||
|
||||
Function("restart") {
|
||||
audioEngine?.resumeRecordingAndPlayer()
|
||||
sendEvent(ON_RECORDING_CHANGE_EVENT, mapOf(
|
||||
"data" to (audioEngine?.isRecording ?: false)
|
||||
))
|
||||
}
|
||||
|
||||
Function("playPCMData") { data: kotlin.ByteArray ->
|
||||
audioEngine?.playPCMData(data)
|
||||
}
|
||||
|
||||
Function("bypassVoiceProcessing") { bypass: Boolean ->
|
||||
audioEngine?.bypassVoiceProcessing(bypass)
|
||||
}
|
||||
|
||||
Function("isPlaying") {
|
||||
audioEngine?.isPlaying ?: false
|
||||
}
|
||||
|
||||
Function("stopPlayback") {
|
||||
audioEngine?.stopPlayback()
|
||||
}
|
||||
|
||||
Function("pausePlayback") {
|
||||
audioEngine?.pausePlayback()
|
||||
}
|
||||
|
||||
Function("resumePlayback") {
|
||||
audioEngine?.resumePlayback()
|
||||
}
|
||||
|
||||
Function("getMicrophoneModeIOS") {
|
||||
throw UnsupportedOperationException("getMicrophoneModeIOS is only supported on iOS")
|
||||
}
|
||||
|
||||
Function ("setMicrophoneModeIOS") {
|
||||
throw UnsupportedOperationException("setMicrophoneModeIOS is only supported on iOS")
|
||||
}
|
||||
|
||||
AsyncFunction("getMicrophonePermissionsAsync") { promise: Promise ->
|
||||
Permissions.getPermissionsWithPermissionsManager(
|
||||
appContext.permissions,
|
||||
promise,
|
||||
android.Manifest.permission.RECORD_AUDIO
|
||||
)
|
||||
}
|
||||
|
||||
AsyncFunction("requestMicrophonePermissionsAsync") { promise: Promise ->
|
||||
Permissions.askForPermissionsWithPermissionsManager(
|
||||
appContext.permissions,
|
||||
promise,
|
||||
android.Manifest.permission.RECORD_AUDIO
|
||||
)
|
||||
}
|
||||
|
||||
// Register events
|
||||
Events(
|
||||
ON_MIC_DATA_EVENT,
|
||||
ON_INPUT_VOLUME_LEVEL_EVENT,
|
||||
ON_OUTPUT_VOLUME_LEVEL_EVENT,
|
||||
ON_RECORDING_CHANGE_EVENT,
|
||||
ON_AUDIO_INTERRUPTION_EVENT
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupCallbacks() {
|
||||
audioEngine?.apply {
|
||||
onMicDataCallback = { data ->
|
||||
sendEvent(ON_MIC_DATA_EVENT, bundleOf("data" to data))
|
||||
}
|
||||
onInputVolumeCallback = { level ->
|
||||
sendEvent(ON_INPUT_VOLUME_LEVEL_EVENT, bundleOf("data" to level))
|
||||
}
|
||||
onOutputVolumeCallback = { level ->
|
||||
sendEvent(ON_OUTPUT_VOLUME_LEVEL_EVENT, bundleOf("data" to level))
|
||||
}
|
||||
onAudioInterruptionCallback = { data ->
|
||||
sendEvent(ON_AUDIO_INTERRUPTION_EVENT, bundleOf("data" to data))
|
||||
sendEvent(ON_RECORDING_CHANGE_EVENT, bundleOf(
|
||||
"data" to (audioEngine?.isRecording ?: false)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package expo.modules.twowayaudio
|
||||
|
||||
import android.content.Context
|
||||
import expo.modules.core.interfaces.Package
|
||||
import expo.modules.core.interfaces.ReactActivityLifecycleListener
|
||||
|
||||
class ExpoTwoWayAudioPackage : Package {
|
||||
override fun createReactActivityLifecycleListeners(activityContext: Context): List<ReactActivityLifecycleListener> {
|
||||
return listOf(ExpoTwoWayAudioLifeCycleListener())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"files": {
|
||||
"ignore": [
|
||||
"./build",
|
||||
"./node_modules/",
|
||||
".expo",
|
||||
"ios",
|
||||
"android",
|
||||
"expo-env.d.ts",
|
||||
"./examples/*/node_modules",
|
||||
"./examples/*/.expo",
|
||||
"./examples/*/ios",
|
||||
"./examples/*/android"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"lineWidth": 100
|
||||
},
|
||||
"linter": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
|
||||
# Native
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,676 @@
|
||||
import { Buffer } from "buffer";
|
||||
import {
|
||||
MicrophoneDataCallback,
|
||||
VolumeLevelCallback,
|
||||
getMicrophoneModeIOS,
|
||||
initialize,
|
||||
playPCMData,
|
||||
setMicrophoneModeIOS,
|
||||
toggleRecording,
|
||||
useExpoTwoWayAudioEventListener,
|
||||
useIsRecording,
|
||||
useMicrophonePermissions,
|
||||
} from "@speechmatics/expo-two-way-audio";
|
||||
import { Button, StyleSheet, Text, View } from "react-native";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export default function App() {
|
||||
const [micPermission, requestMicPermission] = useMicrophonePermissions();
|
||||
console.log(micPermission);
|
||||
|
||||
if (!micPermission?.granted) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Mic permission: {micPermission?.status}</Text>
|
||||
<Button
|
||||
title={micPermission?.canAskAgain ? "Request permission" : "Cannot request permissions"}
|
||||
disabled={!micPermission?.canAskAgain}
|
||||
onPress={requestMicPermission}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return <Testbed />;
|
||||
}
|
||||
|
||||
export function Testbed() {
|
||||
const [audioInitialized, setAudioInitialized] = useState(false);
|
||||
const [inputVolumeLevel, setInputVolumeLevel] = useState(0.0);
|
||||
const [outputVolumeLevel, setOutputVolumeLevel] = useState(0.0);
|
||||
const micMode = Platform.OS === "ios" ? getMicrophoneModeIOS() : "NO_MIC_MODE_IN_ANDROID";
|
||||
|
||||
const playAudio = () => {
|
||||
for (const dataChunk of audioData) {
|
||||
for (const audioChunk of dataChunk.audio) {
|
||||
const buffer = Buffer.from(audioChunk, "base64");
|
||||
const pcmData = new Uint8Array(buffer);
|
||||
playPCMData(pcmData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isRecording = useIsRecording();
|
||||
|
||||
useExpoTwoWayAudioEventListener(
|
||||
"onMicrophoneData",
|
||||
useCallback<MicrophoneDataCallback>((event) => {
|
||||
console.log(`MIC DATA: ${event.data}`);
|
||||
}, []),
|
||||
);
|
||||
|
||||
useExpoTwoWayAudioEventListener(
|
||||
"onInputVolumeLevelData",
|
||||
useCallback<VolumeLevelCallback>((event) => {
|
||||
setInputVolumeLevel(event.data);
|
||||
}, []),
|
||||
);
|
||||
|
||||
useExpoTwoWayAudioEventListener(
|
||||
"onOutputVolumeLevelData",
|
||||
useCallback<VolumeLevelCallback>((event) => {
|
||||
setOutputVolumeLevel(event.data);
|
||||
}, []),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
initialize().then(() => setAudioInitialized(true));
|
||||
}, []);
|
||||
|
||||
const handleToggleMute = useCallback(() => {
|
||||
toggleRecording(!isRecording);
|
||||
}, [isRecording]);
|
||||
|
||||
const handleMicToggle = useCallback(() => {
|
||||
if (Platform.OS === "ios") {
|
||||
setMicrophoneModeIOS();
|
||||
} else {
|
||||
console.log("No mic mode in android");
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button title="Play audio" onPress={playAudio} />
|
||||
<Button title={isRecording ? "Mute" : "Unmute"} onPress={handleToggleMute} />
|
||||
{isRecording && <Button title="mic mode" onPress={handleMicToggle} />}
|
||||
</View>
|
||||
<View style={styles.volumeContainer}>
|
||||
<Text>{`input:${inputVolumeLevel}`}</Text>
|
||||
<Text>{`output:${outputVolumeLevel}`}</Text>
|
||||
<Text>{isRecording ? micMode : "ready"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const audioData = [
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"wP7V/ur++v4T/xb/Ef8U/xX/Dv8G/wn//f7y/gD///4F/xj/Gv8c/yH/I/8m/0T/WP9S/2X/aP9e/2H/aP9v/3L/eP99/4b/jv+j/7f/vf/b/+X/6/8IAAwAEQAfACQAKQA3AEwATwBZAHYAfwB5AHIAdgCBAHwAewCHAJkAqACjAJkAlwCiAKEAjgCYAJ0AnQCxALsAwADDAMoA1wDVANQA3ADlAO8A/AAIAQYB/QD3APwAAQH9APsA+QD5APEA5wDZAL4ArQCrALMAswCsAKcAmgCbAJ8AlACbAJ4AnwCrAKsApgCjAKYApACgAJ4AmQCYAIsAkgCUAIMAjQCLAA==",
|
||||
"ewBwAHEAdQBnAF4ATgA8AD4ALgAgAB4ADwAQAAcA/P8BAPr/9f/w/+T/4v/i/+T/7v/t/93/2//h/9v/z//M/8z/xP+8/7H/qP+q/6H/mf+X/4v/ef9v/3b/Zv9b/2P/WP9T/0z/QP9F/z//Q/9O/1D/XP9V/03/VP9R/1r/Vv9G/03/V/9Z/17/ZP9j/2X/b/9z/23/a/9z/3z/gP+D/3//dP91/27/Zv90/3n/d/94/3n/gP+M/5L/mv+l/6n/pf+l/6v/pf+j/6z/tf++/7//xP/M/8H/vf/D/7z/q/+c/6//yP/M/9P/2//l//H/6//m//b/CAAGAAQAGwArAA==",
|
||||
"MAA2ACwAJQAgABwAKwAwADIAPwBRAFEAPgBEAEIAOgA9ADoARwBTAF8AZgBnAGoAbABvAGsAcwCCAIUAkACQAI0AkgCPAIcAeAByAIIAiwCGAHYAbwB4AHIAcwCJAIQAeQCJAIcAfACAAH0AgQCDAH4AjACNAIkAlgCEAHcAgwCAAHwAfACEAIkAdwB3AHoAagBjAGwAaQBcAF8AZgBlAFgAVgBVAD0AQABGADoAOgA6ADEAMgAxACkAJgAbABQAEwAKAA0ADQAQABcABAD6//n/8//1/+7/6//p/+r/9v/v/+P/5v/g/9//2v/V/+H/3P/e/+f/2v/a/9r/z//H/w==",
|
||||
"v//C/7b/p/+u/63/qP+n/6H/lv+U/5n/kv+Q/5D/hv97/3b/ff9+/3T/cP9x/2b/Yf9q/2n/bP9z/3P/cP9n/2X/af9j/2D/Zv9r/2//bv97/4n/gf97/3X/cv94/3D/bf94/4P/hf95/3X/gP+C/3X/df98/3r/hP+J/43/l/+O/5T/n/+d/6P/pv+w/8P/0P/a/9z/0v/W/+D/4f/t/+v/7f/4//L/8//u/+D/3f/l/+7/8P/6//n/8f/9//j/9v/+//n/AwAHAAcACgAQABkAHAAlACwANQAyADcAUgBMAE8AZwBiAFoAYQBsAHIAeQB+AHsAfwB+AHoAggCAAA==",
|
||||
"ggCKAIYAiwCVAJkAlwCWAJoAmwCbAKQAswCzALEAvQDBALwAvQDEAMEAvwC9ALMAtwC3ALIAuAC2ALAArACtAK8ApwCnAKYAogCeAJgAmgCUAIsAjwCOAI0AiQCBAIEAdwB4AHwAbABlAGQAYwBhAF0AWgBaAFsAWgBaAE8ASwBNAEYARABFADkAMwAyAB8AHQAjABkAEAAJAAIABQAGAPz/AAADAPj/7f/p/+X/2v/V/9L/0f/K/8T/xP+4/6//rf+l/53/jv+G/5L/k/+O/4//j/+O/4n/ff95/4L/e/9p/2//dP9z/3n/ef9z/3H/a/9q/2//aP9o/3X/dv9u/w==",
|
||||
"cv95/3T/cP9v/27/cP9y/3L/d/95/3n/ff93/3//iv+I/4j/iv+I/4r/k/+R/47/kf+Q/5v/n/+V/5T/lv+c/5b/l/+m/6P/rP+y/6z/tv+5/7X/vf/B/8X/0f/T/9j/1//V/9z/3//l/+P/5P/q/+b/7f/4//r/9/////r/8/8FAP//9/8HAAAA+P/7////AQAFAAoABQAEAAcABgANAAoACQATABUAGgAfACwAKgAhACsAKQArADEAMAA2ADQAMQA0ADEANABDAEcARgBJAEsASABEAEUARwBBAD4ASQBLAEEAPABEAEMAQABJAEYARQBJAEkATABNAE4ASQBHAA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"SABEAEkASgBJAEwASABEAEkATQBJAE0AVABNAFAAUwBIAE0ASwBEAEkARABGAEoAPgA9AEMAQgA8ADIANgA8ADgAOgA7ADwAPQA3ADYAOAAzADEALgArACoAKgAnACQAIwAdABUAFQAYABYAGAAbABMADgAKAAMABAD///v//v////v/+v8AAPv/9v/z/+f/7P/u/+r/6P/l/+b/5f/j/+L/5P/e/9b/2//W/9T/2P/R/9P/0P/M/8//x//H/8j/vP+6/7j/uf++/7r/u/+5/7X/sv+x/7L/r/+3/7T/p/+p/6r/rP+v/7D/r/+w/7n/uP+4/7n/tf+2/7T/uv+6/w==",
|
||||
"s/+9/73/tf+6/77/vP+7/7v/tP+w/7P/sv+x/7f/v/+8/7b/vP++/7T/t//E/8j/xP/C/8P/wf/B/8b/yv/Q/9X/0f/S/9f/1P/W/9j/zv/S/+D/3//k/+f/5P/v//P/9P/8//r/+v8CAAYABwALAAwADQAPAAsAFQAeAB0AIQAaABsAKAAkACcALAApACsALQAsACsAMAAwADEANgA2ADoAPAA4ADsAOgA2ADkAPQBBAEUASwBOAEwAVQBZAFUAVgBaAGEAXABaAGAAXABkAGUAXwBhAF4AXwBjAGQAYgBfAGAAYQBZAFoAXABcAFwAWABaAFkAVABUAFIAUABRAA==",
|
||||
"UQBPAFIAUQBOAFAATQBIAEoARwBDAEAAQABHAEEAPAA6ADYANwA0ADMALwAtACsAIwAjACMAIQAbABcAHAATAA0AFgATAA8AEAAHAAMABgAHAP//AAABAPf//v/8//H/8v/v/+3/6f/c/+L/5v/W/9z/4P/Z/9f/zP/O/8//yv/K/8H/wP/A/7v/xP/D/7n/uf+y/6//uP+x/6z/tv+y/6b/qv+s/6n/qv+u/7X/s/+v/7D/rP+y/7f/tP+3/7r/u//C/8P/wP/C/8T/yP/H/8X/x//E/8b/zf/L/8r/0f/R/9D/0f/U/9z/4P/Z/9j/2//Y/+D/4P/a/9//3f/c/w==",
|
||||
"4//h/+P/6f/u/+//7P/s/+3/8P/x//H/+v/2//T/AQAAAAIABgAFAAcABgAFAAkADwARAA8ADgARABkAFwAaACAAFwAbACAAHAAgAB8AJQAmACQAJwAlACgAKwAsACoAKQAyACwAKgAzADEANAAyAC4AMAAsACwAKwApACUAJQApACYAIgAkACYAJQAjACQAIAAfACEAIAAeABoAGAAaABsAGQAZABgAEAAQABcAFwATABcAFgAMAA0AEAALAA0ADQAKAA0ACwAFAAIA/v8CAAMA//8CAAMABAADAPz/+P/9//3/+P/2//T/9//5//n/9v/z//P/8f/x//D/7//y/w==",
|
||||
"8f/v//L/9P/r/+z/7f/n/+n/5//q/+v/5f/m/+f/6v/p/+L/5v/m/+P/6//n/+b/6v/o/+z/7v/r/+3/8P/x/+//7f/t//H/7v/q//H/9P/s/+7/8P/u//L/9f/1//H/8f/x//P/+P/y//H/9//2//X/9P/z//f/9//5//r/+f/4//j//f/8//f/+P////3///8GAP//BwAJAP3/AwADAAEAAAD9/wEAAQD+/wEAAwAAAAEAAwAEAAQAAQADAAIA/v8CAAcACgAGAAMABwAHAAQABwAHAAkADgALAAsADQALAA0AEgASABAADQAOABIADgAPABMAEQATABIADwATAA==",
|
||||
"EgAPAA8AEgATABEAEwATAA4ADwATABYAEwAOAA0ADgATABEADwAUAA8ACwAMAAwADQAOAAsADwAPAAgACQAJAAMABQAJAAcABgAIAAkACQAEAAYACAACAAEAAgD9//7/AgD///z/+v/5//z/+P/0//j/9f/2//f/8//1//L/7//v//L/8P/o/+z/7//r/+n/6//t/+n/6P/q/+j/6P/p/+b/5//q/+f/4v/n/+z/5//n/+n/5//k/+L/6f/p/+L/6P/m/+T/6f/m/+j/6f/h/+T/6P/o/+3/7f/q/+z/7v/s//D/9f/v/+//8//x//L/8P/z//T/8//4//n/9//4/w==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"9v/1//b/7v/w//f/8//0//L/8//3//H/8v/1//H/8//z//H/9P/0//P/9P/x//D/8//y//L/9f/w//L/9//1//P/9v/4//r//P/7//v//f///wIAAwACAAUABgADAAcABwAGAAwACwALAAoACgAOAA0ADwAPAA0ADwAOAA8AEQASABMAEgAVABMAEAAUABgAGQAWABYAGQAZABsAHAAcAB0AHAAiACAAHAAgACAAHgAhACMAJQAlACQAJwAkACEAIQAgACYAJQAjACYAJgAnACgAJQAkACcAKAAmACQAIwAkACYAJQAmACUAIAAgAB8AIwAiAB0AIAAfABsAGwAYAA==",
|
||||
"GAAbABkAGAAbABUAFAATABIAEAAQABMADAANABEACwANAAwABgAIAAYABAAEAPz/AAAGAPn/+/8BAPn/+v/7//f/+P/3//b/8//y//P/8v/z/+7/7v/s/+b/6P/l/+L/5f/j/+P/4P/d/9z/2P/W/9b/1v/W/9L/0v/T/9L/0f/O/8//zv/J/8v/yf/J/8//x//F/83/xP/F/8z/wv/F/8b/wv/I/8X/xP/I/8T/xv/C/8L/xP/B/8T/xf/H/8P/wP/I/8b/xv/G/8f/zP/E/8T/yP/G/8X/w//J/87/yf/K/87/0P/Q/9L/1P/S/9T/2P/Y/9r/2//c/+H/4//j/w==",
|
||||
"5P/k/+T/6P/r/+z/7//w/+//8f/2//j//P/9//7/AwABAAMABwAEAAoAEgAOABMAFwAXAB4AHAAbACIAIwAkACgAKQAqACwALgAxADIAMgAxADYAOgA5ADoAPgA+ADwAPAA8AEEARgA+AD0AQAA8AD4AQQA/AD8APwA/AEAAPAA7ADwANgA2ADwANwAyADUANQA1ADMAMAAzADAALAAvAC0AKgArACkAKwArACcAKgAnACMAKAAlACMAIgAdAB4AIQAeABsAHAAaABQAEwAVABQAEAAOAA4ADAAIAAQAAwABAPz//f/8//v/+v/2//r/9f/s//P/8v/r/+n/6f/q/w==",
|
||||
"6f/n/+P/4P/j/+H/4f/f/9r/3f/a/9v/4P/e/9z/3v/c/9z/3//d/9z/4P/f/97/4P/e/9//5//n/+P/5P/l/+f/6//x//H/8v/z//T/+v/4//b/9//1//b/9v/3//j/9//6//v/+//+//////8AAP///v8EAAMAAwAFAAEABAAFAAQABAADAAQABAAIAAkABAAGAAgABwAJAAgABQAEAAMABQAFAAEA/f/+/wEAAAD//wEAAwAAAAAAAgD8//7/AAD6//r/+v/8//r/9//6//f/8//2//T/9P/2//L/8P/z//D/8P/x/+//8v/v/+//8v/w/+//7f/v//L/8P/z/w==",
|
||||
"9P/z//X/9f/z//P/9f/2//T/9P/1//T/9v/1//X/9v/1//X/9f/3//j/+v/7//j/9//5//z//v/9//7//v/9////AQACAAQABgAFAAQAAwADAAMABQAGAAUABQAHAAkADgALAAgADAAOAA8ADwAOAA4ADAAPABAADAAMAAwADgAOAA4AEQASAA8ACgAMAAkACwAaAA0A/f8CAAIAAAAGAAUABAAIAAYABQAFAAIAAgD/////AAD+////AgAEAAIAAAD///7//v/+//z//P/8//3//f/9//z//f/+//7//f/8//7////9//3//P/9//7//v/+///////+/////////w==",
|
||||
"//8AAAEAAAAAAAAA//8AAAEAAAAAAAAAAAABAAEAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAA//////////8AAP//////////AAAAAP//AAAAAAEAAAAAAAAAAAAAAAAAAAABAAEAAAABAAAAAAABAAAAAQABAAAAAQAAAAAAAAAAAAAAAAABAAEAAQAAAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAAAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"AAABAAEAAQABAAEAAQABAAEAAwACAAIAAwACAAIAAgABAAIAAQABAAIABQAGAAQAAwAEAAQABAAEAAMAAwADAAMAAwADAAMAAgACAAIAAgACAAEAAQACAAEAAQACAAIAAQABAAEAAgABAAIAAgABAAIAAQABAAIAAgABAAIAAgACAAIAAQABAAIAAgACAAEAAQACAAIAAgACAAIAAgACAAEAAQACAAIAAQABAAIAAgACAAIAAgACAAIAAQABAAEAAQACAAIAAQABAAEAAgACAAEAAQACAAIAAQABAAIAAQACAAIAAQABAAIAAgACAAIAAgABAAEAAgACAAIAAgABAA==",
|
||||
"AQABAAIAAQABAAEAAgABAAIAAQABAAEAAQACAAIAAQABAAEAAgABAAEAAgACAAIAAgACAAEAAQABAAIAAgACAAEAAQABAAIAAgACAAEAAgACAAEAAQACAAEAAQABAAEAAQABAAEAAQACAAEAAQABAAEAAQABAAEAAQABAAEAAgACAAEAAQABAAIAAgABAAIAAQABAAIAAgABAAIAAgABAAEAAgABAAIAAgACAAIAAgACAAIAAgABAAEAAgACAAEAAQACAAIAAgABAAIAAgABAAEAAQABAAIAAQABAAEAAgABAAEAAgACAAIAAgABAAEAAgABAAEAAgABAAEAAgABAA==",
|
||||
"AQACAAIAAQABAAIAAQABAAIAAQACAAIAAQACAAIAAgABAAEAAgABAAEAAQABAAIAAgABAAIAAQABAAEAAQABAAEAAQABAAEAAQACAAEAAQABAAIAAgABAAEAAQABAAEAAQACAAEAAQABAAEAAQABAAEAAQABAAEAAAAAAAEAAQABAAEAAQABAAEAAQABAAEAAgACAAEAAgACAAEAAgACAAIAAgABAAEAAQACAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAAAAAAAAAAAAAAAQAAAAEAAQABAAEA/////wAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"TgVHBGID0AIpAt8BdAIVBNcF1QYJBxUG6gRRBN0DlgPtA1wFuQYmB+sG5QbvBtIF1QR1BLIEDgXABAYFMgXsBBkE+wJzAvcBlgHdAHwArQCGAL0A2AByAO7/V/9+//T+Lv2f/PH83/wn/A/8wPz4+5T6cfrd+tv5Nfhc+AD51viV+HT43ves9qH2Nfd19pX1Hvb49mz29/X19or3pPb39Y72tfZV9j/3//hq+cH4dPnN+t/6P/oo+rn60/ok+zf80fxE/d39jv4K/3f/ugCiAe0BhgJeAzoEOQQ2BK8E7QSMBT4G6AYQB7IGNAdPCDkJXQl+CQcKRgo7CnwKggvsCw==",
|
||||
"7guXDHMNDw7KDQ8OhQ7aDWsNpAyHDCUNtg2YDrAMCwq6CEwIGQlPCOQGsQV3BJMEKAQCAxsCFwGLAM//5/96AbwC2wIpAXL/1/6G/tH9UvwA/DP9Ff6m/Q78gPo/+ZT3ePX587L01PfG+dv36PSI9BX2PfXU8WPw/PFi9Cn1uvQJ9e71YfbT9CXy1vFB9ED2yfXw9Nb15vaw9oL18fPg8onzx/Xj9hz2a/Zc+Mb4xvab9Sz3//iA+cb5W/p/+/v8Ev6r/fz8sP4TAbUBAgJpBDcHjAciB+kHHgmTCb4KCQ19DcMMkA01D8kPLg/CD4gR/RFzEoATahSoFCwUWxQBEw==",
|
||||
"ThHKEQ0UfRQdDvAGmgZ7C1UNZAgDBMwEJQb3AwMBagApAbcAcf+1/0sD/wiZCuYFvABPAVgEMQOoADkBewK6ATAB+wHNAOH85fnb91H03PIB9/f6xfgs9ILzhfXQ9FTyEvHg8Fnxa/JP9En2NvhQ+Gr0LfAv8IzzK/Xa863z5fRe9S315vQT9LHxJPAp8cTy6fOO9F31BPUn85DyrfNT9U/13fMt9E73yPvf/VH8E/ok+v77OP0Q/tv/IQLVAnoCNgN1BAsFdAXjBrYIiQm5CnINCRBEEAcPMQ9jEcAThRRWFEoU5RS5FTUWsxaTF/IYLhmzF7sWcRbQEpwJbwJOBQ==",
|
||||
"1grrCIoCVAIgB6gEvf32+tz7Q/z3+5MAIAb3CPcM9g7tCq4EowRqCd4IAQXcBTcKDAyHCX4Ebv2e96D2OPYT8x3zK/pf/pv3Ne/w7/T0ePOL7Z3sfPEX92T5WPi39rz1N/X28ufvdPGq9jH6z/hz9EvyRPIP8pbv1uoW6evsPPL48qzvb+4F8LjvsOy17IvxV/ay9373nvlZ/Ab9pvtI+RH5pfun/zcCOAKaAnkDAAPEAGP/CAFeAwQFYgbgBz4JGQqLC0kMjgviC2UPZxTDFswXNBopHNYaJBhtGWMdnB/fHqwcYxu3GjYYeA5bACX8XgRYCuYCh/rO/5cFw/698w==",
|
||||
"F/I9+G/6S/zhAjMJ5Q7+EkISEQvnBIsJaw61CksHowvGEkARIwmRAR37s/eK9lL0MfFe8z37W/xx893sOvCl8ybujumJ7pz3Zftb+SX4DPiB9xP2FPNY8SvzO/hD+zf4IvRm877ztvCB6xrq/uy174/vdO6i7gPvue4Q7vPt2e5D8SX14feQ+Cj5CPtJ/ML6mvnY+0j/LwCv/sH+IwFqApoANP5t/hoBbwPpA9UEmQY7CJIJ5wplDXwP0BFVFQEYVBqqHJ8eEh5oG7Yc3iCfIosgXh3mHHQbDRdoDWX/gvni/5oInwRE+Sn6uQCu/DPwk+sp9Gr6FfzxAU8KURFHEw==",
|
||||
"ERHEC+kGlArmDx8PWw2uEOMVzhIFCgwCmPpv9Sf0pfRG8/zz2Pn/+uDxvegF6qvusuyA6rHvSvg4/OL6H/mP9kT0SvS/9Ff1nvfL/I//C/th9MHwbu8x7ZXqlup27MrukPD770jsBehq5x/qx+xL7srw0vRr99H3t/Yf9vr3g/qt/OX+cwJyBpUFwQCh/hQAKwDB/Vj/6QUhCfkF7AJVBFIGRwZqB1YLQQ9OEhsWwhmrG28brxr6GpsbxR2pIR0kISWeIpEedBt/EAkB2fhhAAMO1wrH/v77UP+a+lDtjuni8DL3afwLBEYLkgwEDHoOqAzbB2sJ9BBtF6YY1RgXGA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"1t693YLb59eW1i7TjtJD1wbbNN8C4iDjwuab6cXpjOkp6qzt0vJO9oP4Sfpu+0j6OPco9Y/1Kfda93b2k/at9yr38/P18Ffw0u8x77Hwh/IK83fyRvIG9C/27va19m34Wvzg/pUB6wXvCFMLZQ1kD4cTjBbYGK4crB4nIsUmuinxLWAvfy7ULF0lFSDUIocnuSvrKNgiuSIrHcERzQqKCFkLQQk/AdMCUAe5Am746O2M7J7vKuso6mjxoffc+J3y1e6d8k/zePEI89334/5qA/UEOwgrCtcJUggHB+0IYQuKDuwRhRLAEu4SaREYEKwM3Qj7CAYILAZdBXwDWQJS/w==",
|
||||
"Zvjz8yDyUfBE7zHtfeyc7brrZuhn5nblkubW5i3lJeaL6OrpN+pQ6ejpruuS64rrTe3q7wLypfF18QTz/vI38rzyI/Rf9hD3ZPWV9OLzzfH08Lry8/X09zD32/Wa9lP3XfXQ84f1YPiX+j78Ov6tAS4DLABH/qb/4QG0BAIGWQgMDVAOcQ04Dm8O/A9IEkoSvRUvGhscOh9JIFAgrCLEIToeIBy2GgEediH9Hj8daRyPGvoWBhBFDUoPuwx1B4IEUAVdCGUEjvxd+T330vTd8f7wCPbX+Iz2R/X79B72vPaJ9D/1JvgM+pb9bwHyBEIH5gZLB+AHEAdZB5gJ9AxfDg==",
|
||||
"Sg1HDmsQRhD5DX0KZwhLBhECDQDKAHsB/QCi/dj6GvrY9lnzgPGd753uMu0r7Trw7PAH767tlezY7KDsHuzp7lnxY/EZ8tnzqfWZ9Y7zJfNI9IP0jvQ29WH2JfcL94v2QPYI9nj0FPMe88XzvPSJ9Hnze/Ok82TzffMA85ryofLg8s70wvYY+L75/PrW+kb6Lvuj/In+hv+4/4MBFwQgBfcEVgbpCEcJ0AeJCM8JFQseDfsNEBAjE5QTlxSBFvYXvBpIG8wc+B9zHpkaORj1F+sa4RtxGDoZpRqFFxsS6gxLDG8MkgfuAekCIwVdA63+tfrp+tb4PfNe8bPzZvWH9g==",
|
||||
"FPaL9dn3iPer9sb4M/nA+Gb6DP7BAqkEQAXTCGUKcAgXB/gHlwv6DIcKnQogDlMQGA8hDDALjwr4BSgBNgDqAeEB7P5E/Uf95PvH+Hz1RPM38sDvs+2b71byKvNG8vfwevAs8O7uzu7E8Iny9POh9Pb0XvZ09vv0RvRH8+fye/Of80r19vbk9bP03vMv8m7xdfBG7//vY/Ch8PHxGPL78RfxQ+8/8IjxuvE98730F/Zm90/2z/XZ+L76Hfvk+9P96gCQAbUB0QISA2MDCAPAAhMGEAnPCSAMoAxVDBELxwkgDWgPhw8LEuIUTxdHGn8ZcRtpHrgcoR1hGpIWSBl4GQ==",
|
||||
"+xhDGjQX2RZoGJwRzQ2jC54IkQlwA1//rAN7AWP9rfvh9+T48/dJ8cLx9POK81/15PVd+Cn8t/pA+6z+4P3K/VkANAM3B2YJqQv2EDcTWxCNDlcPVA8UDvELYgztDw8QgQ6dDYALnAeFArP+/vw1+7D4lfhi+Cz3qvVA8xHy5u8J7F/p+Oll7NTtJe4p74HxJ/IQ8Rzwsu+N8OrwcvGs8yf2XPho+GH2OPXz83HxUvDc727vdPC28DHx9fEe8KftdesF6gbqQ+n76XXtbe888AbxUPHP8n3zrPJN9IX3u/lc+0b8xv1/AKwAPgDEAXMBNQLGAz4DUQY8CJkG/QeBBw==",
|
||||
"EwfgCEIHpwhoDCgMUw5FEmoUAxg3GGYYcB4PI78lkycuKT0rKSgiHzIanRynHkwc6RgnGokcCRmwDhQHygQLAKn4gPRg9+P64fiR8zPyIPNY737qYOmS7F3wKfEq9Mj7awERA9wDGAbpCPAIoAk2D98UcheFGDIb2B4tHVsY6RVxFEMS6w2RCokMaAyFCGEFYQHT/sj5xPEa78Pt0Ovt63jrbex97Wnr+upX6/XpburQ69vtoPFk83P1/Pgn+mz6FPp3+f35MvpQ+578SvwA/AH6D/eJ9DHxFu8M7V7qlOn16IbnAeaY40viU+LC4BDfSN+z4FPj1+U25/zovOpO6w==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"wP7V/ur++v4T/xb/Ef8U/xX/Dv8G/wn//f7y/gD///4F/xj/Gv8c/yH/I/8m/0T/WP9S/2X/aP9e/2H/aP9v/3L/eP99/4b/jv+j/7f/vf/b/+X/6/8IAAwAEQAfACQAKQA3AEwATwBZAHYAfwB5AHIAdgCBAHwAewCHAJkAqACjAJkAlwCiAKEAjgCYAJ0AnQCxALsAwADDAMoA1wDVANQA3ADlAO8A/AAIAQYB/QD3APwAAQH9APsA+QD5APEA5wDZAL4ArQCrALMAswCsAKcAmgCbAJ8AlACbAJ4AnwCrAKsApgCjAKYApACgAJ4AmQCYAIsAkgCUAIMAjQCLAA==",
|
||||
"ewBwAHEAdQBnAF4ATgA8AD4ALgAgAB4ADwAQAAcA/P8BAPr/9f/w/+T/4v/i/+T/7v/t/93/2//h/9v/z//M/8z/xP+8/7H/qP+q/6H/mf+X/4v/ef9v/3b/Zv9b/2P/WP9T/0z/QP9F/z//Q/9O/1D/XP9V/03/VP9R/1r/Vv9G/03/V/9Z/17/ZP9j/2X/b/9z/23/a/9z/3z/gP+D/3//dP91/27/Zv90/3n/d/94/3n/gP+M/5L/mv+l/6n/pf+l/6v/pf+j/6z/tf++/7//xP/M/8H/vf/D/7z/q/+c/6//yP/M/9P/2//l//H/6//m//b/CAAGAAQAGwArAA==",
|
||||
"MAA2ACwAJQAgABwAKwAwADIAPwBRAFEAPgBEAEIAOgA9ADoARwBTAF8AZgBnAGoAbABvAGsAcwCCAIUAkACQAI0AkgCPAIcAeAByAIIAiwCGAHYAbwB4AHIAcwCJAIQAeQCJAIcAfACAAH0AgQCDAH4AjACNAIkAlgCEAHcAgwCAAHwAfACEAIkAdwB3AHoAagBjAGwAaQBcAF8AZgBlAFgAVgBVAD0AQABGADoAOgA6ADEAMgAxACkAJgAbABQAEwAKAA0ADQAQABcABAD6//n/8//1/+7/6//p/+r/9v/v/+P/5v/g/9//2v/V/+H/3P/e/+f/2v/a/9r/z//H/w==",
|
||||
"v//C/7b/p/+u/63/qP+n/6H/lv+U/5n/kv+Q/5D/hv97/3b/ff9+/3T/cP9x/2b/Yf9q/2n/bP9z/3P/cP9n/2X/af9j/2D/Zv9r/2//bv97/4n/gf97/3X/cv94/3D/bf94/4P/hf95/3X/gP+C/3X/df98/3r/hP+J/43/l/+O/5T/n/+d/6P/pv+w/8P/0P/a/9z/0v/W/+D/4f/t/+v/7f/4//L/8//u/+D/3f/l/+7/8P/6//n/8f/9//j/9v/+//n/AwAHAAcACgAQABkAHAAlACwANQAyADcAUgBMAE8AZwBiAFoAYQBsAHIAeQB+AHsAfwB+AHoAggCAAA==",
|
||||
"ggCKAIYAiwCVAJkAlwCWAJoAmwCbAKQAswCzALEAvQDBALwAvQDEAMEAvwC9ALMAtwC3ALIAuAC2ALAArACtAK8ApwCnAKYAogCeAJgAmgCUAIsAjwCOAI0AiQCBAIEAdwB4AHwAbABlAGQAYwBhAF0AWgBaAFsAWgBaAE8ASwBNAEYARABFADkAMwAyAB8AHQAjABkAEAAJAAIABQAGAPz/AAADAPj/7f/p/+X/2v/V/9L/0f/K/8T/xP+4/6//rf+l/53/jv+G/5L/k/+O/4//j/+O/4n/ff95/4L/e/9p/2//dP9z/3n/ef9z/3H/a/9q/2//aP9o/3X/dv9u/w==",
|
||||
"cv95/3T/cP9v/27/cP9y/3L/d/95/3n/ff93/3//iv+I/4j/iv+I/4r/k/+R/47/kf+Q/5v/n/+V/5T/lv+c/5b/l/+m/6P/rP+y/6z/tv+5/7X/vf/B/8X/0f/T/9j/1//V/9z/3//l/+P/5P/q/+b/7f/4//r/9/////r/8/8FAP//9/8HAAAA+P/7////AQAFAAoABQAEAAcABgANAAoACQATABUAGgAfACwAKgAhACsAKQArADEAMAA2ADQAMQA0ADEANABDAEcARgBJAEsASABEAEUARwBBAD4ASQBLAEEAPABEAEMAQABJAEYARQBJAEkATABNAE4ASQBHAA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"SABEAEkASgBJAEwASABEAEkATQBJAE0AVABNAFAAUwBIAE0ASwBEAEkARABGAEoAPgA9AEMAQgA8ADIANgA8ADgAOgA7ADwAPQA3ADYAOAAzADEALgArACoAKgAnACQAIwAdABUAFQAYABYAGAAbABMADgAKAAMABAD///v//v////v/+v8AAPv/9v/z/+f/7P/u/+r/6P/l/+b/5f/j/+L/5P/e/9b/2//W/9T/2P/R/9P/0P/M/8//x//H/8j/vP+6/7j/uf++/7r/u/+5/7X/sv+x/7L/r/+3/7T/p/+p/6r/rP+v/7D/r/+w/7n/uP+4/7n/tf+2/7T/uv+6/w==",
|
||||
"s/+9/73/tf+6/77/vP+7/7v/tP+w/7P/sv+x/7f/v/+8/7b/vP++/7T/t//E/8j/xP/C/8P/wf/B/8b/yv/Q/9X/0f/S/9f/1P/W/9j/zv/S/+D/3//k/+f/5P/v//P/9P/8//r/+v8CAAYABwALAAwADQAPAAsAFQAeAB0AIQAaABsAKAAkACcALAApACsALQAsACsAMAAwADEANgA2ADoAPAA4ADsAOgA2ADkAPQBBAEUASwBOAEwAVQBZAFUAVgBaAGEAXABaAGAAXABkAGUAXwBhAF4AXwBjAGQAYgBfAGAAYQBZAFoAXABcAFwAWABaAFkAVABUAFIAUABRAA==",
|
||||
"UQBPAFIAUQBOAFAATQBIAEoARwBDAEAAQABHAEEAPAA6ADYANwA0ADMALwAtACsAIwAjACMAIQAbABcAHAATAA0AFgATAA8AEAAHAAMABgAHAP//AAABAPf//v/8//H/8v/v/+3/6f/c/+L/5v/W/9z/4P/Z/9f/zP/O/8//yv/K/8H/wP/A/7v/xP/D/7n/uf+y/6//uP+x/6z/tv+y/6b/qv+s/6n/qv+u/7X/s/+v/7D/rP+y/7f/tP+3/7r/u//C/8P/wP/C/8T/yP/H/8X/x//E/8b/zf/L/8r/0f/R/9D/0f/U/9z/4P/Z/9j/2//Y/+D/4P/a/9//3f/c/w==",
|
||||
"4//h/+P/6f/u/+//7P/s/+3/8P/x//H/+v/2//T/AQAAAAIABgAFAAcABgAFAAkADwARAA8ADgARABkAFwAaACAAFwAbACAAHAAgAB8AJQAmACQAJwAlACgAKwAsACoAKQAyACwAKgAzADEANAAyAC4AMAAsACwAKwApACUAJQApACYAIgAkACYAJQAjACQAIAAfACEAIAAeABoAGAAaABsAGQAZABgAEAAQABcAFwATABcAFgAMAA0AEAALAA0ADQAKAA0ACwAFAAIA/v8CAAMA//8CAAMABAADAPz/+P/9//3/+P/2//T/9//5//n/9v/z//P/8f/x//D/7//y/w==",
|
||||
"8f/v//L/9P/r/+z/7f/n/+n/5//q/+v/5f/m/+f/6v/p/+L/5v/m/+P/6//n/+b/6v/o/+z/7v/r/+3/8P/x/+//7f/t//H/7v/q//H/9P/s/+7/8P/u//L/9f/1//H/8f/x//P/+P/y//H/9//2//X/9P/z//f/9//5//r/+f/4//j//f/8//f/+P////3///8GAP//BwAJAP3/AwADAAEAAAD9/wEAAQD+/wEAAwAAAAEAAwAEAAQAAQADAAIA/v8CAAcACgAGAAMABwAHAAQABwAHAAkADgALAAsADQALAA0AEgASABAADQAOABIADgAPABMAEQATABIADwATAA==",
|
||||
"EgAPAA8AEgATABEAEwATAA4ADwATABYAEwAOAA0ADgATABEADwAUAA8ACwAMAAwADQAOAAsADwAPAAgACQAJAAMABQAJAAcABgAIAAkACQAEAAYACAACAAEAAgD9//7/AgD///z/+v/5//z/+P/0//j/9f/2//f/8//1//L/7//v//L/8P/o/+z/7//r/+n/6//t/+n/6P/q/+j/6P/p/+b/5//q/+f/4v/n/+z/5//n/+n/5//k/+L/6f/p/+L/6P/m/+T/6f/m/+j/6f/h/+T/6P/o/+3/7f/q/+z/7v/s//D/9f/v/+//8//x//L/8P/z//T/8//4//n/9//4/w==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"9v/1//b/7v/w//f/8//0//L/8//3//H/8v/1//H/8//z//H/9P/0//P/9P/x//D/8//y//L/9f/w//L/9//1//P/9v/4//r//P/7//v//f///wIAAwACAAUABgADAAcABwAGAAwACwALAAoACgAOAA0ADwAPAA0ADwAOAA8AEQASABMAEgAVABMAEAAUABgAGQAWABYAGQAZABsAHAAcAB0AHAAiACAAHAAgACAAHgAhACMAJQAlACQAJwAkACEAIQAgACYAJQAjACYAJgAnACgAJQAkACcAKAAmACQAIwAkACYAJQAmACUAIAAgAB8AIwAiAB0AIAAfABsAGwAYAA==",
|
||||
"GAAbABkAGAAbABUAFAATABIAEAAQABMADAANABEACwANAAwABgAIAAYABAAEAPz/AAAGAPn/+/8BAPn/+v/7//f/+P/3//b/8//y//P/8v/z/+7/7v/s/+b/6P/l/+L/5f/j/+P/4P/d/9z/2P/W/9b/1v/W/9L/0v/T/9L/0f/O/8//zv/J/8v/yf/J/8//x//F/83/xP/F/8z/wv/F/8b/wv/I/8X/xP/I/8T/xv/C/8L/xP/B/8T/xf/H/8P/wP/I/8b/xv/G/8f/zP/E/8T/yP/G/8X/w//J/87/yf/K/87/0P/Q/9L/1P/S/9T/2P/Y/9r/2//c/+H/4//j/w==",
|
||||
"5P/k/+T/6P/r/+z/7//w/+//8f/2//j//P/9//7/AwABAAMABwAEAAoAEgAOABMAFwAXAB4AHAAbACIAIwAkACgAKQAqACwALgAxADIAMgAxADYAOgA5ADoAPgA+ADwAPAA8AEEARgA+AD0AQAA8AD4AQQA/AD8APwA/AEAAPAA7ADwANgA2ADwANwAyADUANQA1ADMAMAAzADAALAAvAC0AKgArACkAKwArACcAKgAnACMAKAAlACMAIgAdAB4AIQAeABsAHAAaABQAEwAVABQAEAAOAA4ADAAIAAQAAwABAPz//f/8//v/+v/2//r/9f/s//P/8v/r/+n/6f/q/w==",
|
||||
"6f/n/+P/4P/j/+H/4f/f/9r/3f/a/9v/4P/e/9z/3v/c/9z/3//d/9z/4P/f/97/4P/e/9//5//n/+P/5P/l/+f/6//x//H/8v/z//T/+v/4//b/9//1//b/9v/3//j/9//6//v/+//+//////8AAP///v8EAAMAAwAFAAEABAAFAAQABAADAAQABAAIAAkABAAGAAgABwAJAAgABQAEAAMABQAFAAEA/f/+/wEAAAD//wEAAwAAAAAAAgD8//7/AAD6//r/+v/8//r/9//6//f/8//2//T/9P/2//L/8P/z//D/8P/x/+//8v/v/+//8v/w/+//7f/v//L/8P/z/w==",
|
||||
"9P/z//X/9f/z//P/9f/2//T/9P/1//T/9v/1//X/9v/1//X/9f/3//j/+v/7//j/9//5//z//v/9//7//v/9////AQACAAQABgAFAAQAAwADAAMABQAGAAUABQAHAAkADgALAAgADAAOAA8ADwAOAA4ADAAPABAADAAMAAwADgAOAA4AEQASAA8ACgAMAAkACwAaAA0A/f8CAAIAAAAGAAUABAAIAAYABQAFAAIAAgD/////AAD+////AgAEAAIAAAD///7//v/+//z//P/8//3//f/9//z//f/+//7//f/8//7////9//3//P/9//7//v/+///////+/////////w==",
|
||||
"//8AAAEAAAAAAAAA//8AAAEAAAAAAAAAAAABAAEAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAA//////////8AAP//////////AAAAAP//AAAAAAEAAAAAAAAAAAAAAAAAAAABAAEAAAABAAAAAAABAAAAAQABAAAAAQAAAAAAAAAAAAAAAAABAAEAAQAAAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAAAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"AAABAAEAAQABAAEAAQABAAEAAwACAAIAAwACAAIAAgABAAIAAQABAAIABQAGAAQAAwAEAAQABAAEAAMAAwADAAMAAwADAAMAAgACAAIAAgACAAEAAQACAAEAAQACAAIAAQABAAEAAgABAAIAAgABAAIAAQABAAIAAgABAAIAAgACAAIAAQABAAIAAgACAAEAAQACAAIAAgACAAIAAgACAAEAAQACAAIAAQABAAIAAgACAAIAAgACAAIAAQABAAEAAQACAAIAAQABAAEAAgACAAEAAQACAAIAAQABAAIAAQACAAIAAQABAAIAAgACAAIAAgABAAEAAgACAAIAAgABAA==",
|
||||
"AQABAAIAAQABAAEAAgABAAIAAQABAAEAAQACAAIAAQABAAEAAgABAAEAAgACAAIAAgACAAEAAQABAAIAAgACAAEAAQABAAIAAgACAAEAAgACAAEAAQACAAEAAQABAAEAAQABAAEAAQACAAEAAQABAAEAAQABAAEAAQABAAEAAgACAAEAAQABAAIAAgABAAIAAQABAAIAAgABAAIAAgABAAEAAgABAAIAAgACAAIAAgACAAIAAgABAAEAAgACAAEAAQACAAIAAgABAAIAAgABAAEAAQABAAIAAQABAAEAAgABAAEAAgACAAIAAgABAAEAAgABAAEAAgABAAEAAgABAA==",
|
||||
"AQACAAIAAQABAAIAAQABAAIAAQACAAIAAQACAAIAAgABAAEAAgABAAEAAQABAAIAAgABAAIAAQABAAEAAQABAAEAAQABAAEAAQACAAEAAQABAAIAAgABAAEAAQABAAEAAQACAAEAAQABAAEAAQABAAEAAQABAAEAAAAAAAEAAQABAAEAAQABAAEAAQABAAEAAgACAAEAAgACAAEAAgACAAIAAgABAAEAAQACAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAAAAAAAAAAAAAAAQAAAAEAAQABAAEA/////wAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"TgVHBGID0AIpAt8BdAIVBNcF1QYJBxUG6gRRBN0DlgPtA1wFuQYmB+sG5QbvBtIF1QR1BLIEDgXABAYFMgXsBBkE+wJzAvcBlgHdAHwArQCGAL0A2AByAO7/V/9+//T+Lv2f/PH83/wn/A/8wPz4+5T6cfrd+tv5Nfhc+AD51viV+HT43ves9qH2Nfd19pX1Hvb49mz29/X19or3pPb39Y72tfZV9j/3//hq+cH4dPnN+t/6P/oo+rn60/ok+zf80fxE/d39jv4K/3f/ugCiAe0BhgJeAzoEOQQ2BK8E7QSMBT4G6AYQB7IGNAdPCDkJXQl+CQcKRgo7CnwKggvsCw==",
|
||||
"7guXDHMNDw7KDQ8OhQ7aDWsNpAyHDCUNtg2YDrAMCwq6CEwIGQlPCOQGsQV3BJMEKAQCAxsCFwGLAM//5/96AbwC2wIpAXL/1/6G/tH9UvwA/DP9Ff6m/Q78gPo/+ZT3ePX587L01PfG+dv36PSI9BX2PfXU8WPw/PFi9Cn1uvQJ9e71YfbT9CXy1vFB9ED2yfXw9Nb15vaw9oL18fPg8onzx/Xj9hz2a/Zc+Mb4xvab9Sz3//iA+cb5W/p/+/v8Ev6r/fz8sP4TAbUBAgJpBDcHjAciB+kHHgmTCb4KCQ19DcMMkA01D8kPLg/CD4gR/RFzEoATahSoFCwUWxQBEw==",
|
||||
"ThHKEQ0UfRQdDvAGmgZ7C1UNZAgDBMwEJQb3AwMBagApAbcAcf+1/0sD/wiZCuYFvABPAVgEMQOoADkBewK6ATAB+wHNAOH85fnb91H03PIB9/f6xfgs9ILzhfXQ9FTyEvHg8Fnxa/JP9En2NvhQ+Gr0LfAv8IzzK/Xa863z5fRe9S315vQT9LHxJPAp8cTy6fOO9F31BPUn85DyrfNT9U/13fMt9E73yPvf/VH8E/ok+v77OP0Q/tv/IQLVAnoCNgN1BAsFdAXjBrYIiQm5CnINCRBEEAcPMQ9jEcAThRRWFEoU5RS5FTUWsxaTF/IYLhmzF7sWcRbQEpwJbwJOBQ==",
|
||||
"1grrCIoCVAIgB6gEvf32+tz7Q/z3+5MAIAb3CPcM9g7tCq4EowRqCd4IAQXcBTcKDAyHCX4Ebv2e96D2OPYT8x3zK/pf/pv3Ne/w7/T0ePOL7Z3sfPEX92T5WPi39rz1N/X28ufvdPGq9jH6z/hz9EvyRPIP8pbv1uoW6evsPPL48qzvb+4F8LjvsOy17IvxV/ay9373nvlZ/Ab9pvtI+RH5pfun/zcCOAKaAnkDAAPEAGP/CAFeAwQFYgbgBz4JGQqLC0kMjgviC2UPZxTDFswXNBopHNYaJBhtGWMdnB/fHqwcYxu3GjYYeA5bACX8XgRYCuYCh/rO/5cFw/698w==",
|
||||
"F/I9+G/6S/zhAjMJ5Q7+EkISEQvnBIsJaw61CksHowvGEkARIwmRAR37s/eK9lL0MfFe8z37W/xx893sOvCl8ybujumJ7pz3Zftb+SX4DPiB9xP2FPNY8SvzO/hD+zf4IvRm877ztvCB6xrq/uy174/vdO6i7gPvue4Q7vPt2e5D8SX14feQ+Cj5CPtJ/ML6mvnY+0j/LwCv/sH+IwFqApoANP5t/hoBbwPpA9UEmQY7CJIJ5wplDXwP0BFVFQEYVBqqHJ8eEh5oG7Yc3iCfIosgXh3mHHQbDRdoDWX/gvni/5oInwRE+Sn6uQCu/DPwk+sp9Gr6FfzxAU8KURFHEw==",
|
||||
"ERHEC+kGlArmDx8PWw2uEOMVzhIFCgwCmPpv9Sf0pfRG8/zz2Pn/+uDxvegF6qvusuyA6rHvSvg4/OL6H/mP9kT0SvS/9Ff1nvfL/I//C/th9MHwbu8x7ZXqlup27MrukPD770jsBehq5x/qx+xL7srw0vRr99H3t/Yf9vr3g/qt/OX+cwJyBpUFwQCh/hQAKwDB/Vj/6QUhCfkF7AJVBFIGRwZqB1YLQQ9OEhsWwhmrG28brxr6GpsbxR2pIR0kISWeIpEedBt/EAkB2fhhAAMO1wrH/v77UP+a+lDtjuni8DL3afwLBEYLkgwEDHoOqAzbB2sJ9BBtF6YY1RgXGA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"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",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"Hvsn/D793P07/pr+v/6t/vX++P8YASACYAOIBFAFYAUwBVMFgwUcBgkHAwgTCRkK2gqbCsMJXAlrCZ8JvQkMCgoLWgwFDc8MGwzNC+kLiQuECzoMbg2/Dk0PhQ8mDzUOSg1xDFsMwQxbDSQOaw5YDg4OyQ2DDYcMZAu1Cv0KOgw4DdsNDA6pDesM7wtOC9YKMQrMCeEJkgo9C3wLWguXCh0KlQm9CGwIOwhHCPgHjAeeBy4HlwaCBYMENgSKA3YDfANsA4MDvQJhAi4CzAGnAQ4BsQBXAAwA5P/R/t/9Xf31/Mb8bfxb/N37FPv2+rj6RvrM+bL5z/k2+cP4g/j29w==",
|
||||
"LfdL9jX2OfZO9t72Sfev93P3yPYd9l/18/TJ9C/1uPX09f31yfWa9RP1V/Sy80jzhvMv9Pr0kvXY9cD1EfWD9GT0QfQQ9A70XfS69ET13vUO9uP1jvWi9Rn2e/YQ98X3SPiR+J74qPje+Bb5CfnF+PL4rPlr+tj67/om+4n76/tK/KL8K/3b/X/+EP9Q/1z/iP/D/93/5v9KAAYBrQH1ASECtAIkAzQDbAO3A/QDTATgBHsF6AVHBncGMgbABZUFlgXGBS0GkQbbBgoHQwd1B3UHggeWB2EHOAfJB6cIDAn1CK8IbQi0B9cGwgYHBzAHpweaCGoJowmVCT0JVQhoBw==",
|
||||
"NwemByQItQhZCXoJDwmqCBgIBQcVBtcFGAaMBkwH9AfqB20H2wYXBgcFYgRyBK8E3AQ0BXEFFgWEBN8DJANvAvQB7wEGAksCxgLxAoECsgEOAZgAJgDp//L/JABrAHMAJQDG/z7/r/4y/vn9Rf6l/sb+p/6B/kH+wf1I/dH8nPyr/Nf8PP2C/YL9Ov2x/Df85PvK+8f7qPuK+2/7W/sT+5v6PPrS+aH5x/kp+qz6wPqh+nb6DfrA+a/51/kT+lf6yfoQ+wv79frJ+pT6RPr9+Tj64/pq+5P7xPvl+6r7NPvK+rj64foe+2n7zvsj/GT8ovyH/DT8+PsR/H385/xe/Q==",
|
||||
"1v0s/jf+8f3T/cr9v/3x/TH+rf5a/9X/6P+a/2D/Ov8c/yD/Mv9d/5f/+f9PADsACgALAOz/tv/T/0MAvgAKAU8BkwGzAbQBgwE/ASkBUQGUAeMBIwJAAj4CNwJYAmcCTAJEAl8CewKOAsQCAQP6AtcC1gLWAr4CqQKjAqQCsQK4AsEC3wIIAyYDIAMoAzcDOAMwAxcDBgPuAu0C9ALdAucC6QLWAskCrAKZAoYCfAKGApECowKyArcCowJ6AlYCIwLqAcwBwQHHAcgBxAGvAXgBXwFNARQB2QC3AL8AwwDCANwA2ACqAG8ASAA1ABsAFQAgAC4AQgBdAHcAZwA0AA==",
|
||||
"9v/M/9D/zP/D/9b/2v/Q/8T/rf+M/1f/Mv8l/wb//P4K/yD/L/8T/wb/8v7A/pz+h/51/l3+aP5w/lX+NP4H/v39B/4D/hT+IP4Z/h3+DP4B/hH+A/7u/dj9zv3R/cP9xf29/aT9l/2O/Yf9fv2M/br9x/21/cL90/2+/bT92f3p/fv9Pv5O/kv+f/6n/rv+zf7b/uf+8f4V/1D/bv+B/7P/1v/h/8r/n/+P/4P/f/+L/5f/q/+9/8X/2P/j/87/y//N/8L/w//d/wcAIwAnAB8AMwBCADIAUAB4AJgAzADhAPwAGQEYASQBNQFMAV0BdAGaAasBwgHOAcYBuwGuAQ==",
|
||||
"oAGgAbUBuAG+Ad0B4QHLAbUBqwGpAaABpAG5AcYB3wH9AQ4CEgL+AekB7wHwAekB/gEWAiECHAISAhoCBgLjAdQB0QHVAdIB1QHKAakBjgFhATcBIgEaARMBCAEAAfQA6wDOALoAuQCsALIAxQDNANMA1ADXANUAswCJAH4AdgBkAGYAWgBIAEUALgAaAAwA7f/I/6b/nf+Y/4j/f/93/3D/Uf8o/x//FP8A//f+/P4L/wj/Bv8Q/wb/5P7Y/tP+w/7U/tr+xv7H/rP+l/6a/or+f/55/mv+cf5v/m/+df53/oT+cP5X/mT+ZP5g/nP+fP5//o7+nf6o/pz+mv6z/g==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"wP7V/ur++v4T/xb/Ef8U/xX/Dv8G/wn//f7y/gD///4F/xj/Gv8c/yH/I/8m/0T/WP9S/2X/aP9e/2H/aP9v/3L/eP99/4b/jv+j/7f/vf/b/+X/6/8IAAwAEQAfACQAKQA3AEwATwBZAHYAfwB5AHIAdgCBAHwAewCHAJkAqACjAJkAlwCiAKEAjgCYAJ0AnQCxALsAwADDAMoA1wDVANQA3ADlAO8A/AAIAQYB/QD3APwAAQH9APsA+QD5APEA5wDZAL4ArQCrALMAswCsAKcAmgCbAJ8AlACbAJ4AnwCrAKsApgCjAKYApACgAJ4AmQCYAIsAkgCUAIMAjQCLAA==",
|
||||
"ewBwAHEAdQBnAF4ATgA8AD4ALgAgAB4ADwAQAAcA/P8BAPr/9f/w/+T/4v/i/+T/7v/t/93/2//h/9v/z//M/8z/xP+8/7H/qP+q/6H/mf+X/4v/ef9v/3b/Zv9b/2P/WP9T/0z/QP9F/z//Q/9O/1D/XP9V/03/VP9R/1r/Vv9G/03/V/9Z/17/ZP9j/2X/b/9z/23/a/9z/3z/gP+D/3//dP91/27/Zv90/3n/d/94/3n/gP+M/5L/mv+l/6n/pf+l/6v/pf+j/6z/tf++/7//xP/M/8H/vf/D/7z/q/+c/6//yP/M/9P/2//l//H/6//m//b/CAAGAAQAGwArAA==",
|
||||
"MAA2ACwAJQAgABwAKwAwADIAPwBRAFEAPgBEAEIAOgA9ADoARwBTAF8AZgBnAGoAbABvAGsAcwCCAIUAkACQAI0AkgCPAIcAeAByAIIAiwCGAHYAbwB4AHIAcwCJAIQAeQCJAIcAfACAAH0AgQCDAH4AjACNAIkAlgCEAHcAgwCAAHwAfACEAIkAdwB3AHoAagBjAGwAaQBcAF8AZgBlAFgAVgBVAD0AQABGADoAOgA6ADEAMgAxACkAJgAbABQAEwAKAA0ADQAQABcABAD6//n/8//1/+7/6//p/+r/9v/v/+P/5v/g/9//2v/V/+H/3P/e/+f/2v/a/9r/z//H/w==",
|
||||
"v//C/7b/p/+u/63/qP+n/6H/lv+U/5n/kv+Q/5D/hv97/3b/ff9+/3T/cP9x/2b/Yf9q/2n/bP9z/3P/cP9n/2X/af9j/2D/Zv9r/2//bv97/4n/gf97/3X/cv94/3D/bf94/4P/hf95/3X/gP+C/3X/df98/3r/hP+J/43/l/+O/5T/n/+d/6P/pv+w/8P/0P/a/9z/0v/W/+D/4f/t/+v/7f/4//L/8//u/+D/3f/l/+7/8P/6//n/8f/9//j/9v/+//n/AwAHAAcACgAQABkAHAAlACwANQAyADcAUgBMAE8AZwBiAFoAYQBsAHIAeQB+AHsAfwB+AHoAggCAAA==",
|
||||
"ggCKAIYAiwCVAJkAlwCWAJoAmwCbAKQAswCzALEAvQDBALwAvQDEAMEAvwC9ALMAtwC3ALIAuAC2ALAArACtAK8ApwCnAKYAogCeAJgAmgCUAIsAjwCOAI0AiQCBAIEAdwB4AHwAbABlAGQAYwBhAF0AWgBaAFsAWgBaAE8ASwBNAEYARABFADkAMwAyAB8AHQAjABkAEAAJAAIABQAGAPz/AAADAPj/7f/p/+X/2v/V/9L/0f/K/8T/xP+4/6//rf+l/53/jv+G/5L/k/+O/4//j/+O/4n/ff95/4L/e/9p/2//dP9z/3n/ef9z/3H/a/9q/2//aP9o/3X/dv9u/w==",
|
||||
"cv95/3T/cP9v/27/cP9y/3L/d/95/3n/ff93/3//iv+I/4j/iv+I/4r/k/+R/47/kf+Q/5v/n/+V/5T/lv+c/5b/l/+m/6P/rP+y/6z/tv+5/7X/vf/B/8X/0f/T/9j/1//V/9z/3//l/+P/5P/q/+b/7f/4//r/9/////r/8/8FAP//9/8HAAAA+P/7////AQAFAAoABQAEAAcABgANAAoACQATABUAGgAfACwAKgAhACsAKQArADEAMAA2ADQAMQA0ADEANABDAEcARgBJAEsASABEAEUARwBBAD4ASQBLAEEAPABEAEMAQABJAEYARQBJAEkATABNAE4ASQBHAA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"SABEAEkASgBJAEwASABEAEkATQBJAE0AVABNAFAAUwBIAE0ASwBEAEkARABGAEoAPgA9AEMAQgA8ADIANgA8ADgAOgA7ADwAPQA3ADYAOAAzADEALgArACoAKgAnACQAIwAdABUAFQAYABYAGAAbABMADgAKAAMABAD///v//v////v/+v8AAPv/9v/z/+f/7P/u/+r/6P/l/+b/5f/j/+L/5P/e/9b/2//W/9T/2P/R/9P/0P/M/8//x//H/8j/vP+6/7j/uf++/7r/u/+5/7X/sv+x/7L/r/+3/7T/p/+p/6r/rP+v/7D/r/+w/7n/uP+4/7n/tf+2/7T/uv+6/w==",
|
||||
"s/+9/73/tf+6/77/vP+7/7v/tP+w/7P/sv+x/7f/v/+8/7b/vP++/7T/t//E/8j/xP/C/8P/wf/B/8b/yv/Q/9X/0f/S/9f/1P/W/9j/zv/S/+D/3//k/+f/5P/v//P/9P/8//r/+v8CAAYABwALAAwADQAPAAsAFQAeAB0AIQAaABsAKAAkACcALAApACsALQAsACsAMAAwADEANgA2ADoAPAA4ADsAOgA2ADkAPQBBAEUASwBOAEwAVQBZAFUAVgBaAGEAXABaAGAAXABkAGUAXwBhAF4AXwBjAGQAYgBfAGAAYQBZAFoAXABcAFwAWABaAFkAVABUAFIAUABRAA==",
|
||||
"UQBPAFIAUQBOAFAATQBIAEoARwBDAEAAQABHAEEAPAA6ADYANwA0ADMALwAtACsAIwAjACMAIQAbABcAHAATAA0AFgATAA8AEAAHAAMABgAHAP//AAABAPf//v/8//H/8v/v/+3/6f/c/+L/5v/W/9z/4P/Z/9f/zP/O/8//yv/K/8H/wP/A/7v/xP/D/7n/uf+y/6//uP+x/6z/tv+y/6b/qv+s/6n/qv+u/7X/s/+v/7D/rP+y/7f/tP+3/7r/u//C/8P/wP/C/8T/yP/H/8X/x//E/8b/zf/L/8r/0f/R/9D/0f/U/9z/4P/Z/9j/2//Y/+D/4P/a/9//3f/c/w==",
|
||||
"4//h/+P/6f/u/+//7P/s/+3/8P/x//H/+v/2//T/AQAAAAIABgAFAAcABgAFAAkADwARAA8ADgARABkAFwAaACAAFwAbACAAHAAgAB8AJQAmACQAJwAlACgAKwAsACoAKQAyACwAKgAzADEANAAyAC4AMAAsACwAKwApACUAJQApACYAIgAkACYAJQAjACQAIAAfACEAIAAeABoAGAAaABsAGQAZABgAEAAQABcAFwATABcAFgAMAA0AEAALAA0ADQAKAA0ACwAFAAIA/v8CAAMA//8CAAMABAADAPz/+P/9//3/+P/2//T/9//5//n/9v/z//P/8f/x//D/7//y/w==",
|
||||
"8f/v//L/9P/r/+z/7f/n/+n/5//q/+v/5f/m/+f/6v/p/+L/5v/m/+P/6//n/+b/6v/o/+z/7v/r/+3/8P/x/+//7f/t//H/7v/q//H/9P/s/+7/8P/u//L/9f/1//H/8f/x//P/+P/y//H/9//2//X/9P/z//f/9//5//r/+f/4//j//f/8//f/+P////3///8GAP//BwAJAP3/AwADAAEAAAD9/wEAAQD+/wEAAwAAAAEAAwAEAAQAAQADAAIA/v8CAAcACgAGAAMABwAHAAQABwAHAAkADgALAAsADQALAA0AEgASABAADQAOABIADgAPABMAEQATABIADwATAA==",
|
||||
"EgAPAA8AEgATABEAEwATAA4ADwATABYAEwAOAA0ADgATABEADwAUAA8ACwAMAAwADQAOAAsADwAPAAgACQAJAAMABQAJAAcABgAIAAkACQAEAAYACAACAAEAAgD9//7/AgD///z/+v/5//z/+P/0//j/9f/2//f/8//1//L/7//v//L/8P/o/+z/7//r/+n/6//t/+n/6P/q/+j/6P/p/+b/5//q/+f/4v/n/+z/5//n/+n/5//k/+L/6f/p/+L/6P/m/+T/6f/m/+j/6f/h/+T/6P/o/+3/7f/q/+z/7v/s//D/9f/v/+//8//x//L/8P/z//T/8//4//n/9//4/w==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"9v/1//b/7v/w//f/8//0//L/8//3//H/8v/1//H/8//z//H/9P/0//P/9P/x//D/8//y//L/9f/w//L/9//1//P/9v/4//r//P/7//v//f///wIAAwACAAUABgADAAcABwAGAAwACwALAAoACgAOAA0ADwAPAA0ADwAOAA8AEQASABMAEgAVABMAEAAUABgAGQAWABYAGQAZABsAHAAcAB0AHAAiACAAHAAgACAAHgAhACMAJQAlACQAJwAkACEAIQAgACYAJQAjACYAJgAnACgAJQAkACcAKAAmACQAIwAkACYAJQAmACUAIAAgAB8AIwAiAB0AIAAfABsAGwAYAA==",
|
||||
"GAAbABkAGAAbABUAFAATABIAEAAQABMADAANABEACwANAAwABgAIAAYABAAEAPz/AAAGAPn/+/8BAPn/+v/7//f/+P/3//b/8//y//P/8v/z/+7/7v/s/+b/6P/l/+L/5f/j/+P/4P/d/9z/2P/W/9b/1v/W/9L/0v/T/9L/0f/O/8//zv/J/8v/yf/J/8//x//F/83/xP/F/8z/wv/F/8b/wv/I/8X/xP/I/8T/xv/C/8L/xP/B/8T/xf/H/8P/wP/I/8b/xv/G/8f/zP/E/8T/yP/G/8X/w//J/87/yf/K/87/0P/Q/9L/1P/S/9T/2P/Y/9r/2//c/+H/4//j/w==",
|
||||
"5P/k/+T/6P/r/+z/7//w/+//8f/2//j//P/9//7/AwABAAMABwAEAAoAEgAOABMAFwAXAB4AHAAbACIAIwAkACgAKQAqACwALgAxADIAMgAxADYAOgA5ADoAPgA+ADwAPAA8AEEARgA+AD0AQAA8AD4AQQA/AD8APwA/AEAAPAA7ADwANgA2ADwANwAyADUANQA1ADMAMAAzADAALAAvAC0AKgArACkAKwArACcAKgAnACMAKAAlACMAIgAdAB4AIQAeABsAHAAaABQAEwAVABQAEAAOAA4ADAAIAAQAAwABAPz//f/8//v/+v/2//r/9f/s//P/8v/r/+n/6f/q/w==",
|
||||
"6f/n/+P/4P/j/+H/4f/f/9r/3f/a/9v/4P/e/9z/3v/c/9z/3//d/9z/4P/f/97/4P/e/9//5//n/+P/5P/l/+f/6//x//H/8v/z//T/+v/4//b/9//1//b/9v/3//j/9//6//v/+//+//////8AAP///v8EAAMAAwAFAAEABAAFAAQABAADAAQABAAIAAkABAAGAAgABwAJAAgABQAEAAMABQAFAAEA/f/+/wEAAAD//wEAAwAAAAAAAgD8//7/AAD6//r/+v/8//r/9//6//f/8//2//T/9P/2//L/8P/z//D/8P/x/+//8v/v/+//8v/w/+//7f/v//L/8P/z/w==",
|
||||
"9P/z//X/9f/z//P/9f/2//T/9P/1//T/9v/1//X/9v/1//X/9f/3//j/+v/7//j/9//5//z//v/9//7//v/9////AQACAAQABgAFAAQAAwADAAMABQAGAAUABQAHAAkADgALAAgADAAOAA8ADwAOAA4ADAAPABAADAAMAAwADgAOAA4AEQASAA8ACgAMAAkACwAaAA0A/f8CAAIAAAAGAAUABAAIAAYABQAFAAIAAgD/////AAD+////AgAEAAIAAAD///7//v/+//z//P/8//3//f/9//z//f/+//7//f/8//7////9//3//P/9//7//v/+///////+/////////w==",
|
||||
"//8AAAEAAAAAAAAA//8AAAEAAAAAAAAAAAABAAEAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAA//////////8AAP//////////AAAAAP//AAAAAAEAAAAAAAAAAAAAAAAAAAABAAEAAAABAAAAAAABAAAAAQABAAAAAQAAAAAAAAAAAAAAAAABAAEAAQAAAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAAAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
{
|
||||
message: "audio",
|
||||
audio: [
|
||||
"AAABAAEAAQABAAEAAQABAAEAAwACAAIAAwACAAIAAgABAAIAAQABAAIABQAGAAQAAwAEAAQABAAEAAMAAwADAAMAAwADAAMAAgACAAIAAgACAAEAAQACAAEAAQACAAIAAQABAAEAAgABAAIAAgABAAIAAQABAAIAAgABAAIAAgACAAIAAQABAAIAAgACAAEAAQACAAIAAgACAAIAAgACAAEAAQACAAIAAQABAAIAAgACAAIAAgACAAIAAQABAAEAAQACAAIAAQABAAEAAgACAAEAAQACAAIAAQABAAIAAQACAAIAAQABAAIAAgACAAIAAgABAAEAAgACAAIAAgABAA==",
|
||||
"AQABAAIAAQABAAEAAgABAAIAAQABAAEAAQACAAIAAQABAAEAAgABAAEAAgACAAIAAgACAAEAAQABAAIAAgACAAEAAQABAAIAAgACAAEAAgACAAEAAQACAAEAAQABAAEAAQABAAEAAQACAAEAAQABAAEAAQABAAEAAQABAAEAAgACAAEAAQABAAIAAgABAAIAAQABAAIAAgABAAIAAgABAAEAAgABAAIAAgACAAIAAgACAAIAAgABAAEAAgACAAEAAQACAAIAAgABAAIAAgABAAEAAQABAAIAAQABAAEAAgABAAEAAgACAAIAAgABAAEAAgABAAEAAgABAAEAAgABAA==",
|
||||
"AQACAAIAAQABAAIAAQABAAIAAQACAAIAAQACAAIAAgABAAEAAgABAAEAAQABAAIAAgABAAIAAQABAAEAAQABAAEAAQABAAEAAQACAAEAAQABAAIAAgABAAEAAQABAAEAAQACAAEAAQABAAEAAQABAAEAAQABAAEAAAAAAAEAAQABAAEAAQABAAEAAQABAAEAAgACAAEAAgACAAEAAgACAAIAAgABAAEAAQACAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAAAAAAAAAAAAAAAQAAAAEAAQABAAEA/////wAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
],
|
||||
utterance_idx: 0,
|
||||
text: "Hey there!",
|
||||
},
|
||||
];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
paddingTop: 50,
|
||||
paddingBottom: 50,
|
||||
},
|
||||
buttonContainer: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-around",
|
||||
width: "100%",
|
||||
marginBottom: 20,
|
||||
},
|
||||
volumeContainer: {
|
||||
justifyContent: "space-around",
|
||||
width: "100%",
|
||||
marginBottom: 20,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
width: "90%",
|
||||
borderWidth: 1,
|
||||
borderColor: "#ccc",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
# Expo Two-Way-Audio Using Flow API
|
||||
|
||||
This example showcases the basic functions of the expo-two-way-audio module:
|
||||
|
||||
- Capture input raw pcm data
|
||||
- Play raw pcm data (from a hardcoded base64 string)
|
||||
- Display volume level for both input and output
|
||||
- In iOS: show/set microphone mode while recording.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js
|
||||
- Expo CLI
|
||||
- [iOS](https://docs.expo.dev/workflow/ios-simulator/) and/or [Android](https://docs.expo.dev/workflow/android-studio-emulator/) dev environment setup.
|
||||
|
||||
## Run the app
|
||||
|
||||
In the `examples/basic-usage` directory:
|
||||
|
||||
1. Install dependencies:
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Run the app:
|
||||
```sh
|
||||
# Run iOS
|
||||
npm run ios
|
||||
|
||||
# Run Android
|
||||
npm run android
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Some audio features of expo-two-way-audio like Acoustic Echo Cancelling, noise reduction or microphone modes (iOS) don't work on simulator. Run the example on a real device to test these features.
|
||||
|
||||
```bash
|
||||
# iOS
|
||||
npx expo run:ios --device --configuration Release
|
||||
|
||||
# Android
|
||||
npx expo run:android --device --variant release
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "expo-two-way-audio-example",
|
||||
"slug": "expo-two-way-audio-example",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": false,
|
||||
"bundleIdentifier": "expo.modules.twowayaudio.example",
|
||||
"infoPlist": {
|
||||
"UIBackgroundModes": ["audio"],
|
||||
"NSMicrophoneUsageDescription": "Allow Speechmatics to access your microphone"
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"permissions": ["RECORD_AUDIO", "MODIFY_AUDIO_SETTINGS"],
|
||||
"package": "expo.modules.twowayaudio.example"
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"android": {
|
||||
"compileSdkVersion": 34,
|
||||
"targetSdkVersion": 31,
|
||||
"minSdkVersion": 26,
|
||||
"buildToolsVersion": "34.0.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,6 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
const path = require("path");
|
||||
|
||||
const config = getDefaultConfig(__dirname);
|
||||
|
||||
// npm v7+ will install ../node_modules/react and ../node_modules/react-native because of peerDependencies.
|
||||
// To prevent the incompatible react-native between ./node_modules/react-native and ../node_modules/react-native,
|
||||
// excludes the one from the parent folder when bundling.
|
||||
config.resolver.blockList = [
|
||||
...Array.from(config.resolver.blockList ?? []),
|
||||
new RegExp(path.resolve("../..", "node_modules", "react")),
|
||||
new RegExp(path.resolve("../..", "node_modules", "react-native")),
|
||||
];
|
||||
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(__dirname, "./node_modules"),
|
||||
path.resolve(__dirname, "../../node_modules"),
|
||||
];
|
||||
|
||||
config.resolver.extraNodeModules = {
|
||||
"expo-two-way-audio": "../..",
|
||||
};
|
||||
|
||||
config.watchFolders = [path.resolve(__dirname, "../../")];
|
||||
|
||||
config.transformer.getTransformOptions = async () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: false,
|
||||
inlineRequires: true,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = config;
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "expo-two-way-audio-example",
|
||||
"version": "1.0.0",
|
||||
"main": "expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
"expo": "^52.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-native": "0.76.2",
|
||||
"expo-dev-client": "~5.0.3",
|
||||
"expo-build-properties": "~0.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@types/react": "~18.3.12",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"private": true,
|
||||
"expo": {
|
||||
"autolinking": {
|
||||
"nativeModulesDir": "../.."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "ESNext",
|
||||
"paths": {
|
||||
"@speechmatics/expo-two-way-audio": ["../../src/index"],
|
||||
"@speechmatics/expo-two-way-audio/*": ["../../src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
const createConfigAsync = require("@expo/webpack-config");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = async (env, argv) => {
|
||||
const config = await createConfigAsync(
|
||||
{
|
||||
...env,
|
||||
babel: {
|
||||
dangerouslyAddModulePathsToTranspile: ["expo-two-way-audio"],
|
||||
},
|
||||
},
|
||||
argv,
|
||||
);
|
||||
config.resolve.modules = [
|
||||
path.resolve(__dirname, "./node_modules"),
|
||||
path.resolve(__dirname, "../../node_modules"),
|
||||
];
|
||||
|
||||
return config;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
|
||||
# Native
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,270 @@
|
||||
// EventTarget polyfill is required for the Flow SDK to work in React Native
|
||||
import "event-target-polyfill";
|
||||
import { Buffer } from "buffer";
|
||||
import { Button, StyleSheet, Text, View } from "react-native";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
import {
|
||||
type MicrophoneDataCallback,
|
||||
type VolumeLevelCallback,
|
||||
getMicrophoneModeIOS,
|
||||
initialize,
|
||||
playPCMData,
|
||||
toggleRecording,
|
||||
useExpoTwoWayAudioEventListener,
|
||||
useIsRecording,
|
||||
useMicrophonePermissions,
|
||||
} from "@speechmatics/expo-two-way-audio";
|
||||
|
||||
import {
|
||||
FlowProvider,
|
||||
type RecognitionAlternative,
|
||||
useFlow,
|
||||
useFlowEventListener,
|
||||
} from "@speechmatics/flow-client-react";
|
||||
import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
|
||||
import { useSharedValue } from "react-native-reanimated";
|
||||
import { getFlowAPIJwt } from "./auth";
|
||||
import VolumeDisplay from "./volume-display";
|
||||
|
||||
export default function App() {
|
||||
const [micPermission, requestMicPermission] = useMicrophonePermissions();
|
||||
console.log(micPermission);
|
||||
|
||||
if (!micPermission?.granted) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Mic permission: {micPermission?.status}</Text>
|
||||
<Button
|
||||
title={micPermission?.canAskAgain ? "Request permission" : "Cannot request permissions"}
|
||||
disabled={!micPermission?.canAskAgain}
|
||||
onPress={requestMicPermission}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FlowProvider appId="flow-api-example" websocketBinaryType="arraybuffer">
|
||||
<FlowTest />
|
||||
</FlowProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function FlowTest() {
|
||||
const [isConnectedToFlow, setIsConnectedToFlow] = useState(false);
|
||||
const [isConnectingToFlow, setIsConnectingToFlow] = useState(false);
|
||||
const [audioInitialized, setAudioInitialized] = useState(false);
|
||||
const inputVolumeLevel = useSharedValue(0.0);
|
||||
const outputVolumeLevel = useSharedValue(0.0);
|
||||
const isRecording = useIsRecording();
|
||||
const { startConversation, endConversation, sendAudio } = useFlow();
|
||||
|
||||
// Initialize Expo Two Way Audio
|
||||
useEffect(() => {
|
||||
const initializeAudio = async () => {
|
||||
await initialize();
|
||||
setAudioInitialized(true);
|
||||
};
|
||||
|
||||
initializeAudio();
|
||||
}, []);
|
||||
|
||||
// Setup a handler for the "message" event from Flow API
|
||||
useFlowEventListener("message", ({ data }) => {
|
||||
// For the purpose of this example, just print the content of the transcript
|
||||
if (data.message === "AddTranscript") {
|
||||
for (const result of data.results as {
|
||||
alternatives: Array<RecognitionAlternative>;
|
||||
}[]) {
|
||||
for (const alternative of result.alternatives as Array<RecognitionAlternative>) {
|
||||
console.log(alternative.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Setup a handler for the "agentAudio" event from Flow API
|
||||
useFlowEventListener("agentAudio", (audio) => {
|
||||
// Even though Int16Array is a more natural representation for PCM16_sle,
|
||||
// Expo Modules API uses a convertible type for arrays of bytes and it needs Uint8Array in the JS side.
|
||||
// This is converted to a `Data` type in Swift and to a `kotlin.ByteArray` in Kotlin.
|
||||
// More info here: https://docs.expo.dev/modules/module-api/#convertibles
|
||||
// For this reason, the Expo Two Way Audio library requires a Uint8Array argument for the `playPCMData` function.
|
||||
const byteArray = new Uint8Array(audio.data.buffer);
|
||||
playPCMData(byteArray);
|
||||
});
|
||||
|
||||
// Setup a handler for the "onMicrophoneData" event from Expo Two Way Audio module
|
||||
useExpoTwoWayAudioEventListener(
|
||||
"onMicrophoneData",
|
||||
useCallback<MicrophoneDataCallback>(
|
||||
(event) => {
|
||||
// We send the audio bytes to the Flow API
|
||||
sendAudio(event.data.buffer);
|
||||
},
|
||||
[sendAudio],
|
||||
),
|
||||
);
|
||||
|
||||
// Setup a handler for the "onInputVolumeLevelData" event from Expo Two Way Audio module.
|
||||
// This event is triggered when the input volume level changes.
|
||||
useExpoTwoWayAudioEventListener(
|
||||
"onInputVolumeLevelData",
|
||||
useCallback<VolumeLevelCallback>(
|
||||
(event) => {
|
||||
// We update the react-native-reanimated shared value for the input volume (microphone).
|
||||
// This allow us to create animations for the microphone volume based on this shared value.
|
||||
inputVolumeLevel.value = event.data;
|
||||
},
|
||||
[inputVolumeLevel],
|
||||
),
|
||||
);
|
||||
|
||||
// Setup a handler for the "onOutputVolumeLevelData" event from Expo Two Way Audio module.
|
||||
// This event is triggered when the output volume level changes.
|
||||
useExpoTwoWayAudioEventListener(
|
||||
"onOutputVolumeLevelData",
|
||||
useCallback<VolumeLevelCallback>(
|
||||
(event) => {
|
||||
// We update the react-native-reanimated shared value for the output volume (speaker).
|
||||
// This allow us to create animations for the speaker volume based on this shared value.
|
||||
outputVolumeLevel.value = event.data;
|
||||
},
|
||||
[outputVolumeLevel],
|
||||
),
|
||||
);
|
||||
|
||||
// Handle clicks to the 'Connect/Disconnect' button
|
||||
const handleToggleConnect = useCallback(async () => {
|
||||
setIsConnectingToFlow(true);
|
||||
if (isConnectedToFlow) {
|
||||
endConversation();
|
||||
setIsConnectedToFlow(false);
|
||||
} else {
|
||||
let jwt;
|
||||
try {
|
||||
// WARNING: This function is used for the purpose of this example only.
|
||||
// In a real-world scenario, you should not expose your API key in your client-side code.
|
||||
// Instead, you should create a server-side endpoint that generates the JWT for you.
|
||||
jwt = await getFlowAPIJwt(
|
||||
// EXPO_PUBLIC_SPEECHMATICS_API_KEY could be set in your .env file
|
||||
process.env.EXPO_PUBLIC_SPEECHMATICS_API_KEY,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to get JWT:", error);
|
||||
setIsConnectingToFlow(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await startConversation(jwt, {
|
||||
config: {
|
||||
template_id: "flow-service-assistant-humphrey",
|
||||
template_variables: {
|
||||
timezone: "Europe/London",
|
||||
},
|
||||
},
|
||||
// `audioFormat` is optional. The value below is the default:
|
||||
audioFormat: {
|
||||
type: "raw",
|
||||
encoding: "pcm_s16le", // this can also be set to 'pcm_f32le' for 32-bit Float
|
||||
sample_rate: 16000,
|
||||
},
|
||||
});
|
||||
setIsConnectedToFlow(true);
|
||||
} catch (error) {
|
||||
console.error("Failed to start conversation:", error);
|
||||
}
|
||||
}
|
||||
setIsConnectingToFlow(false);
|
||||
}, [isConnectedToFlow, startConversation, endConversation]);
|
||||
|
||||
// Handle clicks to the 'Mute/Unmute' button
|
||||
const handleToggleMute = useCallback(() => {
|
||||
toggleRecording(!isRecording);
|
||||
}, [isRecording]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.VolumeDisplayContainer}>
|
||||
<View style={styles.volumeDisplay}>
|
||||
<VolumeDisplay
|
||||
volumeLevel={outputVolumeLevel}
|
||||
color="#7ECFBD"
|
||||
minSize={130}
|
||||
maxSize={180}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.volumeDisplay}>
|
||||
<VolumeDisplay
|
||||
volumeLevel={inputVolumeLevel}
|
||||
color="#4CBBA5"
|
||||
minSize={70}
|
||||
maxSize={120}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
<Text>
|
||||
{isConnectedToFlow
|
||||
? isRecording
|
||||
? "I'm ready to listen. Try saying something!"
|
||||
: "Muted. Unmute to start a conversation"
|
||||
: isConnectingToFlow
|
||||
? "Connecting..."
|
||||
: "Disconnected"}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.bottomBar}>
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
title={isConnectedToFlow ? "Disconnect" : "Connect"}
|
||||
disabled={isConnectingToFlow || !audioInitialized}
|
||||
onPress={handleToggleConnect}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={isRecording ? "Mute" : "Unmute"}
|
||||
disabled={!isConnectedToFlow || !audioInitialized}
|
||||
onPress={handleToggleMute}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-evenly",
|
||||
padding: 50,
|
||||
},
|
||||
buttonContainer: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-around",
|
||||
width: "100%",
|
||||
marginBottom: 20,
|
||||
},
|
||||
bottomBar: {
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
padding: 20,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: "lightgray",
|
||||
},
|
||||
VolumeDisplayContainer: {
|
||||
position: "relative",
|
||||
width: 150,
|
||||
height: 150,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
volumeDisplay: {
|
||||
position: "absolute",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
# Expo Two-Way-Audio Using Flow API
|
||||
|
||||
This example shows how expo-two-way-audio can be used to communicate with [Speechmatics Flow API](https://www.speechmatics.com/flow)
|
||||
|
||||
Flow is a conversational AI API and therefore, we need to send audio data in real time and play back the audio responses that we'll get from the API.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js
|
||||
- Expo CLI
|
||||
- [iOS](https://docs.expo.dev/workflow/ios-simulator/) and/or [Android](https://docs.expo.dev/workflow/android-studio-emulator/) dev environment setup.
|
||||
- [Speechmatics Flow Client (React)](https://github.com/speechmatics/speechmatics-js-sdk/tree/main/packages/flow-client-react)
|
||||
- [`event-target-polyfill`](https://www.npmjs.com/package/event-target-polyfill) package, or any other polyfill for the [`EventTarget` class](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
|
||||
|
||||
A Speechmatics API key is also needed in order to run the example. The key is injected into the app via environment variable. It can be passed by creating a `.env` file in the same directory as the example with the following content:
|
||||
|
||||
```
|
||||
EXPO_PUBLIC_SPEECHMATICS_API_KEY='YOUR-API-KEY-HERE'
|
||||
```
|
||||
|
||||
An API key can be obtained from the [Speechmatics Portal](https://portal.speechmatics.com)
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> The example uses the API key to request a temporary token (needed for communicating with flow).
|
||||
> In a real-world scenario, you should not expose your API key in your client-side code.
|
||||
> Instead, you should create a server-side endpoint that generates the temporary token (JWT) for you.
|
||||
|
||||
## Run the app
|
||||
|
||||
In the `examples/flow-api` directory:
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Run the app:
|
||||
|
||||
```sh
|
||||
# Run iOS
|
||||
npm run ios
|
||||
|
||||
# Run Android
|
||||
npm run android
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Some audio features of expo-two-way-audio like Acoustic Echo Cancelling, noise reduction or microphone modes (iOS) don't work on simulator. Run the example on a real device to test these features.
|
||||
|
||||
```bash
|
||||
# iOS
|
||||
npx expo run:ios --device --configuration Release
|
||||
|
||||
# Android
|
||||
npx expo run:android --device --variant release
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "Flow Example",
|
||||
"slug": "expo-two-way-audio-flow-example",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": false,
|
||||
"bundleIdentifier": "expo.modules.twowayaudio.flow.example",
|
||||
"infoPlist": {
|
||||
"UIBackgroundModes": ["audio"],
|
||||
"NSMicrophoneUsageDescription": "Allow Speechmatics to access your microphone"
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"permissions": ["RECORD_AUDIO", "MODIFY_AUDIO_SETTINGS"],
|
||||
"package": "expo.modules.twowayaudio.flow.example"
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"android": {
|
||||
"compileSdkVersion": 34,
|
||||
"targetSdkVersion": 31,
|
||||
"minSdkVersion": 26,
|
||||
"buildToolsVersion": "34.0.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,15 @@
|
||||
import { createSpeechmaticsJWT } from "@speechmatics/auth";
|
||||
|
||||
// WARNING: This function is used for the purpose of this example only.
|
||||
// In a real-world scenario, you should not expose your API key in your client-side code.
|
||||
// Instead, you should create a server-side endpoint that generates the JWT for you.
|
||||
export async function getFlowAPIJwt(apiKey: string) {
|
||||
if (!apiKey) {
|
||||
throw new Error("API key is required.");
|
||||
}
|
||||
return await createSpeechmaticsJWT({
|
||||
type: "flow",
|
||||
apiKey,
|
||||
ttl: 60, // 1 minute
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
const { wrapWithReanimatedMetroConfig } = require("react-native-reanimated/metro-config");
|
||||
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
const path = require("path");
|
||||
|
||||
const config = getDefaultConfig(__dirname);
|
||||
|
||||
// npm v7+ will install ../node_modules/react and ../node_modules/react-native because of peerDependencies.
|
||||
// To prevent the incompatible react-native between ./node_modules/react-native and ../node_modules/react-native,
|
||||
// excludes the one from the parent folder when bundling.
|
||||
config.resolver.blockList = [
|
||||
...Array.from(config.resolver.blockList ?? []),
|
||||
new RegExp(path.resolve("../..", "node_modules", "react")),
|
||||
new RegExp(path.resolve("../..", "node_modules", "react-native")),
|
||||
];
|
||||
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(__dirname, "./node_modules"),
|
||||
path.resolve(__dirname, "../../node_modules"),
|
||||
];
|
||||
|
||||
config.resolver.extraNodeModules = {
|
||||
"expo-two-way-audio": "../..",
|
||||
};
|
||||
|
||||
config.watchFolders = [path.resolve(__dirname, "../../")];
|
||||
|
||||
config.transformer.getTransformOptions = async () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: false,
|
||||
inlineRequires: true,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = wrapWithReanimatedMetroConfig(config);
|
||||
config;
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "expo-two-way-audio-flow-example",
|
||||
"version": "1.0.0",
|
||||
"main": "expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
"@speechmatics/auth": "^0.0.1",
|
||||
"@speechmatics/flow-client-react": "^0.2.0",
|
||||
"buffer": "^6.0.3",
|
||||
"event-target-polyfill": "^0.0.4",
|
||||
"expo": "^52.0.25",
|
||||
"expo-build-properties": "~0.13.2",
|
||||
"expo-dev-client": "~5.0.9",
|
||||
"react": "18.3.1",
|
||||
"react-native": "0.76.6",
|
||||
"react-native-reanimated": "~3.16.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@types/react": "~18.3.12",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"private": true,
|
||||
"expo": {
|
||||
"autolinking": {
|
||||
"nativeModulesDir": "../.."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "ESNext",
|
||||
"paths": {
|
||||
"@speechmatics/expo-two-way-audio": ["../../src/index"],
|
||||
"@speechmatics/expo-two-way-audio/*": ["../../src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import Animated, {
|
||||
Easing,
|
||||
interpolate,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
type SharedValue,
|
||||
} from "react-native-reanimated";
|
||||
|
||||
export default function VolumeDisplay({
|
||||
volumeLevel,
|
||||
color,
|
||||
minSize,
|
||||
maxSize,
|
||||
}: {
|
||||
volumeLevel: SharedValue<number>;
|
||||
color: string;
|
||||
minSize: number;
|
||||
maxSize: number;
|
||||
}) {
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
const size = interpolate(volumeLevel.value, [0, 1], [minSize, maxSize]);
|
||||
|
||||
return {
|
||||
width: withTiming(size, {
|
||||
duration: 100,
|
||||
easing: Easing.linear,
|
||||
}),
|
||||
height: withTiming(size, {
|
||||
duration: 100,
|
||||
easing: Easing.linear,
|
||||
}),
|
||||
borderRadius: withTiming(size / 2, {
|
||||
duration: 100,
|
||||
easing: Easing.linear,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Animated.View style={[{ backgroundColor: color }, animatedStyle]} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
const createConfigAsync = require("@expo/webpack-config");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = async (env, argv) => {
|
||||
const config = await createConfigAsync(
|
||||
{
|
||||
...env,
|
||||
babel: {
|
||||
dangerouslyAddModulePathsToTranspile: ["expo-two-way-audio"],
|
||||
},
|
||||
},
|
||||
argv,
|
||||
);
|
||||
config.resolve.modules = [
|
||||
path.resolve(__dirname, "./node_modules"),
|
||||
path.resolve(__dirname, "../../node_modules"),
|
||||
];
|
||||
|
||||
return config;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"platforms": ["ios", "android"],
|
||||
"ios": {
|
||||
"modules": ["ExpoTwoWayAudioModule"]
|
||||
},
|
||||
"android": {
|
||||
"modules": ["expo.modules.twowayaudio.ExpoTwoWayAudioModule"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
import AVFoundation
|
||||
import Foundation
|
||||
|
||||
class AudioEngine {
|
||||
private var avAudioEngine = AVAudioEngine()
|
||||
private var speechPlayer = AVAudioPlayerNode()
|
||||
private var engineConfigChangeObserver: Any?
|
||||
private var sessionInterruptionObserver: Any?
|
||||
private var mediaServicesResetObserver: Any?
|
||||
|
||||
public private(set) var voiceIOFormat: AVAudioFormat
|
||||
public private(set) var isRecording = false
|
||||
|
||||
public var onMicDataCallback: ((Data) -> Void)?
|
||||
public var onInputVolumeCallback: ((Float) -> Void)?
|
||||
public var onOutputVolumeCallback: ((Float) -> Void)?
|
||||
public var onAudioInterruptionCallback: ((String) -> Void)?
|
||||
|
||||
private var inputLevelTimer: Timer?
|
||||
private var outputLevelTimer: Timer?
|
||||
|
||||
private var inputBuffer = [Float](repeating: 0, count: 2048)
|
||||
private var outputBuffer = [Float](repeating: 0, count: 2048)
|
||||
private var inputBufferIndex = 0
|
||||
private var outputBufferIndex = 0
|
||||
|
||||
private var hasFirstInputBeenDiscarded = false
|
||||
private var discardRecording = false
|
||||
private var discardFirstInputMillis = 2000
|
||||
|
||||
enum AudioEngineError: Error {
|
||||
case audioFormatError
|
||||
}
|
||||
|
||||
init() throws {
|
||||
avAudioEngine.attach(speechPlayer)
|
||||
|
||||
guard let format = AVAudioFormat(standardFormatWithSampleRate: 16000, channels: 1) else {
|
||||
throw AudioEngineError.audioFormatError
|
||||
}
|
||||
voiceIOFormat = format
|
||||
print("Voice IO format: \(String(describing: voiceIOFormat))")
|
||||
|
||||
engineConfigChangeObserver = NotificationCenter.default.addObserver(
|
||||
forName: .AVAudioEngineConfigurationChange,
|
||||
object: avAudioEngine,
|
||||
queue: .main) { [weak self] _ in
|
||||
self?.checkEngineIsRunning()
|
||||
}
|
||||
sessionInterruptionObserver = NotificationCenter.default.addObserver(
|
||||
forName: AVAudioSession.interruptionNotification,
|
||||
object: AVAudioSession.sharedInstance(),
|
||||
queue: .main) { [weak self] notification in
|
||||
self?.handleAudioSessionInterruption(notification)
|
||||
}
|
||||
mediaServicesResetObserver = NotificationCenter.default.addObserver(
|
||||
forName: AVAudioSession.mediaServicesWereResetNotification,
|
||||
object: AVAudioSession.sharedInstance(),
|
||||
queue: .main) { [weak self] _ in
|
||||
self?.handleMediaServicesWereReset()
|
||||
}
|
||||
|
||||
self.setupAudioSession()
|
||||
self.setup()
|
||||
self.start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let observer = engineConfigChangeObserver {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
if let observer = sessionInterruptionObserver {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
if let observer = mediaServicesResetObserver {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
func setupAudioSession() {
|
||||
let session = AVAudioSession.sharedInstance()
|
||||
|
||||
do {
|
||||
try session.setCategory(.playAndRecord, mode: .voiceChat, options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP])
|
||||
} catch {
|
||||
print("Could not set the audio category: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
do {
|
||||
try session.setPreferredSampleRate(voiceIOFormat.sampleRate)
|
||||
} catch {
|
||||
print("Could not set the preferred sample rate: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
do {
|
||||
try session.setActive(true)
|
||||
} catch {
|
||||
print("Could not set the audio session as active")
|
||||
}
|
||||
}
|
||||
|
||||
func setup() {
|
||||
let input = avAudioEngine.inputNode
|
||||
do {
|
||||
try input.setVoiceProcessingEnabled(true)
|
||||
} catch {
|
||||
print("Could not enable voice processing \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
avAudioEngine.inputNode.isVoiceProcessingInputMuted = !isRecording
|
||||
|
||||
let output = avAudioEngine.outputNode
|
||||
let mainMixer = avAudioEngine.mainMixerNode
|
||||
|
||||
avAudioEngine.connect(speechPlayer, to: mainMixer, format: voiceIOFormat)
|
||||
avAudioEngine.connect(mainMixer, to: output, format: voiceIOFormat)
|
||||
|
||||
input.installTap(onBus: 0, bufferSize: 2048, format: voiceIOFormat) { [weak self] buffer, when in
|
||||
// We don't do any input processing (no volume calculation or passing mic data to the callback) if discardRecording == true
|
||||
// See comment in the playPCMData function
|
||||
if self?.isRecording == true && self?.discardRecording == false {
|
||||
self?.processMicrophoneBuffer(buffer)
|
||||
self?.updateInputVolume()
|
||||
}
|
||||
}
|
||||
|
||||
mainMixer.installTap(onBus: 0, bufferSize: 2048, format: voiceIOFormat) { [weak self] buffer, when in
|
||||
self?.processOutputBuffer(buffer)
|
||||
self?.updateOutputVolume()
|
||||
}
|
||||
|
||||
avAudioEngine.prepare()
|
||||
}
|
||||
|
||||
func processMicrophoneBuffer(_ buffer: AVAudioPCMBuffer) {
|
||||
guard let channelData = buffer.floatChannelData?[0] else {
|
||||
print("Error: Could not access channel data")
|
||||
return
|
||||
}
|
||||
|
||||
let frameCount = Int(buffer.frameLength)
|
||||
var int16Samples = [Int16](repeating: 0, count: frameCount)
|
||||
|
||||
// Convert float samples to Int16 and update input buffer for volume calculation
|
||||
for i in 0..<frameCount {
|
||||
let floatSample = max(-1.0, min(1.0, channelData[i]))
|
||||
int16Samples[i] = Int16(floatSample * Float(Int16.max))
|
||||
|
||||
inputBuffer[inputBufferIndex] = floatSample
|
||||
inputBufferIndex = (inputBufferIndex + 1) % inputBuffer.count
|
||||
}
|
||||
|
||||
// Create Data object from Int16 samples
|
||||
let data = Data(bytes: int16Samples, count: frameCount * MemoryLayout<Int16>.size)
|
||||
|
||||
// Send the data to the callback
|
||||
onMicDataCallback?(data)
|
||||
}
|
||||
|
||||
func processOutputBuffer(_ buffer: AVAudioPCMBuffer) {
|
||||
guard let channelData = buffer.floatChannelData?[0] else {
|
||||
print("Error: Could not access channel data")
|
||||
return
|
||||
}
|
||||
|
||||
let frameCount = Int(buffer.frameLength)
|
||||
|
||||
// Update output buffer for volume calculation
|
||||
for i in 0..<frameCount {
|
||||
let floatSample = max(-1.0, min(1.0, channelData[i]))
|
||||
outputBuffer[outputBufferIndex] = floatSample
|
||||
outputBufferIndex = (outputBufferIndex + 1) % outputBuffer.count
|
||||
}
|
||||
}
|
||||
|
||||
func start() {
|
||||
do {
|
||||
try avAudioEngine.start()
|
||||
} catch {
|
||||
print("Could not start audio engine: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func playPCMData(_ pcmData: Data) {
|
||||
// Looks like we don't get a proper AEC for the very first chunks of audio that we play.
|
||||
// To work around this, we will discard microphone input for the first few milliseconds.
|
||||
// This will give the AEC time to adapt to the playback audio.
|
||||
// We achieve this by setting discardRecording to true for a short time (this doesn't actually mute the input). It's just not processed in the tap.
|
||||
// Audio that is not processed by the input tap is not sent to the callback and therefore not sent to the server.
|
||||
if !hasFirstInputBeenDiscarded {
|
||||
self.hasFirstInputBeenDiscarded = true
|
||||
self.discardRecording = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(discardFirstInputMillis)) {
|
||||
self.discardRecording = false
|
||||
}
|
||||
}
|
||||
|
||||
guard let buffer = createBuffer(from: pcmData) else {
|
||||
print("Failed to create audio buffer")
|
||||
return
|
||||
}
|
||||
speechPlayer.scheduleBuffer(buffer)
|
||||
|
||||
if !speechPlayer.isPlaying {
|
||||
speechPlayer.play()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func createBuffer(from data: Data) -> AVAudioPCMBuffer? {
|
||||
let frameCount = UInt32(data.count) / 2 // 16-bit input = 2 bytes per frame
|
||||
|
||||
let format = AVAudioFormat(commonFormat: .pcmFormatFloat32,
|
||||
sampleRate: 16000,
|
||||
channels: 1,
|
||||
interleaved: false)!
|
||||
|
||||
guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
buffer.frameLength = frameCount
|
||||
|
||||
data.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) in
|
||||
if let sourcePtr = rawBufferPointer.baseAddress?.assumingMemoryBound(to: Int16.self),
|
||||
let destPtr = buffer.floatChannelData?[0] {
|
||||
for i in 0..<Int(frameCount) {
|
||||
destPtr[i] = Float(sourcePtr[i]) / Float(Int16.max)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
func bypassVoiceProcessing(_ bypass: Bool) {
|
||||
let input = avAudioEngine.inputNode
|
||||
input.isVoiceProcessingBypassed = bypass
|
||||
}
|
||||
|
||||
func toggleRecording(_ val: Bool) -> Bool {
|
||||
isRecording = val
|
||||
if !isRecording {
|
||||
avAudioEngine.inputNode.isVoiceProcessingInputMuted = true
|
||||
// Reset input buffer, so that volume levels report 0
|
||||
inputBuffer = [Float](repeating: 0, count: 2048)
|
||||
updateInputVolume()
|
||||
} else {
|
||||
avAudioEngine.inputNode.isVoiceProcessingInputMuted = false
|
||||
}
|
||||
print("Recording \(isRecording ? "started" : "stopped")")
|
||||
|
||||
return isRecording
|
||||
}
|
||||
|
||||
func stopRecordingAndPlayer(){
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(false)
|
||||
} catch {
|
||||
print("Could not set the audio session to inactive: \(error)")
|
||||
}
|
||||
toggleRecording(false)
|
||||
speechPlayer.stop()
|
||||
updateOutputVolume()
|
||||
}
|
||||
|
||||
func resumeRecordingAndPlayer(){
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
} catch {
|
||||
print("Could not set the audio session to active: \(error)")
|
||||
}
|
||||
self.checkEngineIsRunning()
|
||||
isRecording = toggleRecording(true)
|
||||
speechPlayer.play()
|
||||
}
|
||||
|
||||
func tearDown() {
|
||||
stopRecordingAndPlayer()
|
||||
avAudioEngine.stop()
|
||||
}
|
||||
|
||||
var isPlaying: Bool {
|
||||
return speechPlayer.isPlaying
|
||||
}
|
||||
|
||||
func stopPlayback() {
|
||||
speechPlayer.stop()
|
||||
// Clear any scheduled buffers
|
||||
outputBuffer = [Float](repeating: 0, count: 2048)
|
||||
updateOutputVolume()
|
||||
print("Playback stopped")
|
||||
}
|
||||
|
||||
func pausePlayback() {
|
||||
speechPlayer.pause()
|
||||
print("Playback paused")
|
||||
}
|
||||
|
||||
func resumePlayback() {
|
||||
if !speechPlayer.isPlaying {
|
||||
speechPlayer.play()
|
||||
print("Playback resumed")
|
||||
}
|
||||
}
|
||||
|
||||
private func checkEngineIsRunning() {
|
||||
if !avAudioEngine.isRunning {
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
private func handleAudioSessionInterruption(_ notification: Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
||||
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return }
|
||||
|
||||
switch type {
|
||||
case .began:
|
||||
self.stopRecordingAndPlayer()
|
||||
onAudioInterruptionCallback?("began")
|
||||
case .ended:
|
||||
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
|
||||
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
||||
if options.contains(.shouldResume) {
|
||||
// Interruption ended. Resume playback.
|
||||
self.resumeRecordingAndPlayer()
|
||||
onAudioInterruptionCallback?("ended")
|
||||
} else {
|
||||
// Interruption ends. Don't resume playback.
|
||||
onAudioInterruptionCallback?("blocked")
|
||||
}
|
||||
}
|
||||
@unknown default:
|
||||
fatalError("Unknown type: \(type)")
|
||||
}
|
||||
}
|
||||
|
||||
private func handleMediaServicesWereReset() {
|
||||
self.avAudioEngine.stop()
|
||||
self.setup()
|
||||
self.start()
|
||||
}
|
||||
|
||||
private func updateInputVolume() {
|
||||
let volume = calculateRMSLevel(from: inputBuffer)
|
||||
onInputVolumeCallback?(volume)
|
||||
}
|
||||
|
||||
private func updateOutputVolume() {
|
||||
let volume = calculateRMSLevel(from: outputBuffer)
|
||||
onOutputVolumeCallback?(volume)
|
||||
}
|
||||
|
||||
private func calculateRMSLevel(from buffer: [Float]) -> Float {
|
||||
let epsilon: Float = 1e-5 // To avoid log(0)
|
||||
let rmsValue = sqrt(buffer.reduce(0) { $0 + $1 * $1 } / Float(buffer.count))
|
||||
|
||||
// Convert to decibels
|
||||
let dbValue = 20 * log10(max(rmsValue, epsilon))
|
||||
|
||||
// Normalize decibel value to 0-1 range
|
||||
// Assuming minimum audible is -60dB and maximum is 0dB
|
||||
let minDb: Float = -80.0
|
||||
let normalizedValue = max(0.0, min(1.0, (dbValue - minDb) / abs(minDb)))
|
||||
|
||||
// Optional: Apply exponential factor to push smaller values down
|
||||
let expFactor: Float = 2.0 // Adjust this value to change the curve
|
||||
let adjustedValue = pow(normalizedValue, expFactor)
|
||||
|
||||
return adjustedValue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
require 'json'
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ExpoTwoWayAudio'
|
||||
s.version = package['version']
|
||||
s.summary = package['description']
|
||||
s.description = package['description']
|
||||
s.license = package['license']
|
||||
s.author = package['author']
|
||||
s.homepage = package['homepage']
|
||||
s.platforms = { :ios => '13.4', :tvos => '13.4' }
|
||||
s.swift_version = '5.4'
|
||||
s.source = { git: 'https://github.com/speechmatics/expo-two-way-audio' }
|
||||
s.static_framework = true
|
||||
|
||||
s.dependency 'ExpoModulesCore'
|
||||
|
||||
# Swift/Objective-C compatibility
|
||||
s.pod_target_xcconfig = {
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
'SWIFT_COMPILATION_MODE' => 'wholemodule'
|
||||
}
|
||||
|
||||
s.source_files = "**/*.{h,m,swift}"
|
||||
end
|
||||
@@ -0,0 +1,211 @@
|
||||
import ExpoModulesCore
|
||||
|
||||
let ON_MIC_DATA_EVENT_NAME = "onMicrophoneData"
|
||||
let ON_INPUT_VOLUME_LEVEL_EVENT_NAME = "onInputVolumeLevelData"
|
||||
let ON_OUTPUT_VOLUME_LEVEL_EVENT_NAME = "onOutputVolumeLevelData"
|
||||
let ON_RECORDING_CHANGE_EVENT_NAME = "onRecordingChange"
|
||||
let ON_AUDIO_INTERRUPTION_EVENT_NAME = "onAudioInterruption"
|
||||
|
||||
public class ExpoTwoWayAudioModule: Module {
|
||||
private var audioEngine: AudioEngine?
|
||||
public func definition() -> ModuleDefinition {
|
||||
Name("ExpoTwoWayAudio")
|
||||
|
||||
OnCreate {
|
||||
let permissionsManager = self.appContext?.permissions
|
||||
EXPermissionsMethodsDelegate.register(
|
||||
[
|
||||
MicrophonePermissionRequester()
|
||||
],
|
||||
withPermissionsManager: permissionsManager
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
AsyncFunction("initialize") { () -> Bool in
|
||||
do {
|
||||
if self.audioEngine != nil {
|
||||
return true
|
||||
}
|
||||
self.audioEngine = try AudioEngine()
|
||||
self.setupMicrophoneCallback()
|
||||
self.setupInputAudioLevelCallback()
|
||||
self.setupOutputAudioLevelCallback()
|
||||
self.setupAudioInterruptionCallback()
|
||||
return true
|
||||
} catch {
|
||||
print("Failed to initialize AudioEngine: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Function("isRecording") { () -> Bool in
|
||||
guard let audioEngine = self.audioEngine else {
|
||||
print("AudioEngine not initialized")
|
||||
return false
|
||||
}
|
||||
return audioEngine.isRecording
|
||||
}
|
||||
|
||||
Function("toggleRecording") { (val: Bool) -> Bool in
|
||||
guard let audioEngine = self.audioEngine else {
|
||||
print("AudioEngine not initialized")
|
||||
return false
|
||||
}
|
||||
let isRecording = audioEngine.toggleRecording(val)
|
||||
self.sendEvent(
|
||||
ON_RECORDING_CHANGE_EVENT_NAME,
|
||||
[
|
||||
"data": isRecording
|
||||
])
|
||||
return isRecording
|
||||
}
|
||||
|
||||
Function("getMicrophoneModeIOS") { () -> String in
|
||||
if #available(iOS 15.0, *) {
|
||||
let mode = AVCaptureDevice.preferredMicrophoneMode.rawValue
|
||||
var micMode = ""
|
||||
|
||||
switch mode {
|
||||
case 1:
|
||||
micMode = MicrophoneMode.wideSpectrum.rawValue
|
||||
case 2:
|
||||
micMode = MicrophoneMode.voiceIsolation.rawValue
|
||||
default:
|
||||
micMode = MicrophoneMode.standard.rawValue
|
||||
}
|
||||
return micMode
|
||||
}
|
||||
print("Please update your ios")
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
Function("setMicrophoneModeIOS") {
|
||||
if #available(iOS 15, *) {
|
||||
if AVCaptureDevice.preferredMicrophoneMode != .voiceIsolation {
|
||||
AVCaptureDevice.showSystemUserInterface(.microphoneModes)
|
||||
}
|
||||
} else {
|
||||
print("This code only runs on iOS 14 and lower")
|
||||
}
|
||||
}
|
||||
|
||||
Function("tearDown") {
|
||||
self.audioEngine?.tearDown()
|
||||
self.audioEngine = nil
|
||||
}
|
||||
|
||||
Function("restart") {
|
||||
self.audioEngine?.resumeRecordingAndPlayer()
|
||||
self.sendEvent(
|
||||
ON_RECORDING_CHANGE_EVENT_NAME,
|
||||
[
|
||||
"data": audioEngine?.isRecording
|
||||
])
|
||||
|
||||
}
|
||||
|
||||
Function("playPCMData") { (pcmData: Data) in
|
||||
self.audioEngine?.playPCMData(pcmData)
|
||||
}
|
||||
|
||||
Function("bypassVoiceProcessing") { (bypass: Bool) in
|
||||
self.audioEngine?.bypassVoiceProcessing(bypass)
|
||||
}
|
||||
|
||||
Function("isPlaying") { () -> Bool in
|
||||
return self.audioEngine?.isPlaying ?? false
|
||||
}
|
||||
|
||||
Function("stopPlayback") {
|
||||
self.audioEngine?.stopPlayback()
|
||||
}
|
||||
|
||||
Function("pausePlayback") {
|
||||
self.audioEngine?.pausePlayback()
|
||||
}
|
||||
|
||||
Function("resumePlayback") {
|
||||
self.audioEngine?.resumePlayback()
|
||||
}
|
||||
|
||||
AsyncFunction("getMicrophonePermissionsAsync") { (promise: Promise) in
|
||||
EXPermissionsMethodsDelegate.getPermissionWithPermissionsManager(
|
||||
self.appContext?.permissions,
|
||||
withRequester: MicrophonePermissionRequester.self,
|
||||
resolve: promise.resolver,
|
||||
reject: promise.legacyRejecter
|
||||
)
|
||||
}
|
||||
|
||||
AsyncFunction("requestMicrophonePermissionsAsync") { (promise: Promise) in
|
||||
EXPermissionsMethodsDelegate.askForPermission(
|
||||
withPermissionsManager: self.appContext?.permissions,
|
||||
withRequester: MicrophonePermissionRequester.self,
|
||||
resolve: promise.resolver,
|
||||
reject: promise.legacyRejecter
|
||||
)
|
||||
}
|
||||
|
||||
// Define the events that can be emitted
|
||||
Events([
|
||||
ON_MIC_DATA_EVENT_NAME,
|
||||
ON_INPUT_VOLUME_LEVEL_EVENT_NAME,
|
||||
ON_OUTPUT_VOLUME_LEVEL_EVENT_NAME,
|
||||
ON_RECORDING_CHANGE_EVENT_NAME,
|
||||
ON_AUDIO_INTERRUPTION_EVENT_NAME,
|
||||
])
|
||||
}
|
||||
|
||||
private func setupMicrophoneCallback() {
|
||||
audioEngine?.onMicDataCallback = { [weak self] data in
|
||||
self?.sendEvent(
|
||||
ON_MIC_DATA_EVENT_NAME,
|
||||
[
|
||||
"data": data
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
private func setupInputAudioLevelCallback() {
|
||||
audioEngine?.onInputVolumeCallback = { [weak self] level in
|
||||
self?.sendEvent(
|
||||
ON_INPUT_VOLUME_LEVEL_EVENT_NAME,
|
||||
[
|
||||
"data": level
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
private func setupOutputAudioLevelCallback() {
|
||||
audioEngine?.onOutputVolumeCallback = { [weak self] level in
|
||||
self?.sendEvent(
|
||||
ON_OUTPUT_VOLUME_LEVEL_EVENT_NAME,
|
||||
[
|
||||
"data": level
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
private func setupAudioInterruptionCallback() {
|
||||
audioEngine?.onAudioInterruptionCallback = { [weak self] data in
|
||||
self?.sendEvent(
|
||||
ON_AUDIO_INTERRUPTION_EVENT_NAME,
|
||||
[
|
||||
"data": data
|
||||
])
|
||||
self?.sendEvent(
|
||||
ON_RECORDING_CHANGE_EVENT_NAME,
|
||||
[
|
||||
"data": self?.audioEngine?.isRecording
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
enum MicrophoneMode: String, Enumerable {
|
||||
case standard
|
||||
case voiceIsolation
|
||||
case wideSpectrum
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import ExpoModulesCore
|
||||
import AVFoundation
|
||||
|
||||
let microphoneKey = "NSMicrophoneUsageDescription"
|
||||
|
||||
class MicrophonePermissionRequester: NSObject, EXPermissionsRequester {
|
||||
let mediaType: AVMediaType = .audio
|
||||
|
||||
static func permissionType() -> String {
|
||||
"microphone"
|
||||
}
|
||||
|
||||
func getPermissions() -> [AnyHashable: Any] {
|
||||
let systemStatus = AVAudioSession.sharedInstance().recordPermission
|
||||
var status: EXPermissionStatus
|
||||
|
||||
guard (Bundle.main.infoDictionary?["NSMicrophoneUsageDescription"]) != nil else {
|
||||
EXFatal(EXErrorWithMessage("""
|
||||
This app is missing NSMicrophoneUsageDescription, so audio services will fail.
|
||||
Add one of these keys to your bundle's Info.plist.
|
||||
"""))
|
||||
return ["status": EXPermissionStatusDenied]
|
||||
}
|
||||
|
||||
switch systemStatus {
|
||||
case .granted:
|
||||
status = EXPermissionStatusGranted
|
||||
case .denied:
|
||||
status = EXPermissionStatusDenied
|
||||
case .undetermined:
|
||||
status = EXPermissionStatusUndetermined
|
||||
@unknown default:
|
||||
status = EXPermissionStatusUndetermined
|
||||
}
|
||||
|
||||
return [
|
||||
"status": status.rawValue
|
||||
]
|
||||
}
|
||||
|
||||
func requestPermissions(resolver resolve: @escaping EXPromiseResolveBlock, rejecter reject: EXPromiseRejectBlock) {
|
||||
if #available(iOS 17.0, *) {
|
||||
AVAudioApplication.requestRecordPermission { _ in
|
||||
resolve(self.getPermissions())
|
||||
}
|
||||
} else {
|
||||
AVAudioSession.sharedInstance().requestRecordPermission { _ in
|
||||
resolve(self.getPermissions())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@getpaseo/expo-two-way-audio",
|
||||
"version": "0.1.4",
|
||||
"description": "Native module for two way audio streaming",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "expo-module build",
|
||||
"clean": "expo-module clean",
|
||||
"lint": "expo-module lint",
|
||||
"test": "expo-module test",
|
||||
"format": "biome check .",
|
||||
"format:fix": "biome check --write .",
|
||||
"typecheck": "tsc",
|
||||
"expo-module": "expo-module",
|
||||
"open:ios": "xed examples/basic-usage/ios",
|
||||
"open:android": "open -a \"Android Studio\" examples/basic-usage/android"
|
||||
},
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"expo",
|
||||
"expo-two-way-audio",
|
||||
"ExpoTwoWayAudio"
|
||||
],
|
||||
"repository": "https://github.com/boudra/expo-two-way-audio",
|
||||
"bugs": {
|
||||
"url": "https://github.com/speechmatics/expo-two-way-audio/issues"
|
||||
},
|
||||
"author": "Speechmatics <devrel@speechmatics.com> (https://github.com/speechmatics)",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/boudra/expo-two-way-audio",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/react": "^18.0.25",
|
||||
"expo-module-scripts": "^3.5.2",
|
||||
"expo-modules-core": "^2.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "*",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { requireNativeModule } from "expo-modules-core";
|
||||
|
||||
// It loads the native module object from the JSI or falls back to
|
||||
// the bridge module (from NativeModulesProxy) if the remote debugger is on.
|
||||
export default requireNativeModule("ExpoTwoWayAudio");
|
||||
@@ -0,0 +1,62 @@
|
||||
import { type PermissionResponse, createPermissionHook } from "expo-modules-core";
|
||||
import ExpoTwoWayAudioModule from "./ExpoTwoWayAudioModule";
|
||||
|
||||
export async function initialize() {
|
||||
return await ExpoTwoWayAudioModule.initialize();
|
||||
}
|
||||
|
||||
export function playPCMData(audioData: Uint8Array) {
|
||||
return ExpoTwoWayAudioModule.playPCMData(audioData);
|
||||
}
|
||||
|
||||
export function bypassVoiceProcessing(bypass: boolean) {
|
||||
return ExpoTwoWayAudioModule.bypassVoiceProcessing(bypass);
|
||||
}
|
||||
|
||||
export function toggleRecording(val: boolean): boolean {
|
||||
return ExpoTwoWayAudioModule.toggleRecording(val);
|
||||
}
|
||||
|
||||
export function isRecording(): boolean {
|
||||
return ExpoTwoWayAudioModule.isRecording();
|
||||
}
|
||||
|
||||
export function tearDown() {
|
||||
return ExpoTwoWayAudioModule.tearDown();
|
||||
}
|
||||
|
||||
export function restart() {
|
||||
return ExpoTwoWayAudioModule.restart();
|
||||
}
|
||||
|
||||
export async function getMicrophonePermissionsAsync(): Promise<PermissionResponse> {
|
||||
return ExpoTwoWayAudioModule.getMicrophonePermissionsAsync();
|
||||
}
|
||||
|
||||
export async function requestMicrophonePermissionsAsync(): Promise<PermissionResponse> {
|
||||
return ExpoTwoWayAudioModule.requestMicrophonePermissionsAsync();
|
||||
}
|
||||
|
||||
export function getMicrophoneModeIOS() {
|
||||
return ExpoTwoWayAudioModule.getMicrophoneModeIOS();
|
||||
}
|
||||
|
||||
export function setMicrophoneModeIOS() {
|
||||
return ExpoTwoWayAudioModule.setMicrophoneModeIOS();
|
||||
}
|
||||
|
||||
export function isPlaying(): boolean {
|
||||
return ExpoTwoWayAudioModule.isPlaying();
|
||||
}
|
||||
|
||||
export function stopPlayback() {
|
||||
return ExpoTwoWayAudioModule.stopPlayback();
|
||||
}
|
||||
|
||||
export function pausePlayback() {
|
||||
return ExpoTwoWayAudioModule.pausePlayback();
|
||||
}
|
||||
|
||||
export function resumePlayback() {
|
||||
return ExpoTwoWayAudioModule.resumePlayback();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import ExpoTwoWayAudioModule from "./ExpoTwoWayAudioModule";
|
||||
|
||||
export type MicrophoneDataEvent = {
|
||||
data: Uint8Array;
|
||||
};
|
||||
|
||||
export type VolumeLevelEvent = {
|
||||
data: number;
|
||||
};
|
||||
|
||||
export type RecordingChangeEvent = {
|
||||
data: boolean;
|
||||
};
|
||||
|
||||
export type AudioInterruptionEvent = {
|
||||
data: string;
|
||||
};
|
||||
|
||||
export interface ExpoTwoWayAudioEventMap {
|
||||
onMicrophoneData: MicrophoneDataEvent;
|
||||
onInputVolumeLevelData: VolumeLevelEvent;
|
||||
onOutputVolumeLevelData: VolumeLevelEvent;
|
||||
onRecordingChange: RecordingChangeEvent;
|
||||
onAudioInterruption: AudioInterruptionEvent;
|
||||
}
|
||||
|
||||
// These are useful for defining `useCallback` types inline
|
||||
export type MicrophoneDataCallback = (event: MicrophoneDataEvent) => void;
|
||||
export type VolumeLevelCallback = (event: VolumeLevelEvent) => void;
|
||||
export type RecordingChangeCallback = (event: RecordingChangeEvent) => void;
|
||||
export type AudioInterruptionCallback = (event: AudioInterruptionEvent) => void;
|
||||
|
||||
export function addExpoTwoWayAudioEventListener<K extends keyof ExpoTwoWayAudioEventMap>(
|
||||
eventName: K,
|
||||
handler: (ev: ExpoTwoWayAudioEventMap[K]) => void,
|
||||
) {
|
||||
return ExpoTwoWayAudioModule.addListener(eventName, handler);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { createPermissionHook } from "expo-modules-core";
|
||||
import { useEffect, useSyncExternalStore } from "react";
|
||||
import {
|
||||
getMicrophonePermissionsAsync,
|
||||
isRecording,
|
||||
requestMicrophonePermissionsAsync,
|
||||
} from "./core";
|
||||
import { ExpoTwoWayAudioEventMap, addExpoTwoWayAudioEventListener } from "./events";
|
||||
|
||||
export const useMicrophonePermissions = createPermissionHook({
|
||||
getMethod: getMicrophonePermissionsAsync,
|
||||
requestMethod: requestMicrophonePermissionsAsync,
|
||||
});
|
||||
|
||||
export function useIsRecording() {
|
||||
const subscribe = (cb: () => void) => {
|
||||
const sub = addExpoTwoWayAudioEventListener("onRecordingChange", cb);
|
||||
return () => sub.remove();
|
||||
};
|
||||
const getSnapshot = () => isRecording();
|
||||
const getServerSnapshot = () => false;
|
||||
|
||||
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||
}
|
||||
|
||||
export function useExpoTwoWayAudioEventListener<K extends keyof ExpoTwoWayAudioEventMap>(
|
||||
eventName: K,
|
||||
listener: (ev: ExpoTwoWayAudioEventMap[K]) => void,
|
||||
) {
|
||||
useEffect(() => {
|
||||
const sub = addExpoTwoWayAudioEventListener(eventName, listener);
|
||||
return () => sub.remove();
|
||||
}, [listener]);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./core";
|
||||
export * from "./events";
|
||||
export * from "./hooks";
|
||||
@@ -0,0 +1,9 @@
|
||||
// @generated by expo-module-scripts
|
||||
{
|
||||
"extends": "expo-module-scripts/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["./src"],
|
||||
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
|
||||
}
|
||||