393 lines
12 KiB
JavaScript
393 lines
12 KiB
JavaScript
/**
|
|
* Animation Queue Module
|
|
* Handles scheduling and executing animations with proper resource management
|
|
* and synchronization with TTS
|
|
*/
|
|
import { BaseModule } from './base-module.js';
|
|
import { moduleRegistry } from './module-registry.js';
|
|
|
|
class AnimationQueueModule extends BaseModule {
|
|
constructor() {
|
|
super('animation-queue', 'Animation Queue');
|
|
|
|
// Module dependencies
|
|
this.dependencies = [];
|
|
|
|
// Queue of scheduled animations/functions
|
|
this.timeoutQueue = [];
|
|
|
|
// Animation timing properties - use parent's config system
|
|
this.updateConfig({
|
|
speed: 0.05, // Base animation speed (seconds per character)
|
|
fastForwardEnabled: false
|
|
});
|
|
|
|
this.delay = 0; // Current accumulated delay
|
|
this.tts = null; // TTS module reference
|
|
|
|
// Bind methods using parent's bindMethods utility
|
|
this.bindMethods([
|
|
'schedule',
|
|
'fastForward',
|
|
'clearAll',
|
|
'setSpeed',
|
|
'beginFastForward',
|
|
'endFastForward',
|
|
'emitAnimationComplete',
|
|
'cleanupStaleTasks',
|
|
'isAnyTtsSpeaking'
|
|
]);
|
|
}
|
|
|
|
async initialize() {
|
|
try {
|
|
this.reportProgress(20, "Initializing Animation Queue");
|
|
|
|
// We'll try to get the TTS module, but it's not a hard dependency
|
|
// We'll check for it again at runtime when needed
|
|
setTimeout(() => {
|
|
// Try to get TTS module after a delay to allow it to initialize
|
|
this.tts = this.getModule('tts-player');
|
|
if (!this.tts) {
|
|
console.log("Animation Queue: TTS Player module not found yet, will try again when needed");
|
|
}
|
|
}, 500);
|
|
|
|
this.reportProgress(100, "Animation Queue ready");
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Error initializing Animation Queue:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schedule a function to execute after a delay, with optional TTS synchronization
|
|
* @param {Function} func - Function to execute
|
|
* @param {number} delay - Delay in milliseconds
|
|
* @param {Object} options - Optional parameters including TTS text
|
|
* @returns {number} - Timeout ID for cancellation
|
|
*/
|
|
schedule(func, delay, options = {}) {
|
|
if (typeof func !== 'function') {
|
|
console.error('AnimationQueue: Invalid function provided to schedule');
|
|
return -1;
|
|
}
|
|
|
|
// Adjust delay based on fast-forward or speed settings
|
|
const actualDelay = this.config.fastForwardEnabled ? 0 : Math.max(0, delay * this.config.speed);
|
|
|
|
// Record the delay for tracking
|
|
this.delay = Math.max(this.delay, delay);
|
|
|
|
// If we don't have a reference to the TTS module yet, try to get it
|
|
if (!this.tts) {
|
|
this.tts = this.getModule('tts-player');
|
|
}
|
|
|
|
// Handle TTS if text is provided and TTS is available and enabled
|
|
let ttsSpeaking = false;
|
|
if (options.text && this.tts && typeof this.tts.isEnabled === 'function' && this.tts.isEnabled()) {
|
|
// If we're fast forwarding, don't speak
|
|
if (!this.config.fastForwardEnabled) {
|
|
ttsSpeaking = true;
|
|
// Request TTS to speak the text
|
|
this.tts.speak(options.text, (result) => {
|
|
ttsSpeaking = false;
|
|
|
|
// Check if this was keeping the queue busy
|
|
if (this.timeoutQueue.length === 0) {
|
|
this.emitAnimationComplete();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create a timeout object
|
|
const timeoutObject = {
|
|
func: func,
|
|
delay: actualDelay,
|
|
timeoutId: null,
|
|
executed: false,
|
|
startTime: Date.now(),
|
|
ttsSpeaking: ttsSpeaking,
|
|
|
|
// Add an execute method that marks the timeout as executed
|
|
execute: function() {
|
|
if (!this.executed) {
|
|
this.func();
|
|
this.executed = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Add to queue
|
|
this.timeoutQueue.push(timeoutObject);
|
|
|
|
// If we're fast forwarding, execute immediately
|
|
if (this.config.fastForwardEnabled) {
|
|
timeoutObject.execute();
|
|
return -1; // No timeout ID since it executed immediately
|
|
}
|
|
|
|
// Schedule the timeout
|
|
timeoutObject.timeoutId = setTimeout(() => {
|
|
// Execute the function
|
|
timeoutObject.execute();
|
|
|
|
// Remove from queue
|
|
const index = this.timeoutQueue.indexOf(timeoutObject);
|
|
if (index !== -1) {
|
|
this.timeoutQueue.splice(index, 1);
|
|
}
|
|
|
|
// If queue is empty and no TTS is speaking, emit animation complete
|
|
if (this.timeoutQueue.length === 0 && !this.isAnyTtsSpeaking()) {
|
|
this.emitAnimationComplete();
|
|
}
|
|
|
|
}, actualDelay);
|
|
|
|
return timeoutObject.timeoutId;
|
|
}
|
|
|
|
/**
|
|
* Emit an animation complete event
|
|
*/
|
|
emitAnimationComplete() {
|
|
// Only emit if queue is empty and no TTS is speaking
|
|
if (this.timeoutQueue.length === 0 && !this.isAnyTtsSpeaking()) {
|
|
// Use parent's dispatchEvent method
|
|
this.dispatchEvent('ui:animation:complete', {
|
|
timestamp: Date.now()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean up any animation tasks that might have been missed
|
|
* (e.g. due to browser tab being inactive)
|
|
*/
|
|
cleanupStaleTasks() {
|
|
const now = Date.now();
|
|
const staleTasks = [];
|
|
|
|
// Find stale tasks
|
|
this.timeoutQueue.forEach(task => {
|
|
// If task has been running for more than 10 seconds, consider it stale
|
|
if (now - task.startTime > 10000 && !task.executed) {
|
|
staleTasks.push(task);
|
|
}
|
|
});
|
|
|
|
// Execute and remove stale tasks
|
|
staleTasks.forEach(task => {
|
|
console.log('AnimationQueue: Cleaning up stale task');
|
|
|
|
// Clear the timeout
|
|
if (task.timeoutId !== null) {
|
|
clearTimeout(task.timeoutId);
|
|
}
|
|
|
|
// Execute the task
|
|
task.execute();
|
|
|
|
// Remove from queue
|
|
const index = this.timeoutQueue.indexOf(task);
|
|
if (index !== -1) {
|
|
this.timeoutQueue.splice(index, 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if any TTS is currently speaking
|
|
* @returns {boolean} - True if TTS is speaking
|
|
*/
|
|
isAnyTtsSpeaking() {
|
|
// If we don't have a reference to the TTS module yet, try to get it
|
|
if (!this.tts) {
|
|
this.tts = this.getModule('tts-player');
|
|
}
|
|
|
|
// Check if TTS is speaking
|
|
if (this.tts && typeof this.tts.isSpeaking === 'function') {
|
|
return this.tts.isSpeaking();
|
|
}
|
|
|
|
// Default to false if TTS module is not available
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Fast forward all pending animations and stop TTS
|
|
*/
|
|
fastForward() {
|
|
if (this.timeoutQueue.length === 0) {
|
|
console.log('AnimationQueue: No animations to fast forward');
|
|
return;
|
|
}
|
|
|
|
console.log(`AnimationQueue: Fast forwarding ${this.timeoutQueue.length} pending items`);
|
|
|
|
// If we don't have a reference to the TTS module yet, try to get it
|
|
if (!this.tts) {
|
|
this.tts = this.getModule('tts-player');
|
|
}
|
|
|
|
// Stop any active TTS
|
|
if (this.tts && typeof this.tts.stop === 'function') {
|
|
this.tts.stop();
|
|
}
|
|
|
|
// Execute all pending animations immediately
|
|
this.timeoutQueue.forEach(timeout => {
|
|
// Clear the timeout
|
|
if (timeout.timeoutId !== null) {
|
|
clearTimeout(timeout.timeoutId);
|
|
timeout.timeoutId = null;
|
|
}
|
|
|
|
// Clear TTS flag
|
|
timeout.ttsSpeaking = false;
|
|
|
|
// Execute the function immediately
|
|
timeout.execute();
|
|
});
|
|
|
|
// Clear the queue
|
|
this.timeoutQueue = [];
|
|
|
|
// Reset delay
|
|
this.delay = 0;
|
|
|
|
// Update config using parent's updateConfig method
|
|
this.updateConfig({ fastForwardEnabled: false });
|
|
|
|
// Emit animation complete event
|
|
this.emitAnimationComplete();
|
|
|
|
// Log the fastforward completion
|
|
console.log('AnimationQueue: Fast forward complete');
|
|
|
|
// Use parent's dispatchEvent method
|
|
this.dispatchEvent('ui:animation:fastforward', { state: false });
|
|
}
|
|
|
|
/**
|
|
* Begin fast forwarding mode
|
|
*/
|
|
beginFastForward() {
|
|
if (this.config.fastForwardEnabled) return;
|
|
|
|
// Update config using parent's updateConfig method
|
|
this.updateConfig({ fastForwardEnabled: true });
|
|
|
|
// If we don't have a reference to the TTS module yet, try to get it
|
|
if (!this.tts) {
|
|
this.tts = this.getModule('tts-player');
|
|
}
|
|
|
|
// Stop any active TTS
|
|
if (this.tts && typeof this.tts.stop === 'function') {
|
|
this.tts.stop();
|
|
}
|
|
|
|
// Use parent's dispatchEvent method
|
|
this.dispatchEvent('ui:animation:fastforward', { state: true });
|
|
|
|
console.log('AnimationQueue: Fast forward mode activated');
|
|
}
|
|
|
|
/**
|
|
* End fast forwarding mode
|
|
*/
|
|
endFastForward() {
|
|
if (!this.config.fastForwardEnabled) return;
|
|
|
|
// Update config using parent's updateConfig method
|
|
this.updateConfig({ fastForwardEnabled: false });
|
|
|
|
// Use parent's dispatchEvent method
|
|
this.dispatchEvent('ui:animation:fastforward', { state: false });
|
|
|
|
console.log('AnimationQueue: Fast forward mode deactivated');
|
|
}
|
|
|
|
/**
|
|
* Clear all scheduled animations without executing them
|
|
*/
|
|
clearAll() {
|
|
console.log(`Animation Queue: Clearing ${this.timeoutQueue.length} pending items`);
|
|
|
|
// Clear all timeouts
|
|
this.timeoutQueue.forEach(timeoutObject => {
|
|
if (timeoutObject.timeoutId !== null) {
|
|
clearTimeout(timeoutObject.timeoutId);
|
|
}
|
|
});
|
|
|
|
// Clear queue
|
|
this.timeoutQueue = [];
|
|
|
|
// Reset delay
|
|
this.delay = 0;
|
|
}
|
|
|
|
/**
|
|
* Set the animation speed
|
|
* @param {number} speed - Animation speed factor (lower is faster)
|
|
*/
|
|
setSpeed(speed) {
|
|
if (typeof speed !== 'number' || speed <= 0) {
|
|
console.error('Animation Queue: Invalid speed value');
|
|
return;
|
|
}
|
|
|
|
// Update config using parent's updateConfig method
|
|
this.updateConfig({ speed });
|
|
console.log(`Animation Queue: Speed set to ${speed}`);
|
|
}
|
|
|
|
/**
|
|
* Get the current animation speed
|
|
* @returns {number} - Current animation speed factor
|
|
*/
|
|
getSpeed() {
|
|
return this.config.speed;
|
|
}
|
|
|
|
/**
|
|
* Check if fast forwarding is active
|
|
* @returns {boolean} - Whether fast forwarding is active
|
|
*/
|
|
isFastForwarding() {
|
|
return this.config.fastForwardEnabled;
|
|
}
|
|
|
|
/**
|
|
* Get current queue length
|
|
* @returns {number} - Number of items in the queue
|
|
*/
|
|
getQueueLength() {
|
|
return this.timeoutQueue.length;
|
|
}
|
|
|
|
/**
|
|
* Get current accumulated delay
|
|
* @returns {number} - Current delay in milliseconds
|
|
*/
|
|
getCurrentDelay() {
|
|
return this.delay;
|
|
}
|
|
}
|
|
|
|
// Create the singleton instance
|
|
const AnimationQueue = new AnimationQueueModule();
|
|
|
|
// Register with the module registry
|
|
moduleRegistry.register(AnimationQueue);
|
|
|
|
// Export the module
|
|
export { AnimationQueue };
|