react-native-deepgram
Version:
React Native SDK for Deepgram's AI-powered speech-to-text, real-time transcription, and text intelligence APIs. Supports live audio streaming, file transcription, sentiment analysis, and topic detection for iOS and Android.
314 lines (313 loc) • 11 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useDeepgramSpeechToText = useDeepgramSpeechToText;
var _react = require("react");
var _reactNative = require("react-native");
var _NativeDeepgram = require("./NativeDeepgram.js");
var _askMicPermission = require("./helpers/askMicPermission.js");
var _index = require("./constants/index.js");
var _index2 = require("./helpers/index.js");
function useDeepgramSpeechToText({
onBeforeStart = () => {},
onStart = () => {},
onTranscript = () => {},
onError = () => {},
onEnd = () => {},
onBeforeTranscribe = () => {},
onTranscribeSuccess = () => {},
onTranscribeError = () => {},
live = {},
prerecorded = {}
} = {}) {
const ws = (0, _react.useRef)(null);
const audioSub = (0, _react.useRef)(null);
const apiVersionRef = (0, _react.useRef)('v1');
const closeEverything = () => {
audioSub.current?.remove();
_NativeDeepgram.Deepgram.stopRecording().catch(() => {});
if (apiVersionRef.current === 'v2' && ws.current?.readyState === WebSocket.OPEN) {
try {
ws.current.send(JSON.stringify({
type: 'CloseStream'
}));
} catch {
// ignore close errors
}
}
ws.current?.close(1000, 'cleanup');
ws.current = null;
apiVersionRef.current = 'v1';
};
const startListening = (0, _react.useCallback)(async (overrideOptions = {}) => {
try {
onBeforeStart();
const granted = await (0, _askMicPermission.askMicPermission)();
if (!granted) throw new Error('Microphone permission denied');
await _NativeDeepgram.Deepgram.startRecording();
const apiKey = globalThis.__DEEPGRAM_API_KEY__;
if (!apiKey) throw new Error('Deepgram API key missing');
const merged = {
encoding: 'linear16',
sampleRate: 16000,
model: 'nova-2',
apiVersion: 'v1',
...live,
...overrideOptions
};
if (merged.apiVersion === 'v2' && !merged.model) {
merged.model = 'flux-general-en';
}
const isV2 = merged.apiVersion === 'v2';
apiVersionRef.current = isV2 ? 'v2' : 'v1';
const query = {
callback: merged.callback,
callback_method: merged.callbackMethod,
channels: merged.channels,
diarize: merged.diarize,
dictation: merged.dictation,
encoding: merged.encoding,
endpointing: merged.endpointing,
filler_words: merged.fillerWords,
interim_results: merged.interimResults,
keyterm: merged.keyterm,
keywords: merged.keywords,
language: merged.language,
mip_opt_out: merged.mipOptOut,
model: merged.model,
multichannel: merged.multichannel,
numerals: merged.numerals,
profanity_filter: merged.profanityFilter,
punctuate: merged.punctuate,
replace: merged.replace,
sample_rate: merged.sampleRate,
search: merged.search,
smart_format: merged.smartFormat,
tag: merged.tag,
utterance_end_ms: merged.utteranceEndMs,
vad_events: merged.vadEvents,
version: merged.version
};
if (isV2) {
query.eager_eot_threshold = merged.eagerEotThreshold;
query.eot_threshold = merged.eotThreshold;
query.eot_timeout_ms = merged.eotTimeoutMs;
}
if (merged.redact) {
query.redact = Array.isArray(merged.redact) ? merged.redact : [merged.redact];
}
if (merged.extra) {
Object.entries(merged.extra).forEach(([key, value]) => {
query[`extra.${key}`] = value;
});
}
const params = (0, _index2.buildParams)(query);
const baseWss = isV2 ? _index.DEEPGRAM_V2_BASEWSS : _index.DEEPGRAM_BASEWSS;
const baseListenUrl = `${baseWss}/listen`;
const url = params ? `${baseListenUrl}?${params}` : baseListenUrl;
ws.current = new WebSocket(url, undefined, {
headers: {
Authorization: `Token ${apiKey}`
}
});
ws.current.onopen = () => onStart();
const emitter = new _reactNative.NativeEventEmitter(_reactNative.NativeModules.Deepgram);
audioSub.current = emitter.addListener(_reactNative.Platform.select({
ios: 'DeepgramAudioPCM',
android: 'AudioChunk'
}), ev => {
let chunk;
if (typeof ev?.b64 === 'string') {
const floatBytes = Uint8Array.from(atob(ev.b64), c => c.charCodeAt(0));
const float32 = new Float32Array(floatBytes.buffer);
const downsampled = float32.filter((_, i) => i % 3 === 0);
const int16 = new Int16Array(downsampled.length);
for (let i = 0; i < downsampled.length; i++) {
const s = Math.max(-1, Math.min(1, downsampled[i]));
int16[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
}
chunk = int16.buffer;
} else if (Array.isArray(ev?.data)) {
const bytes = new Uint8Array(ev.data.length);
for (let i = 0; i < ev.data.length; i++) {
const v = ev.data[i];
bytes[i] = v < 0 ? v + 256 : v;
}
const view = new DataView(bytes.buffer);
const int16 = new Int16Array(bytes.length / 2);
for (let i = 0; i < int16.length; i++) {
int16[i] = view.getInt16(i * 2, true);
}
chunk = int16.buffer;
}
if (chunk && ws.current?.readyState === WebSocket.OPEN) {
ws.current.send(chunk);
}
});
ws.current.onmessage = ev => {
if (typeof ev.data === 'string') {
try {
const msg = JSON.parse(ev.data);
if (isV2) {
if (msg.type === 'Error') {
const description = msg.description || 'Deepgram stream error';
onError(new Error(description));
closeEverything();
return;
}
const transcript = msg.transcript;
if (typeof transcript === 'string' && transcript.length > 0) {
onTranscript(transcript);
}
return;
}
const transcript = msg.channel?.alternatives?.[0]?.transcript;
if (transcript) onTranscript(transcript);
} catch {
// non-JSON or unexpected format
}
}
};
ws.current.onerror = onError;
ws.current.onclose = () => {
onEnd();
closeEverything();
};
} catch (err) {
onError(err);
closeEverything();
}
}, [onBeforeStart, onStart, onTranscript, onError, onEnd, live]);
const stopListening = (0, _react.useCallback)(() => {
try {
closeEverything();
onEnd();
} catch (err) {
onError(err);
}
}, [onEnd, onError]);
const transcribeFile = (0, _react.useCallback)(async (file, overrideOptions = {}) => {
onBeforeTranscribe();
try {
const apiKey = globalThis.__DEEPGRAM_API_KEY__;
if (!apiKey) throw new Error('Deepgram API key missing');
const merged = {
...prerecorded,
...overrideOptions
};
const query = {
callback: merged.callback,
callback_method: merged.callbackMethod,
sentiment: merged.sentiment,
summarize: merged.summarize,
tag: merged.tag,
topics: merged.topics,
custom_topic_mode: merged.customTopicMode,
intents: merged.intents,
custom_intent_mode: merged.customIntentMode,
detect_entities: merged.detectEntities,
diarize: merged.diarize,
dictation: merged.dictation,
encoding: merged.encoding,
filler_words: merged.fillerWords,
keyterm: merged.keyterm,
keywords: merged.keywords,
language: merged.language,
measurements: merged.measurements,
model: merged.model,
multichannel: merged.multichannel,
numerals: merged.numerals,
paragraphs: merged.paragraphs,
profanity_filter: merged.profanityFilter,
punctuate: merged.punctuate,
replace: merged.replace,
search: merged.search,
smart_format: merged.smartFormat,
utterances: merged.utterances,
utt_split: merged.uttSplit,
version: merged.version
};
if (merged.customTopic) {
query.custom_topic = merged.customTopic;
}
if (merged.customIntent) {
query.custom_intent = merged.customIntent;
}
if (merged.detectLanguage !== undefined) {
if (typeof merged.detectLanguage === 'boolean') {
query.detect_language = merged.detectLanguage;
} else {
query.detect_language = merged.detectLanguage;
}
}
if (merged.redact) {
query.redact = Array.isArray(merged.redact) ? merged.redact : [merged.redact];
}
if (merged.extra) {
if (typeof merged.extra === 'string' || Array.isArray(merged.extra)) {
query.extra = merged.extra;
} else {
Object.entries(merged.extra).forEach(([key, value]) => {
if (value == null) return;
query[`extra.${key}`] = value;
});
}
}
const params = (0, _index2.buildParams)(query);
const baseUrl = `${_index.DEEPGRAM_BASEURL}/listen`;
const url = params ? `${baseUrl}?${params}` : baseUrl;
const headers = {
Authorization: `Token ${apiKey}`
};
let body;
if (typeof file === 'string') {
headers['Content-Type'] = 'application/json';
body = JSON.stringify({
url: file
});
} else if (typeof file === 'object' && file !== null && 'url' in file) {
headers['Content-Type'] = 'application/json';
body = JSON.stringify({
url: file.url
});
} else {
const formData = new FormData();
if (file instanceof Blob) {
formData.append('audio', file, 'recording.wav');
} else {
formData.append('audio', {
uri: file.uri,
name: file.name || 'recording.wav',
type: file.type || 'audio/wav'
});
}
body = formData;
}
const res = await fetch(url, {
method: 'POST',
headers,
body
});
if (!res.ok) {
const errBody = await res.text();
throw new Error(`HTTP ${res.status}: ${errBody}`);
}
const json = await res.json();
const transcript = json.results?.channels?.[0]?.alternatives?.[0]?.transcript;
if (transcript) {
onTranscribeSuccess(transcript);
} else {
throw new Error('No transcript present in Deepgram response');
}
} catch (err) {
onTranscribeError(err);
}
}, [onBeforeTranscribe, onTranscribeSuccess, onTranscribeError, prerecorded]);
return {
startListening,
stopListening,
transcribeFile
};
}
//# sourceMappingURL=useDeepgramSpeechToText.js.map