expo-edge-speech
Version:
Text-to-speech library for Expo using Microsoft Edge TTS service
206 lines (205 loc) • 8.25 kB
JavaScript
;
/**
* Common utility functions shared across the application.
* Consolidated from duplicated implementations to maintain DRY principle.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateConnectionId = generateConnectionId;
exports.generateTimestamp = generateTimestamp;
exports.clampValue = clampValue;
exports.generateSessionId = generateSessionId;
exports.validateText = validateText;
exports.validateSpeechParameters = validateSpeechParameters;
exports.createSpeechError = createSpeechError;
const Crypto = __importStar(require("expo-crypto"));
const constants_1 = require("../constants");
/**
* Generate unique connection ID using UUID v4 without dashes (32 character string)
* Consolidated from duplicated implementations in connectionManager.ts, networkService.ts, and state.ts
*/
function generateConnectionId() {
return Crypto.randomUUID().replace(/-/g, "").toLowerCase();
}
/**
* Generate RFC 3339 timestamp with microseconds
* Used for message timestamps in Edge TTS protocol
*/
function generateTimestamp() {
const now = new Date();
const isoString = now.toISOString();
// Add microseconds (always 000 since JavaScript doesn't have microsecond precision)
return isoString.replace("Z", "000Z");
}
/**
* Clamp a numeric value between min and max bounds
* @param value - The value to clamp
* @param min - Minimum allowed value
* @param max - Maximum allowed value
* @returns Clamped value
*/
function clampValue(value, min, max) {
return Math.max(min, Math.min(max, value));
}
/**
* Generate a unique session ID
* @returns A unique session identifier
*/
function generateSessionId() {
return Crypto.randomUUID();
}
/**
* Validate text input for speech synthesis
* @param text - The text to validate
* @returns Validation result
*/
function validateText(text) {
const errors = [];
if (!text || typeof text !== "string") {
errors.push("Text must be a non-empty string");
}
else if (text.length > constants_1.MAX_TEXT_LENGTH) {
errors.push(`Text length (${text.length}) exceeds maximum allowed length (${constants_1.MAX_TEXT_LENGTH})`);
}
return {
isValid: errors.length === 0,
errors,
warnings: [],
};
}
/**
* Validate and normalize speech parameters
* Consolidates validation logic from Speech.ts and ssmlUtils.ts
* @param options - Speech options to validate
* @param clampValues - Whether to clamp values to valid ranges (default: true)
* @returns Validation result and normalized options
*/
function validateSpeechParameters(options, clampValues = true) {
const errors = [];
const warnings = [];
const normalizedOptions = { ...options };
// Apply default values
if (normalizedOptions.voice === undefined) {
normalizedOptions.voice = constants_1.DEFAULT_VOICE;
}
if (normalizedOptions.rate === undefined) {
normalizedOptions.rate = constants_1.PARAMETER_RANGES.rate.default;
}
if (normalizedOptions.pitch === undefined) {
normalizedOptions.pitch = constants_1.PARAMETER_RANGES.pitch.default;
}
if (normalizedOptions.volume === undefined) {
normalizedOptions.volume = constants_1.PARAMETER_RANGES.volume.default;
}
// Validate voice parameter (optional, but if provided, must be a non-empty string)
if (options.voice !== undefined &&
(typeof options.voice !== "string" || options.voice.trim() === "")) {
errors.push("Voice option, if provided, must be a non-empty string.");
}
// Validate and clamp rate parameter
if (options.rate !== undefined) {
if (typeof options.rate !== "number" || isNaN(options.rate)) {
errors.push("Rate must be a valid number");
}
else if (options.rate < constants_1.PARAMETER_RANGES.rate.min || // Use imported PARAMETER_RANGES
options.rate > constants_1.PARAMETER_RANGES.rate.max) {
if (clampValues) {
normalizedOptions.rate = clampValue(options.rate, constants_1.PARAMETER_RANGES.rate.min, constants_1.PARAMETER_RANGES.rate.max);
warnings.push(`Rate ${options.rate} clamped to ${normalizedOptions.rate}`);
}
else {
errors.push(`Rate ${options.rate} is outside valid range ${constants_1.PARAMETER_RANGES.rate.min}-${constants_1.PARAMETER_RANGES.rate.max}`);
}
}
}
// Validate and clamp pitch parameter
if (options.pitch !== undefined) {
if (typeof options.pitch !== "number" || isNaN(options.pitch)) {
errors.push("Pitch must be a valid number");
}
else if (options.pitch < constants_1.PARAMETER_RANGES.pitch.min || // Use imported PARAMETER_RANGES
options.pitch > constants_1.PARAMETER_RANGES.pitch.max) {
if (clampValues) {
normalizedOptions.pitch = clampValue(options.pitch, constants_1.PARAMETER_RANGES.pitch.min, constants_1.PARAMETER_RANGES.pitch.max);
warnings.push(`Pitch ${options.pitch} clamped to ${normalizedOptions.pitch}`);
}
else {
errors.push(`Pitch ${options.pitch} is outside valid range ${constants_1.PARAMETER_RANGES.pitch.min}-${constants_1.PARAMETER_RANGES.pitch.max}`);
}
}
}
// Validate and clamp volume parameter
if (options.volume !== undefined) {
if (typeof options.volume !== "number" || isNaN(options.volume)) {
errors.push("Volume must be a valid number");
}
else if (options.volume < constants_1.PARAMETER_RANGES.volume.min || // Use imported PARAMETER_RANGES
options.volume > constants_1.PARAMETER_RANGES.volume.max) {
if (clampValues) {
normalizedOptions.volume = clampValue(options.volume, constants_1.PARAMETER_RANGES.volume.min, constants_1.PARAMETER_RANGES.volume.max);
warnings.push(`Volume ${options.volume} clamped to ${normalizedOptions.volume}`);
}
else {
errors.push(`Volume ${options.volume} is outside valid range ${constants_1.PARAMETER_RANGES.volume.min}-${constants_1.PARAMETER_RANGES.volume.max}`);
}
}
}
// Validate language parameter
if (options.language !== undefined && typeof options.language !== "string") {
errors.push("Language must be a string");
}
return {
result: {
isValid: errors.length === 0,
errors,
warnings,
},
normalizedOptions,
};
}
/**
* Create a standardized SpeechError
* @param name - Error name
* @param message - Error message
* @param code - Error code (optional)
* @returns SpeechError object
*/
function createSpeechError(name, message, code) {
return {
name,
message,
code,
};
}