Initial commit
This commit is contained in:
200
src/lib/accessibility/AccessibilityManager.ts
Normal file
200
src/lib/accessibility/AccessibilityManager.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { EventEmitter } from '$lib/utils/EventEmitter';
|
||||
|
||||
export interface SpeechOptions {
|
||||
pitch?: number;
|
||||
rate?: number;
|
||||
volume?: number;
|
||||
voice?: SpeechSynthesisVoice;
|
||||
}
|
||||
|
||||
export class AccessibilityManager extends EventEmitter {
|
||||
private isSpeechEnabled: boolean = false;
|
||||
private speechSynthesis: SpeechSynthesis | null = null;
|
||||
private speechOptions: SpeechOptions = {
|
||||
pitch: 1,
|
||||
rate: 1,
|
||||
volume: 0.8
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
console.log('AccessibilityManager constructed');
|
||||
|
||||
// Simple initialization - no speech synthesis by default
|
||||
if (typeof window !== 'undefined' && window.speechSynthesis) {
|
||||
this.speechSynthesis = window.speechSynthesis;
|
||||
console.log('Speech synthesis is available');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable speech
|
||||
*/
|
||||
public setSpeechEnabled(enabled: boolean): void {
|
||||
console.log('Setting speech enabled:', enabled);
|
||||
this.isSpeechEnabled = enabled;
|
||||
|
||||
// Cancel speech if disabling
|
||||
if (!enabled && this.speechSynthesis) {
|
||||
try {
|
||||
this.speechSynthesis.cancel();
|
||||
} catch (error) {
|
||||
console.error('Error cancelling speech:', error);
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('speechEnabledChanged', enabled);
|
||||
|
||||
// Test speech synthesis if enabling was successful
|
||||
if (this.isSpeechEnabled) {
|
||||
try {
|
||||
console.log('Text-to-speech is now enabled');
|
||||
} catch (error) {
|
||||
console.error('Error testing speech:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update speech options
|
||||
*/
|
||||
public updateSpeechOptions(options: SpeechOptions): void {
|
||||
console.log('Speech options updated:', options);
|
||||
|
||||
// Update stored options
|
||||
if (options.rate !== undefined) this.speechOptions.rate = options.rate;
|
||||
if (options.pitch !== undefined) this.speechOptions.pitch = options.pitch;
|
||||
if (options.volume !== undefined) this.speechOptions.volume = options.volume;
|
||||
if (options.voice !== undefined) this.speechOptions.voice = options.voice;
|
||||
|
||||
console.log('New speech options:', this.speechOptions);
|
||||
|
||||
this.emit('speechOptionsChanged', this.speechOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Speak text using text-to-speech
|
||||
*/
|
||||
public speak(text: string): void {
|
||||
// Skip if speech is disabled
|
||||
if (!this.isSpeechEnabled || !this.speechSynthesis) {
|
||||
// console.log('Speech is disabled, not speaking');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Clean and truncate text to prevent issues with large blocks
|
||||
const cleanText = this.cleanTextForSpeech(text);
|
||||
|
||||
// Only speak if there's meaningful text after cleaning
|
||||
if (cleanText && cleanText.trim().length > 0) {
|
||||
console.log('Speaking text with options:', {
|
||||
rate: this.speechOptions.rate,
|
||||
pitch: this.speechOptions.pitch,
|
||||
volume: this.speechOptions.volume
|
||||
});
|
||||
|
||||
const utterance = new SpeechSynthesisUtterance(cleanText);
|
||||
|
||||
// Explicitly set options
|
||||
utterance.rate = Number(this.speechOptions.rate) || 1;
|
||||
utterance.pitch = Number(this.speechOptions.pitch) || 1;
|
||||
utterance.volume = Number(this.speechOptions.volume) || 0.8;
|
||||
|
||||
console.log('Created utterance with:', {
|
||||
rate: utterance.rate,
|
||||
pitch: utterance.pitch,
|
||||
volume: utterance.volume
|
||||
});
|
||||
|
||||
// Add event handlers for debugging
|
||||
utterance.onstart = () => console.log('Speech started');
|
||||
utterance.onend = () => console.log('Speech ended');
|
||||
utterance.onerror = (e) => console.error('Speech error:', e);
|
||||
|
||||
// Apply voice if set
|
||||
if (this.speechOptions.voice) {
|
||||
utterance.voice = this.speechOptions.voice;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Speak the text
|
||||
this.speechSynthesis.speak(utterance);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in speak:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean text for speech synthesis
|
||||
*/
|
||||
private cleanTextForSpeech(text: string): string {
|
||||
if (!text) return '';
|
||||
|
||||
try {
|
||||
// Limit text length to prevent freezes with very long text
|
||||
let cleanText = text.length > 200 ? text.substring(0, 200) : text;
|
||||
|
||||
// Remove ANSI color codes
|
||||
cleanText = cleanText.replace(/\u001b\[\d+(;\d+)*m/g, '');
|
||||
|
||||
// Remove HTML tags
|
||||
cleanText = cleanText.replace(/<[^>]*>/g, '');
|
||||
|
||||
return cleanText.trim();
|
||||
} catch (error) {
|
||||
console.error('Error cleaning text for speech:', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop current speech
|
||||
*/
|
||||
public stopSpeech(): void {
|
||||
if (!this.speechSynthesis) return;
|
||||
|
||||
try {
|
||||
// Make sure we cancel any pending or active speech
|
||||
this.speechSynthesis.cancel();
|
||||
|
||||
console.log('Speech stopped');
|
||||
this.emit('speechStopped');
|
||||
|
||||
// Force a small delay to ensure the speech engine has time to process the cancel
|
||||
setTimeout(() => {
|
||||
if (this.speechSynthesis && this.speechSynthesis.speaking) {
|
||||
console.log('Force stopping speech again after delay');
|
||||
this.speechSynthesis.cancel();
|
||||
}
|
||||
}, 50);
|
||||
} catch (error) {
|
||||
console.error('Error stopping speech:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if speech is currently speaking
|
||||
*/
|
||||
public isSpeaking(): boolean {
|
||||
// Ensure the speech synthesis is available
|
||||
if (!this.speechSynthesis) return false;
|
||||
|
||||
// Check if speech is currently active
|
||||
try {
|
||||
return this.speechSynthesis.speaking || this.speechSynthesis.pending;
|
||||
} catch (error) {
|
||||
console.error('Error checking if speech is speaking:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if speech is currently enabled
|
||||
*/
|
||||
public isSpeechActive(): boolean {
|
||||
return this.isSpeechEnabled;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user