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
JavaScript
"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