UNPKG

vlibras-player-webjs

Version:

Biblioteca JavaScript moderna para integração do VLibras Player com React, Vue, Angular e vanilla JS

604 lines 20.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VLibrasPlayer = void 0; const config_1 = require("../config/config"); const PlayerManagerAdapter_1 = require("./PlayerManagerAdapter"); const GlosaTranslator_1 = require("../unity/GlosaTranslator"); const core_types_1 = require("../../types/core.types"); const UnityBridge_1 = require("../unity/UnityBridge"); const VLibrasCSS_1 = require("../../infrastructure/styling/VLibrasCSS"); const VLibrasEvents_1 = require("../../infrastructure/events/VLibrasEvents"); const VLibrasDevTools_1 = require("../../features/devtools/VLibrasDevTools"); /** * VLibras Player - Classe principal para controle do avatar de Libras */ class VLibrasPlayer { constructor(options = {}) { this.loaded = false; this.status = core_types_1.PlayerStatus.IDLE; this.region = 'BR'; this.globalGlossLength = ''; // Detecta automaticamente o caminho dos assets const autoAssetsPath = (() => { if (typeof window !== 'undefined' && window.location && typeof window.location.pathname === 'string') { // Tenta detectar se está em ambiente de desenvolvimento ou produção if (window.location.pathname.includes('/demo') || window.location.pathname.includes('/dist')) { return '/assets/vlibras'; } // 2. Se rodando via node_modules (npm install) return '/node_modules/vlibras-player-webjs/assets/vlibras'; } // Ambiente não web ou teste return config_1.config.defaultTargetPath; })(); this.options = { ...options, // Valores padrão para compatibilidade fallbackUrl: options.fallbackUrl || config_1.config.translatorUrl, targetPath: options.targetPath || autoAssetsPath, }; this.playerManager = new PlayerManagerAdapter_1.PlayerManagerAdapter(); this.translator = new GlosaTranslator_1.GlosaTranslator(this.options.fallbackUrl || config_1.config.translatorUrl); this.eventEmitter = new VLibrasEvents_1.VLibrasEventEmitter(); this.setupPlayerManagerEvents(); } /** * Configura eventos do PlayerManager */ setupPlayerManagerEvents() { this.playerManager.on('load', () => { this.loaded = true; this.onLoad(); this.playerManager.setBaseUrl(config_1.config.dictionaryUrl + this.region + '/'); if (this.options.onReady) { this.options.onReady(); } else { this.play(undefined, { fromTranslation: true }); } }); this.playerManager.on('progress', (progress) => { this.onAnimationProgress(progress); }); this.playerManager.on('stateChange', (isPlaying, isPaused, isLoading) => { if (isPaused) { this.onAnimationPause(); } else if (isPlaying && !isPaused) { this.onAnimationPlay(); this.changeStatus(core_types_1.PlayerStatus.PLAYING); } else if (!isPlaying && !isLoading) { this.onAnimationEnd(); this.changeStatus(core_types_1.PlayerStatus.IDLE); } }); this.playerManager.on('CounterGloss', (counter, glossLength) => { this.onGlossResponse(counter, glossLength); this.globalGlossLength = glossLength; }); this.playerManager.on('GetAvatar', (avatar) => { this.onGetAvatar(avatar); }); this.playerManager.on('FinishWelcome', (finished) => { this.onStopWelcome(finished); }); } /** * Carrega o player no elemento wrapper especificado */ load(wrapper) { this.gameContainer = document.createElement('div'); this.gameContainer.setAttribute('id', config_1.config.unity.containerId); this.gameContainer.classList.add('emscripten'); wrapper.appendChild(this.gameContainer); this.initializeUnity(); } /** * Traduz texto para glosa e reproduz */ translate(text, options = {}) { const { isEnabledStats = true } = options; this.onTranslateStart(); this.eventEmitter.emit('translation:start', { text, timestamp: Date.now() }); if (this.loaded) { this.stop(); } this.text = text; const callback = (gloss, error) => { if (error) { this.eventEmitter.emit('translation:error', { error, text, timestamp: Date.now() }); if (error === 'timeout_error') { this.onError('timeout_error'); } else { this.onTranslateEnd(); return; } } if (gloss) { this.eventEmitter.emit('translation:complete', { gloss, duration: Date.now() - this.eventEmitter.lastTranslationStart || 0, timestamp: Date.now() }); this.play(gloss, { fromTranslation: true, isEnabledStats }); } this.onTranslateEnd(); }; // Guardar timestamp para calcular duração this.eventEmitter.lastTranslationStart = Date.now(); this.translator.translate(text, window.location.host, callback); } /** * Reproduz glosa */ play(gloss, options = {}) { const { isEnabledStats = true } = options; // Gerencia URLs de dicionário baseado em estatísticas if (!isEnabledStats && this.isDefaultUrl()) { this.playerManager.setBaseUrl(config_1.config.dictionaryStaticUrl + this.region + '/'); } else if (isEnabledStats && !this.isDefaultUrl()) { this.playerManager.setBaseUrl(config_1.config.dictionaryUrl + this.region + '/'); } this.gloss = gloss || this.gloss; if (this.gloss !== undefined && this.loaded) { this.changeStatus(core_types_1.PlayerStatus.INITIALIZING); // Guardar timestamp para calcular duração da animação this.eventEmitter.lastAnimationStart = Date.now(); this.eventEmitter.emit('animation:start', { gloss: this.gloss, timestamp: Date.now() }); this.playerManager.play(this.gloss); } } /** * Reproduz mensagem de boas-vindas */ playWelcome() { this.playerManager.playWellcome(); this.onStartWelcome(); } /** * Continua reprodução */ continue() { this.playerManager.play(); } /** * Repete reprodução atual */ repeat() { this.play(); } /** * Pausa reprodução */ pause() { this.playerManager.pause(); } /** * Para reprodução */ stop() { this.playerManager.stop(); } /** * Define velocidade de reprodução */ setSpeed(speed) { this.playerManager.setSpeed(speed); } /** * Define personalização do avatar */ setPersonalization(personalization) { this.playerManager.setPersonalization(personalization); } /** * Muda avatar */ changeAvatar(avatarName) { this.playerManager.changeAvatar(avatarName); } /** * Alterna legendas */ toggleSubtitle() { this.playerManager.toggleSubtitle(); } /** * Define região */ setRegion(region) { this.region = region; this.playerManager.setBaseUrl(config_1.config.dictionaryUrl + region + '/'); } // Getters públicos getStatus() { return this.status; } getText() { return this.text; } getGloss() { return this.gloss; } isLoaded() { return this.loaded; } getRegion() { return this.region; } /** * Obtém o event emitter do player */ getEventEmitter() { return this.eventEmitter; } // Métodos privados isDefaultUrl() { return this.playerManager.currentBaseUrl === config_1.config.dictionaryUrl + this.region + '/'; } getTargetScript() { return this.joinUrl(this.options.targetPath || config_1.config.defaultTargetPath, config_1.config.unity.loaderFile); } joinUrl(...parts) { return parts.join('/').replace(/\/+/g, '/').replace(/\/$/, ''); } initializeUnity() { const targetSetup = this.joinUrl(this.options.targetPath || config_1.config.defaultTargetPath, config_1.config.unity.configFile); const targetScript = document.createElement('script'); targetScript.src = this.getTargetScript(); targetScript.onload = () => { const unityLoader = window.UnityLoader; if (!unityLoader) { this.onError('unity_loader_not_found'); return; } this.player = unityLoader.instantiate(config_1.config.unity.containerId, targetSetup, { compatibilityCheck: (_, accept, deny) => { if (unityLoader.SystemInfo.hasWebGL) { return accept(); } this.onError('unsupported'); alert('Seu navegador não suporta WebGL'); console.error('Seu navegador não suporta WebGL'); deny(); } }); this.playerManager.setPlayerReference(this.player); }; targetScript.onerror = () => { this.onError('unity_script_load_failed'); }; document.body.appendChild(targetScript); } changeStatus(status) { switch (status) { case core_types_1.PlayerStatus.IDLE: if (this.status === core_types_1.PlayerStatus.PLAYING) { this.status = status; this.onGlossEnd(this.globalGlossLength); } break; case core_types_1.PlayerStatus.INITIALIZING: this.status = status; break; case core_types_1.PlayerStatus.PLAYING: if (this.status === core_types_1.PlayerStatus.INITIALIZING) { this.status = status; this.onGlossStart(); } break; } } // Event handlers - podem ser sobrescritos onLoad() { // Implementação padrão vazia - pode ser sobrescrita } onTranslateStart() { // Implementação padrão vazia - pode ser sobrescrita } onTranslateEnd() { // Implementação padrão vazia - pode ser sobrescrita } onGlossStart() { // Implementação padrão vazia - pode ser sobrescrita } onGlossEnd(_glossLength) { // Implementação padrão vazia - pode ser sobrescrita } onAnimationPlay() { this.eventEmitter.emit('animation:resume', { timestamp: Date.now() }); } onAnimationPause() { this.eventEmitter.emit('animation:pause', { timestamp: Date.now() }); } onAnimationEnd() { this.eventEmitter.emit('animation:complete', { duration: Date.now() - (this.eventEmitter.lastAnimationStart || Date.now()), totalFrames: 0, // TODO: Obter do Unity se possível timestamp: Date.now() }); } onAnimationProgress(progress) { this.eventEmitter.emit('animation:progress', { progress, currentFrame: Math.floor(progress * 10), // Estimativa timestamp: Date.now() }); } onGlossResponse(_counter, _glossLength) { // Implementação padrão vazia - pode ser sobrescrita } onGetAvatar(_avatar) { // Implementação padrão vazia - pode ser sobrescrita } onStartWelcome() { // Implementação padrão vazia - pode ser sobrescrita } onStopWelcome(_finished) { // Implementação padrão vazia - pode ser sobrescrita } onError(error) { console.error('VLibras Player Error:', error); this.eventEmitter.emit('player:error', { error: new Error(error), player: this, timestamp: Date.now() }); } // === NOVAS FUNCIONALIDADES v2.1.0 === /** * Adiciona listener para eventos do player */ on(event, listener) { return this.eventEmitter.on(event, listener); } /** * Remove listener de evento */ off(event, listener) { this.eventEmitter.off(event, listener); } /** * Configura Unity Bridge automaticamente */ setupUnityBridge() { (0, UnityBridge_1.setupUnityBridge)(); } /** * Aplica CSS otimizado automaticamente */ setupOptimizedCSS(containerSelector) { (0, VLibrasCSS_1.setupOptimizedCSS)(containerSelector); } /** * Executa diagnósticos do sistema */ async runDiagnostics() { return await VLibrasDevTools_1.VLibrasDevTools.runDiagnostics(); } /** * Ativa modo debug */ enableDebugMode() { VLibrasDevTools_1.VLibrasDevTools.enableDebugMode(); } /** * Desativa modo debug */ disableDebugMode() { VLibrasDevTools_1.VLibrasDevTools.disableDebugMode(); } /** * Verifica se está em modo debug */ isDebugMode() { return VLibrasDevTools_1.VLibrasDevTools.isDebugEnabled(); } /** * Obtém estatísticas do player */ getStats() { return { status: this.status, loaded: this.loaded, region: this.region, text: this.text, gloss: this.gloss }; } /** * Carrega o player de forma assíncrona */ async loadAsync(wrapper) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error('Timeout: Player não carregou em 30 segundos')); }, 30000); const cleanupAndResolve = () => { clearTimeout(timeoutId); resolve(); }; const cleanupAndReject = (error) => { clearTimeout(timeoutId); reject(error); }; // Escuta eventos de carregamento const removeListeners = this.on('player:ready', () => { removeListeners(); cleanupAndResolve(); }); this.on('player:error', (data) => { removeListeners(); cleanupAndReject(data.error); }); // Inicia carregamento try { this.load(wrapper); } catch (error) { cleanupAndReject(error instanceof Error ? error : new Error(String(error))); } }); } /** * Traduz texto de forma assíncrona */ async translateAsync(text, options = {}) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error('Timeout: Tradução não completou em 15 segundos')); }, 15000); const cleanupAndResolve = (gloss) => { clearTimeout(timeoutId); resolve(gloss); }; const cleanupAndReject = (error) => { clearTimeout(timeoutId); reject(error); }; // Escuta eventos de tradução const removeListeners = this.on('translation:complete', (data) => { removeListeners(); cleanupAndResolve(data.gloss); }); this.on('translation:error', (data) => { removeListeners(); cleanupAndReject(new Error(data.error)); }); // Inicia tradução try { this.translate(text, options); } catch (error) { cleanupAndReject(error instanceof Error ? error : new Error(String(error))); } }); } /** * Reproduz glosa de forma assíncrona */ async playAsync(gloss, options = {}) { return new Promise((resolve, reject) => { const startTime = Date.now(); const timeoutId = setTimeout(() => { reject(new Error('Timeout: Reprodução não completou em 60 segundos')); }, 60000); const cleanupAndResolve = (result) => { clearTimeout(timeoutId); resolve(result); }; const cleanupAndReject = (error) => { clearTimeout(timeoutId); reject(error); }; // Escuta eventos de animação const removeListeners = this.on('animation:complete', (data) => { removeListeners(); cleanupAndResolve({ duration: data.duration, totalFrames: data.totalFrames, success: true, startTime, endTime: Date.now() }); }); this.on('animation:error', (data) => { removeListeners(); cleanupAndReject(new Error(data.error)); }); // Inicia reprodução try { this.play(gloss, options); } catch (error) { cleanupAndReject(error instanceof Error ? error : new Error(String(error))); } }); } /** * Traduz e reproduz texto em uma única operação assíncrona */ async translateAndPlay(text, options = {}) { try { const gloss = await this.translateAsync(text, options); return await this.playAsync(gloss, options); } catch (error) { this.eventEmitter.emit('player:error', { error: error instanceof Error ? error : new Error(String(error)), player: this, timestamp: Date.now() }); throw error; } } /** * Aguarda até o player estar pronto */ async waitForReady() { if (this.loaded && this.status === core_types_1.PlayerStatus.READY) { return Promise.resolve(); } return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error('Timeout: Player não ficou pronto em 30 segundos')); }, 30000); const cleanup = () => { clearTimeout(timeoutId); }; const removeListeners = this.on('player:ready', () => { cleanup(); removeListeners(); resolve(); }); this.on('player:error', (data) => { cleanup(); removeListeners(); reject(data.error); }); }); } /** * Destrói o player e limpa recursos */ destroy() { // Para reprodução se estiver ativa if (this.status === core_types_1.PlayerStatus.PLAYING) { this.stop(); } // Remove elemento do Unity do DOM if (this.gameContainer && this.gameContainer.parentNode) { this.gameContainer.parentNode.removeChild(this.gameContainer); } // Limpa referências this.gameContainer = undefined; this.player = undefined; this.loaded = false; this.status = core_types_1.PlayerStatus.IDLE; // Emite evento de destruição this.eventEmitter.emit('player:destroy', { player: this, timestamp: Date.now() }); // Remove todos os listeners de eventos this.eventEmitter.removeAllListeners(); console.log('🗑️ VLibras Player destruído e recursos limpos'); } } exports.VLibrasPlayer = VLibrasPlayer; //# sourceMappingURL=VLibrasPlayer.js.map