UNPKG

vlibras-player-nextjs

Version:

VLibras Player para Next.js - Biblioteca moderna para tradução de texto em Libras com callbacks de estado

1,645 lines 57.9 kB
import { useReducer, useRef, useCallback, useMemo, useEffect } from 'react';
import axios from 'axios';

/**
 * Configuração padrão do VLibras
 */
const defaultConfig = {
    translatorUrl: 'https://traducao2-dth.vlibras.gov.br/dl/translate',
    dictionaryUrl: 'https://dicionario2-dth.vlibras.gov.br/2018.3.1/WEBGL/',
    dictionaryStaticUrl: 'https://dicionario2-dth.vlibras.gov.br/static/BUNDLES/2018.3.1/WEBGL/',
};
/**
 * Status do player
 */
const PLAYER_STATUSES = {
    idle: 'idle',
    preparing: 'preparing',
    playing: 'playing',
};
/**
 * Configurações padrão do player
 */
const DEFAULT_PLAYER_OPTIONS = {
    translatorUrl: defaultConfig.translatorUrl,
    targetPath: '/vlibras/target',
    region: 'BR',
    enableStats: true,
    autoInit: true,
};
/**
 * Nomes dos objetos Unity
 */
const UNITY_GAME_OBJECTS = {
    playerManager: 'PlayerManager',
    customizationBridge: 'CustomizationBridge',
};
/**
 * Timeouts padrão
 */
const TIMEOUTS = {
    translation: 10000, // 10 segundos base
    translationPerWord: 400, // 400ms por palavra adicional
    maxTranslationTimeout: 60000, // 60 segundos máximo
};

class VLibrasTranslator {
    constructor(endpoint) {
        this.endpoint = endpoint;
    }
    /**
     * Traduz um texto para glosa (linguagem intermediária para Libras)
     */
    async translate(text, domain = '') {
        if (!text || text.trim().length === 0) {
            throw new Error('Texto não pode estar vazio');
        }
        const timeout = this.calculateTimeout(text);
        try {
            const response = await axios.post(this.endpoint, {
                text: text.trim(),
                domain: domain || window?.location?.hostname || 'localhost',
            }, {
                timeout,
                headers: {
                    'Content-Type': 'application/json',
                },
            });
            if (!response.data) {
                throw new Error('Resposta vazia do servidor de tradução');
            }
            return response.data;
        }
        catch (error) {
            if (error?.code === 'ECONNABORTED') {
                throw new Error('timeout_error');
            }
            if (error?.response?.status === 400) {
                throw new Error('Texto inválido para tradução');
            }
            if (error?.response?.status >= 500) {
                throw new Error('Erro interno do servidor de tradução');
            }
            throw error instanceof Error ? error : new Error('Erro desconhecido na tradução');
        }
    }
    /**
     * Calcula o timeout baseado no tamanho do texto
     */
    calculateTimeout(text) {
        const wordCount = text.split(/\s+/).length;
        let timeout = TIMEOUTS.translation;
        // Adiciona tempo extra para textos maiores
        if (wordCount > 50) {
            timeout += Math.floor(wordCount * TIMEOUTS.translationPerWord / 10);
        }
        // Garante que não ultrapasse o máximo
        return Math.min(timeout, TIMEOUTS.maxTranslationTimeout);
    }
    /**
     * Valida se o endpoint está acessível
     */
    async validateEndpoint() {
        try {
            await axios.get(this.endpoint, { timeout: 5000 });
            return true;
        }
        catch {
            return false;
        }
    }
}

/**
 * Adapter para gerenciar a comunicação com o Unity WebGL Player
 */
class UnityPlayerManager {
    constructor() {
        this.player = null;
        this.eventListeners = new Map();
        this.subtitle = true;
        this.currentBaseUrl = '';
        if (UnityPlayerManager.instance) {
            return UnityPlayerManager.instance;
        }
        this.setupGlobalCallbacks();
        UnityPlayerManager.instance = this;
    }
    /**
     * Define a referência do player Unity
     */
    setPlayerReference(player) {
        this.player = player;
    }
    /**
     * Envia uma mensagem para o Unity
     */
    sendMessage(method, params) {
        if (!this.player) {
            // Unity player not initialized - silently return
            return;
        }
        try {
            this.player.SendMessage(UNITY_GAME_OBJECTS.playerManager, method, params);
        }
        catch (error) {
            // Silently handle Unity communication errors
        }
    }
    /**
     * Reproduz uma glosa ou continua a reprodução
     */
    play(gloss) {
        if (gloss) {
            this.sendMessage('playNow', gloss);
        }
        else {
            this.sendMessage('setPauseState', 0);
        }
    }
    /**
     * Pausa a reprodução
     */
    pause() {
        this.sendMessage('setPauseState', 1);
    }
    /**
     * ✅ NOVO: Retoma a reprodução pausada
     */
    resume() {
        this.sendMessage('setPauseState', 0);
    }
    /**
     * ✅ NOVO: Reinicia a animação atual (mesmo se estiver rodando)
     */
    restart() {
        // 🔥 CORREÇÃO: Unity não tem comando restart nativo, então fazemos stop + play da glosa atual
        this.sendMessage('stopAll');
        // O restart será implementado na lógica do VLibrasPlayer
    }
    /**
     * Para toda a reprodução
     */
    stop() {
        this.sendMessage('stopAll');
    }
    /**
     * Define a velocidade de reprodução
     */
    setSpeed(speed) {
        if (speed < 0.5 || speed > 2.0) {
            // Speed should be between 0.5 and 2.0 - silently return
            return;
        }
        this.sendMessage('setSlider', speed);
    }
    /**
     * Alterna a exibição de legendas
     */
    toggleSubtitle() {
        this.subtitle = !this.subtitle;
        this.sendMessage('setSubtitlesState', this.subtitle ? 1 : 0);
    }
    /**
     * Reproduz animação de boas-vindas
     */
    playWelcome() {
        this.sendMessage('playWellcome');
    }
    /**
     * Troca o avatar
     */
    changeAvatar(avatarName) {
        this.sendMessage('Change', avatarName);
    }
    /**
     * Define a URL base para o dicionário
     */
    setBaseUrl(url) {
        this.sendMessage('setBaseUrl', url);
        this.currentBaseUrl = url;
    }
    /**
     * ✅ CRITICAL FIX: Inicializa animações aleatórias (método do original)
     */
    initRandomAnimationsProcess() {
        this.sendMessage('initRandomAnimationsProcess');
    }
    /**
     * Define configurações de personalização
     */
    setPersonalization(personalization) {
        if (!this.player) {
            // Unity player not initialized - silently return
            return;
        }
        try {
            this.player.SendMessage(UNITY_GAME_OBJECTS.customizationBridge, 'setURL', personalization);
        }
        catch (error) {
            // Silently handle personalization errors
        }
    }
    /**
     * Adiciona listener para eventos
     */
    addEventListener(event, listener) {
        if (!this.eventListeners.has(event)) {
            this.eventListeners.set(event, new Set());
        }
        this.eventListeners.get(event).add(listener);
    }
    /**
     * Remove listener de eventos
     */
    removeEventListener(event, listener) {
        const listeners = this.eventListeners.get(event);
        if (listeners) {
            listeners.delete(listener);
        }
    }
    /**
     * Emite um evento
     */
    emit(event, ...args) {
        const listeners = this.eventListeners.get(event);
        if (listeners) {
            listeners.forEach(listener => {
                try {
                    listener(...args);
                }
                catch (error) {
                    // Silently handle event listener errors
                }
            });
        }
    }
    /**
     * Configura callbacks globais para comunicação com Unity
     */
    setupGlobalCallbacks() {
        if (typeof window === 'undefined')
            return;
        // Callback para quando o player é carregado
        window.onLoadPlayer = () => {
            this.sendMessage('initRandomAnimationsProcess');
            this.emit('load');
        };
        // Callback para progresso
        window.updateProgress = (progress) => {
            this.emit('progress', progress);
        };
        // Callback para mudança de estado
        window.onPlayingStateChange = (isPlaying, isPaused, isPlayingIntervalAnimation, isLoading, _isRepeatable) => {
            this.emit('stateChange', this.toBool(isPlaying), this.toBool(isPaused), this.toBool(isLoading));
        };
        // Callback para contador de glosa
        window.CounterGloss = (counter, length) => {
            this.emit('counterGloss', counter, length);
        };
        // Callback para obter avatar
        window.GetAvatar = (avatar) => {
            this.emit('getAvatar', avatar);
        };
        // Callback para fim da apresentação
        window.FinishWelcome = (finished) => {
            this.emit('finishWelcome', this.toBool(finished));
        };
    }
    /**
     * Converte string Unity para boolean
     */
    toBool(value) {
        return value !== 'False';
    }
    /**
     * Obtém URL base atual
     */
    getCurrentBaseUrl() {
        return this.currentBaseUrl;
    }
    /**
     * Verifica se as legendas estão habilitadas
     */
    isSubtitleEnabled() {
        return this.subtitle;
    }
    /**
     * Limpa todos os listeners
     */
    dispose() {
        this.eventListeners.clear();
        this.player = null;
        if (typeof window !== 'undefined') {
            delete window.onLoadPlayer;
            delete window.updateProgress;
            delete window.onPlayingStateChange;
            delete window.CounterGloss;
            delete window.GetAvatar;
            delete window.FinishWelcome;
        }
    }
}
UnityPlayerManager.instance = null;

/**
 * Gerencia o carregamento do Unity WebGL Player
 */
class UnityLoader {
    /**
     * Carrega e inicializa o Unity WebGL Player
     */
    static async loadPlayer(config) {
        const { targetPath, gameContainer, onSuccess, onError, onProgress } = config;
        try {
            // Carrega o script do Unity Loader se ainda não foi carregado
            await this.loadUnityScript(targetPath);
            // Aguarda o UnityLoader estar disponível
            await this.waitForUnityLoader();
            // Configura o container
            this.setupGameContainer(gameContainer);
            // Carrega a configuração do jogo
            const configUrl = this.joinUrl(targetPath, 'playerweb.json');
            // Inicializa o Unity
            const UnityLoaderGlobal = window.UnityLoader;
            const player = UnityLoaderGlobal.instantiate(gameContainer.id, configUrl, {
                compatibilityCheck: (_, accept, deny) => {
                    if (UnityLoaderGlobal.SystemInfo.hasWebGL) {
                        accept();
                    }
                    else {
                        const errorMsg = 'Seu navegador não suporta WebGL';
                        onError(errorMsg);
                        deny();
                    }
                },
                onProgress: onProgress ? (unityInstance, progress) => {
                    onProgress(progress);
                } : undefined,
            });
            onSuccess(player);
        }
        catch (error) {
            const errorMsg = error instanceof Error ? error.message : 'Erro ao carregar Unity Player';
            onError(errorMsg);
        }
    }
    /**
     * Carrega o script do Unity Loader
     */
    static async loadUnityScript(targetPath) {
        const scriptUrl = this.joinUrl(targetPath, 'UnityLoader.js');
        // Verifica se já foi carregado
        if (this.loadedScripts.has(scriptUrl)) {
            return;
        }
        return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = scriptUrl;
            script.async = true;
            script.onload = () => {
                this.loadedScripts.add(scriptUrl);
                resolve();
            };
            script.onerror = () => {
                reject(new Error(`Falha ao carregar Unity script: ${scriptUrl}`));
            };
            document.head.appendChild(script);
        });
    }
    /**
     * Aguarda o UnityLoader estar disponível no window
     */
    static async waitForUnityLoader(timeout = 10000) {
        const startTime = Date.now();
        return new Promise((resolve, reject) => {
            const check = () => {
                if (window.UnityLoader) {
                    resolve();
                    return;
                }
                if (Date.now() - startTime > timeout) {
                    reject(new Error('Timeout aguardando UnityLoader'));
                    return;
                }
                setTimeout(check, 100);
            };
            check();
        });
    }
    /**
     * ✅ MELHORADO: Configura o container do jogo com ID estável
     */
    static setupGameContainer(container) {
        // ✅ CORREÇÃO: ID mais estável baseado em região ou ID padrão
        if (!container.id) {
            container.id = 'vlibras-game-container-main';
        }
        // ✅ Verificar se já tem as classes necessárias para evitar duplicação
        if (!container.classList.contains('emscripten')) {
            container.classList.add('emscripten', 'vlibras-unity-container');
        }
        // Define estilos básicos se necessário
        if (!container.style.position) {
            container.style.position = 'relative';
        }
    }
    /**
     * Junta URLs de forma segura
     */
    static joinUrl(base, path) {
        const normalizedBase = base.endsWith('/') ? base.slice(0, -1) : base;
        const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
        return `${normalizedBase}/${normalizedPath}`;
    }
    /**
     * Verifica se o navegador suporta WebGL
     */
    static checkWebGLSupport() {
        try {
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
            return !!context;
        }
        catch {
            return false;
        }
    }
    /**
     * Remove todos os scripts Unity carregados
     */
    static cleanup() {
        this.loadedScripts.forEach(scriptUrl => {
            const scripts = document.querySelectorAll(`script[src="${scriptUrl}"]`);
            scripts.forEach(script => script.remove());
        });
        this.loadedScripts.clear();
        // Remove variáveis globais do Unity
        if (typeof window !== 'undefined') {
            delete window.UnityLoader;
            delete window.Module;
        }
    }
}
UnityLoader.loadedScripts = new Set();

class UnityStateManager {
    /**
     * Verifica se o Unity WebGL carregou completamente
     */
    static isUnityReady(container) {
        try {
            const canvas = container.querySelector('canvas');
            const unityInstance = this.getUnityInstance(container);
            // ✅ Verificação mais robusta
            const hasValidCanvas = canvas &&
                canvas.clientWidth > 0 &&
                canvas.clientHeight > 0 &&
                typeof canvas.getContext === 'function' &&
                !canvas.style.display?.includes('none');
            const hasValidUnity = unityInstance &&
                unityInstance.Module &&
                unityInstance.Module.ready === true &&
                unityInstance.SendMessage &&
                typeof unityInstance.SendMessage === 'function';
            // ✅ Verificação adicional: tentar enviar mensagem de teste
            let canSendMessage = false;
            if (hasValidUnity && unityInstance?.SendMessage) {
                try {
                    // Teste silencioso - não vai gerar erro se Unity estiver pronto
                    unityInstance.SendMessage('NonExistentObject', 'NonExistentMethod', '');
                    canSendMessage = true;
                }
                catch (error) {
                    // Se der erro específico de objeto não encontrado, Unity está ok
                    const errorStr = error instanceof Error ? error.message : String(error);
                    canSendMessage = errorStr.includes('object') || errorStr.includes('method') || errorStr.includes('not found');
                }
            }
            return !!(hasValidCanvas && hasValidUnity && canSendMessage);
        }
        catch (error) {
            // Silently handle errors - avoid console warnings in production
            return false;
        }
    }
    /**
     * Verifica se uma animação está sendo reproduzida
     */
    static isAnimationPlaying(container) {
        try {
            const unityInstance = this.getUnityInstance(container);
            if (unityInstance?.Module?.isAnimationPlaying) {
                return unityInstance.Module.isAnimationPlaying();
            }
            // Fallback: verificar se há canvas ativo
            const canvas = container?.querySelector('canvas') || document.querySelector('canvas');
            return canvas ? canvas.style.display !== 'none' : false;
        }
        catch (error) {
            // Silently handle errors
            return false;
        }
    }
    /**
     * Verifica se uma animação foi completada
     */
    static isAnimationComplete(container) {
        try {
            const unityInstance = this.getUnityInstance(container);
            if (unityInstance?.Module?.isAnimationComplete) {
                return unityInstance.Module.isAnimationComplete();
            }
            // Se não tem método específico, assume que não está tocando = completo
            return !this.isAnimationPlaying(container);
        }
        catch (error) {
            // In case of error, assume complete to avoid blocking
            return true;
        }
    }
    /**
     * Aguarda o Unity estar completamente carregado
     */
    static waitForUnity(container, timeout = 30000) {
        return new Promise((resolve, reject) => {
            const startTime = Date.now();
            const check = () => {
                if (this.isUnityReady(container)) {
                    resolve();
                    return;
                }
                if (Date.now() - startTime > timeout) {
                    reject(new Error(`Unity não carregou em ${timeout}ms`));
                    return;
                }
                setTimeout(check, 100);
            };
            // Delay inicial para permitir que Unity comece a carregar
            setTimeout(check, 500);
        });
    }
    /**
     * Aguarda uma animação terminar completamente
     */
    static waitForAnimationEnd(container, timeout = 10000) {
        return new Promise((resolve, _reject) => {
            const startTime = Date.now();
            const check = () => {
                if (this.isAnimationComplete(container)) {
                    resolve();
                    return;
                }
                if (Date.now() - startTime > timeout) {
                    // Resolve even with timeout to avoid blocking the application
                    resolve();
                    return;
                }
                setTimeout(check, 100);
            };
            // Delay inicial para permitir que animação comece
            setTimeout(check, 200);
        });
    }
    /**
     * Obtém a instância do Unity para um container específico
     */
    static getUnityInstance(container) {
        if (container) {
            // Tentar encontrar por ID do container
            const containerId = container.id;
            if (containerId && window.unityInstances?.has(containerId)) {
                return window.unityInstances.get(containerId);
            }
        }
        // Fallback para instância global
        return window.unityInstance;
    }
    /**
     * Registra uma instância do Unity para um container específico
     */
    static registerUnityInstance(containerId, instance) {
        if (!window.unityInstances) {
            window.unityInstances = new Map();
        }
        window.unityInstances.set(containerId, instance);
        // Também manter referência global para compatibilidade
        window.unityInstance = instance;
    }
    /**
     * Remove registro de uma instância do Unity
     */
    static unregisterUnityInstance(containerId) {
        if (window.unityInstances) {
            window.unityInstances.delete(containerId);
        }
    }
    /**
     * Limpa todas as instâncias registradas
     */
    static clearAllInstances() {
        if (window.unityInstances) {
            window.unityInstances.clear();
        }
        window.unityInstance = undefined;
    }
    /**
     * Obtém informações de debug do estado atual
     */
    static getDebugInfo(container) {
        const unityInstance = this.getUnityInstance(container);
        const canvas = container?.querySelector('canvas');
        return {
            hasUnityInstance: !!unityInstance,
            hasModule: !!unityInstance?.Module,
            moduleReady: !!unityInstance?.Module?.ready,
            hasSendMessage: !!unityInstance?.SendMessage,
            hasCanvas: !!canvas,
            canvasSize: canvas ? `${canvas.clientWidth}x${canvas.clientHeight}` : 'N/A',
            isReady: container ? this.isUnityReady(container) : false,
            isAnimationPlaying: this.isAnimationPlaying(container),
            isAnimationComplete: this.isAnimationComplete(container),
            registeredInstances: window.unityInstances?.size || 0,
        };
    }
}

/**
 * Classe principal do VLibras Player para Next.js
 */
class VLibrasPlayer {
    constructor(options = {}) {
        this.eventListeners = new Map();
        this.container = null;
        this.globalGlossLength = 0;
        this.isInTranslation = false; // 🔥 Flag para rastrear se estamos em uma tradução
        this.isRestarting = false; // 🔥 NOVO: Flag para rastrear se estamos reiniciando
        this.options = {
            ...DEFAULT_PLAYER_OPTIONS,
            ...options,
        };
        // Extrair callbacks das opções
        this.callbacks = {
            onTranslationStart: options.onTranslationStart,
            onTranslationEnd: options.onTranslationEnd,
            onTranslationError: options.onTranslationError,
            onPlay: options.onPlay,
            onPause: options.onPause,
            onResume: options.onResume,
            onRestart: options.onRestart,
            onStop: options.onStop,
            onPlayerReady: options.onPlayerReady,
            onPlayerError: options.onPlayerError,
        };
        this.translator = new VLibrasTranslator(this.options.translatorUrl);
        this.unityManager = new UnityPlayerManager();
        this.state = {
            status: PLAYER_STATUSES.idle,
            loaded: false,
            translated: false,
            progress: null,
            region: this.options.region,
            isTranslating: false,
            isPlaying: false,
        };
        this.setupEventListeners();
        this.setupCallbackIntegration();
    }
    /**
     * Carrega o player no container especificado
     */
    async load(wrapper) {
        if (typeof window === 'undefined') {
            throw new Error('VLibras Player só pode ser usado no lado cliente');
        }
        if (!UnityLoader.checkWebGLSupport()) {
            throw new Error('Navegador não suporta WebGL');
        }
        // ✅ CORREÇÃO ORIGINAL: Implementar sequência exata do VLibras original
        // Limpar containers anteriores se existirem
        const oldContainers = wrapper.querySelectorAll('[id*="vlibras-container"], [id*="gameContainer"]');
        oldContainers.forEach(container => {
            const containerId = container.id;
            UnityStateManager.unregisterUnityInstance(containerId);
            container.remove();
        });
        // ✅ CRITICAL FIX: Criar gameContainer com ID específico que Unity espera
        this.container = document.createElement('div');
        this.container.setAttribute('id', 'gameContainer'); // ✅ Unity original usa gameContainer
        this.container.classList.add('emscripten', 'vlibras-unity-container');
        wrapper.appendChild(this.container);
        return new Promise((resolve, reject) => {
            // ✅ CRITICAL FIX: Implementar window.onLoadPlayer como no original
            const originalOnLoadPlayer = window.onLoadPlayer;
            window.onLoadPlayer = () => {
                try {
                    // ✅ ORIGINAL SEQUENCE: Exatamente como no Player.js original
                    this.state.loaded = true;
                    this.emit('load');
                    // ✅ CRITICAL FIX: Inicializar animações aleatórias (estava faltando!)
                    this.unityManager.initRandomAnimationsProcess();
                    // ✅ ORIGINAL FIX: setBaseUrl SEM região como no original
                    this.unityManager.setBaseUrl(defaultConfig.dictionaryUrl);
                    // ✅ ORIGINAL: Chamar onLoad callback ou play automático
                    if (this.options.onLoad) {
                        this.options.onLoad();
                    }
                    else {
                        // ✅ ORIGINAL: Play automático de boas-vindas com null
                        this.play(null, { fromTranslation: true });
                    }
                    this.callbacks.onPlayerReady?.();
                    resolve();
                }
                catch (error) {
                    const errorMessage = error instanceof Error ? error.message : 'Erro na inicialização do player';
                    this.emit('error', errorMessage);
                    this.callbacks.onPlayerError?.(errorMessage);
                    reject(new Error(errorMessage));
                }
                finally {
                    // Restaurar callback original se existia
                    if (originalOnLoadPlayer) {
                        window.onLoadPlayer = originalOnLoadPlayer;
                    }
                }
            };
            // ✅ Usar UnityLoader original
            UnityLoader.loadPlayer({
                targetPath: this.options.targetPath,
                gameContainer: this.container,
                onSuccess: (player) => {
                    // ✅ CRITICAL FIX: Apenas configurar referência, NÃO marcar como pronto
                    this.unityManager.setPlayerReference(player);
                    UnityStateManager.registerUnityInstance('gameContainer', player);
                    // ✅ O window.onLoadPlayer será chamado pelo Unity quando estiver realmente pronto
                },
                onError: (error) => {
                    // Restaurar callback original se existia
                    if (originalOnLoadPlayer) {
                        window.onLoadPlayer = originalOnLoadPlayer;
                    }
                    this.emit('error', error);
                    this.callbacks.onPlayerError?.(error);
                    reject(new Error(error));
                },
                onProgress: (progress) => {
                    this.state.progress = progress;
                    this.emit('animation:progress', progress);
                },
            });
        });
    }
    /**
     * Traduz um texto para Libras - EXATAMENTE como no código original
     */
    async translate(text, options = {}) {
        if (!text || text.trim().length === 0) {
            throw new Error('Texto não pode estar vazio');
        }
        if (!this.state.loaded) {
            throw new Error('Player não está pronto. Aguarde o carregamento completo.');
        }
        // ✅ ORIGINAL: Emitir translate:start imediatamente
        this.emit('translate:start');
        this.callbacks.onTranslationStart?.();
        this.isInTranslation = true; // 🔥 Marcar que estamos em uma tradução
        // ✅ ORIGINAL: Parar reprodução atual se estiver carregado
        if (this.state.loaded) {
            this.stop();
        }
        this.state.text = text;
        try {
            const domain = typeof window !== 'undefined' ? window.location.hostname : '';
            const gloss = await this.translator.translate(text, domain);
            if (!gloss) {
                // ✅ ORIGINAL: Em caso de erro, finalizar tradução
                this.isInTranslation = false; // 🔥 Finalizar flag de tradução
                this.emit('translate:end');
                this.callbacks.onTranslationEnd?.();
                return;
            }
            this.state.gloss = gloss;
            // ✅ CRITICAL FIX: Seguir EXATAMENTE o código original
            this.play(gloss, { ...options, fromTranslation: true, isEnabledStats: options.isEnabledStats });
            // 🔥 REMOVED: Não emitir translate:end aqui!
            // Agora será emitido quando animation:end for disparado
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : 'Erro na tradução';
            this.callbacks.onTranslationError?.(errorMessage);
            if (errorMessage === 'timeout_error') {
                this.emit('error', 'timeout_error');
            }
            else {
                this.emit('error', errorMessage);
            }
            // ✅ ORIGINAL: Em caso de erro, reproduzir texto em maiúsculas
            this.play(text.toUpperCase());
            this.emit('translate:end');
        }
    }
    /**
     * Reproduz uma glosa ou continua a reprodução
     */
    play(gloss, options = {}) {
        const { fromTranslation = false, isEnabledStats = true } = options;
        // Configura URL do dicionário baseado nas estatísticas
        this.updateDictionaryUrl(isEnabledStats);
        this.state.translated = fromTranslation;
        this.state.gloss = gloss || this.state.gloss;
        if (this.state.gloss && this.state.loaded) {
            this.changeStatus(PLAYER_STATUSES.preparing);
            this.unityManager.play(this.state.gloss);
        }
    }
    /**
     * Reproduz animação de boas-vindas
     */
    playWelcome() {
        this.unityManager.playWelcome();
        this.emit('welcome:start');
    }
    /**
     * Continua a reprodução pausada
     */
    continue() {
        this.unityManager.play();
    }
    /**
     * Repete a última reprodução
     */
    repeat() {
        this.play();
    }
    /**
     * Pausa a reprodução
     */
    pause() {
        this.unityManager.pause();
        // 🔥 CORREÇÃO: Não chamar callback aqui - será chamado pelo evento animation:pause
    }
    /**
     * ✅ NOVO: Retoma a reprodução pausada
     */
    resume() {
        this.unityManager.resume();
        // 🔥 CORREÇÃO: Não chamar callback aqui - será chamado pelo evento animation:play
    }
    /**
     * ✅ NOVO: Reinicia a animação atual (mesmo se estiver rodando)
     */
    restart() {
        if (!this.state.loaded || !this.state.gloss) {
            // Não há glosa para reiniciar
            return;
        }
        // 🔥 CORREÇÃO: Marcar que estamos reiniciando
        this.isRestarting = true;
        // Emitir apenas o evento - o callback será chamado pelo listener
        this.emit('animation:restart');
        // Parar e reproduzir novamente a glosa atual
        this.unityManager.stop();
        // Pequeno delay para garantir que o stop foi processado
        setTimeout(() => {
            if (this.state.gloss && this.isRestarting) {
                this.unityManager.play(this.state.gloss);
                this.isRestarting = false; // Reset flag após iniciar
            }
        }, 100);
    }
    /**
     * Para a reprodução
     */
    stop() {
        this.unityManager.stop();
        // 🔥 CORREÇÃO: Não chamar callback aqui - será chamado pelo evento animation:end
        // 🔥 Resetar flag de tradução se necessário
        if (this.isInTranslation) {
            this.isInTranslation = false;
            // Note: Não emitir translate:end aqui pois stop() pode ser chamado manualmente
        }
    }
    /**
     * Define a velocidade de reprodução
     */
    setSpeed(speed) {
        this.unityManager.setSpeed(speed);
    }
    /**
     * Define configurações de personalização
     */
    setPersonalization(personalization) {
        this.unityManager.setPersonalization(personalization);
    }
    /**
     * Troca o avatar
     */
    changeAvatar(avatarName) {
        this.unityManager.changeAvatar(avatarName);
        this.emit('avatar:change', avatarName);
    }
    /**
     * Alterna exibição de legendas
     */
    toggleSubtitle() {
        this.unityManager.toggleSubtitle();
    }
    /**
     * Define a região do dicionário
     */
    setRegion(region) {
        this.state.region = region;
        const url = `${defaultConfig.dictionaryUrl}${region}/`;
        this.unityManager.setBaseUrl(url);
    }
    /**
     * Obtém o estado atual do player
     */
    getState() {
        return { ...this.state };
    }
    /**
     * Adiciona listener para eventos
     */
    addEventListener(event, listener) {
        if (!this.eventListeners.has(event)) {
            this.eventListeners.set(event, new Set());
        }
        this.eventListeners.get(event).add(listener);
    }
    /**
     * Remove listener de eventos
     */
    removeEventListener(event, listener) {
        const listeners = this.eventListeners.get(event);
        if (listeners) {
            listeners.delete(listener);
        }
    }
    /**
     * ✅ NOVO: Remove todos os listeners de eventos
     */
    removeAllListeners() {
        this.eventListeners.clear();
    }
    /**
     * Destrói o player e limpa recursos
    /**
     * ✅ MELHORADO: Destrói o player e limpa recursos completamente
     */
    dispose() {
        // Limpar listeners
        this.eventListeners.clear();
        // Limpar Unity Manager
        if (this.unityManager) {
            this.unityManager.dispose();
        }
        // ✅ CORREÇÃO: Limpeza completa do container
        if (this.container) {
            this.container.remove();
            this.container = null;
        }
        // ✅ Limpar estado
        this.state.loaded = false;
        this.state.status = 'idle';
        this.state.translated = false;
        this.state.progress = null;
        // Limpar referências
        this.globalGlossLength = 0;
    }
    /**
     * Emite um evento
     */
    emit(event, ...args) {
        const listeners = this.eventListeners.get(event);
        if (listeners) {
            listeners.forEach(listener => {
                try {
                    listener(...args);
                }
                catch (error) {
                    // ✅ CORREÇÃO: Emitir erro em vez de console.error
                    this.emit('error', `Error in VLibras event listener for ${event}: ${error}`);
                }
            });
        }
    }
    /**
     * Configura integração dos callbacks com eventos internos
     */
    setupCallbackIntegration() {
        // 🔥 CORREÇÃO: Integrar callbacks APENAS com eventos, evitando duplicação
        this.addEventListener('animation:play', () => {
            this.state.isPlaying = true;
            this.callbacks.onPlay?.();
        });
        this.addEventListener('animation:pause', () => {
            this.state.isPlaying = false;
            this.callbacks.onPause?.();
        });
        this.addEventListener('animation:resume', () => {
            this.state.isPlaying = true;
            this.callbacks.onResume?.();
        });
        this.addEventListener('animation:restart', () => {
            this.state.isPlaying = true;
            this.callbacks.onRestart?.();
        });
        this.addEventListener('animation:end', () => {
            this.state.isPlaying = false;
            this.callbacks.onStop?.();
        });
        this.addEventListener('error', (error) => {
            this.callbacks.onPlayerError?.(error);
        });
    }
    /**
     * Configura listeners para eventos do Unity
     */
    setupEventListeners() {
        this.unityManager.addEventListener('load', () => {
            this.state.loaded = true;
            this.emit('load');
            this.unityManager.setBaseUrl(`${defaultConfig.dictionaryUrl}${this.state.region}/`);
            if (this.options.onLoad) {
                this.options.onLoad();
            }
        });
        this.unityManager.addEventListener('progress', (progress) => {
            this.state.progress = progress;
            this.emit('animation:progress', progress);
        });
        let wasPaused = false; // 🔥 CORREÇÃO: Flag para detectar se estava pausado
        this.unityManager.addEventListener('stateChange', (isPlaying, isPaused, isLoading) => {
            if (isPaused) {
                wasPaused = true; // 🔥 Marcar que foi pausado
                this.emit('animation:pause');
            }
            else if (isPlaying && !isPaused) {
                // 🔥 CORREÇÃO: Distinguir entre play inicial, resume e restart
                if (this.isRestarting) {
                    // Não emitir evento aqui, já foi emitido no método restart()
                    this.isRestarting = false;
                }
                else if (wasPaused) {
                    this.emit('animation:resume'); // ✅ Retomar após pausa
                    wasPaused = false; // Reset flag
                }
                else {
                    this.emit('animation:play'); // ✅ Play inicial
                }
                this.changeStatus(PLAYER_STATUSES.playing);
            }
            else if (!isPlaying && !isLoading) {
                wasPaused = false; // Reset flag quando termina
                // 🔥 CORREÇÃO: Não emitir animation:end se estivermos reiniciando
                if (!this.isRestarting) {
                    this.emit('animation:end');
                }
                this.changeStatus(PLAYER_STATUSES.idle);
                // 🔥 GENIUS LOGIC: Emitir translate:end quando animação terminar durante uma tradução!
                if (this.isInTranslation && !this.isRestarting) {
                    this.isInTranslation = false;
                    this.emit('translate:end');
                    this.callbacks.onTranslationEnd?.();
                }
            }
        });
        this.unityManager.addEventListener('counterGloss', (counter, length) => {
            this.globalGlossLength = length;
            this.emit('gloss:info', counter, length);
        });
        this.unityManager.addEventListener('getAvatar', (avatar) => {
            this.emit('avatar:change', avatar);
        });
        this.unityManager.addEventListener('finishWelcome', (finished) => {
            this.emit('welcome:end', finished);
        });
    }
    /**
     * Altera o status do player
     */
    changeStatus(status) {
        const previousStatus = this.state.status;
        switch (status) {
            case PLAYER_STATUSES.idle:
                if (previousStatus === PLAYER_STATUSES.playing) {
                    this.state.status = status;
                    this.emit('gloss:end', this.globalGlossLength);
                }
                break;
            case PLAYER_STATUSES.preparing:
                this.state.status = status;
                break;
            case PLAYER_STATUSES.playing:
                if (previousStatus === PLAYER_STATUSES.preparing) {
                    this.state.status = status;
                    this.emit('gloss:start');
                }
                break;
        }
    }
    /**
     * Atualiza URL do dicionário baseado nas configurações de estatísticas
     */
    updateDictionaryUrl(isEnabledStats) {
        const currentUrl = this.unityManager.getCurrentBaseUrl();
        const defaultUrl = `${defaultConfig.dictionaryUrl}${this.state.region}/`;
        const staticUrl = `${defaultConfig.dictionaryStaticUrl}${this.state.region}/`;
        if (!isEnabledStats && currentUrl === defaultUrl) {
            this.unityManager.setBaseUrl(staticUrl);
        }
        else if (isEnabledStats && currentUrl !== defaultUrl) {
            this.unityManager.setBaseUrl(defaultUrl);
        }
    }
}

// ========================================
// REDUCER PARA GERENCIAMENTO DE ESTADO
// ========================================
const initialState = {
    status: 'idle',
    isLoading: false,
    isReady: false,
    isTranslating: false,
    isPlaying: false,
    isPaused: false,
    progress: null,
    region: 'BR',
    errors: {
        fatal: null,
        warnings: [],
    },
};
function playerReducer(state, action) {
    switch (action.type) {
        case 'INITIALIZE_START':
            return {
                ...state,
                status: 'initializing',
                isLoading: true,
                errors: { fatal: null, warnings: [] },
            };
        case 'INITIALIZE_SUCCESS':
            return {
                ...state,
                status: 'ready',
                isLoading: false,
                isReady: true,
            };
        case 'INITIALIZE_ERROR':
            return {
                ...state,
                status: 'error',
                isLoading: false,
                isReady: false,
                errors: { ...state.errors, fatal: action.payload },
            };
        case 'TRANSLATION_START':
            return {
                ...state,
                status: 'translating',
                isTranslating: true,
                currentText: action.payload,
                lastTranslation: {
                    text: action.payload,
                    timestamp: Date.now(),
                },
            };
        case 'TRANSLATION_END':
            return {
                ...state,
                status: 'ready',
                isTranslating: false,
            };
        case 'TRANSLATION_ERROR':
            return {
                ...state,
                isTranslating: false,
                errors: {
                    ...state.errors,
                    warnings: [...state.errors.warnings, action.payload],
                },
            };
        case 'PLAYBACK_START':
            return {
                ...state,
                status: 'playing',
                isPlaying: true,
                isPaused: false,
            };
        case 'PLAYBACK_PAUSE':
            return {
                ...state,
                status: 'paused',
                isPlaying: false,
                isPaused: true,
            };
        case 'PLAYBACK_RESUME':
            return {
                ...state,
                status: 'playing',
                isPlaying: true,
                isPaused: false,
            };
        case 'PLAYBACK_STOP':
            return {
                ...state,
                status: 'ready',
                isPlaying: false,
                isPaused: false,
                progress: null,
            };
        case 'PLAYBACK_RESTART':
            return {
                ...state,
                status: 'playing',
                isPlaying: true,
                isPaused: false,
                progress: 0,
            };
        case 'PROGRESS_UPDATE':
            return {
                ...state,
                progress: action.payload,
            };
        case 'REGION_CHANGE':
            return {
                ...state,
                region: action.payload,
            };
        case 'WARNING_ADD':
            return {
                ...state,
                errors: {
                    ...state.errors,
                    warnings: [...state.errors.warnings, action.payload],
                },
            };
        case 'WARNINGS_CLEAR':
            return {
                ...state,
                errors: {
                    ...state.errors,
                    warnings: [],
                },
            };
        case 'RESET':
            return initialState;
        default:
            return state;
    }
}
// ========================================
// HOOK PRINCIPAL
// ========================================
/**
 * Hook React para VLibras Player com melhores práticas
 *
 * @example
 * ```tsx
 * const containerRef = useRef<HTMLDivElement>(null);
 * const player = useVLibrasPlayer({
 *   containerRef,
 *   autoInit: true,
 *   onPlayerReady: () => console.log('Player pronto!'),
 *   onStateChange: (state) => console.log('Estado:', state.status)
 * });
 *
 * // Usar o player
 * await player.translate('Olá mundo');
 * ```
 */
function useVLibrasPlayer(options = {}) {
    const { containerRef, autoInit = false, retryOnError = true, maxRetries = 3, debounceMs = 300, onStateChange, onPlayerReady, onLoad, // Compatibilidade com VLibrasPlayer
    onPlayerError, onTranslationStart, onTranslationEnd, onTranslationError, onPlay, onPlaybackStart, onPlaybackEnd, onPause, onPlaybackPause, onResume, onPlaybackResume, onRestart, onPlaybackRestart, onStop, ...playerOptions } = options;
    // ========================================
    // ESTADO GERENCIADO POR REDUCER
    // ========================================
    const [state, dispatch] = useReducer(playerReducer, initialState);
    // Refs para instâncias e controle
    const playerRef = useRef(null);
    const retryCountRef = useRef(0);
    const debounceTimerRef = useRef(null);
    const abortControllerRef = useRef(null);
    // ========================================
    // CALLBACKS MEMOIZADOS
    // ========================================
    const handlePlayerReady = useCallback(() => {
        dispatch({ type: 'INITIALIZE_SUCCESS' });
        onPlayerReady?.();
        onLoad?.(); // Compatibilidade com VLibrasPlayer
    }, [onPlayerReady, onLoad]);
    const handlePlayerError = useCallback((errorMessage) => {
        const isFatal = errorMessage.includes('failed to load') || errorMessage.includes('network error');
        if (isFatal) {
            dispatch({ type: 'INITIALIZE_ERROR', payload: errorMessage });
        }
        else {
            dispatch({ type: 'WARNING_ADD', payload: errorMessage });
        }
        onPlayerError?.(errorMessage, isFatal);
    }, [onPlayerError]);
    const handleTranslationStart = useCallback((text) => {
        dispatch({ type: 'TRANSLATION_START', payload: text });
        onTranslationStart?.();
    }, [onTranslationStart]);
    const handleTranslationEnd = useCallback(() => {
        dispatch({ type: 'TRANSLATION_END' });
        onTranslationEnd?.();
    }, [onTranslationEnd]);
    const handleTranslationError = useCallback((errorMessage) => {
        dispatch({ type: 'TRANSLATION_ERROR', payload: errorMessage });
        onTranslationError?.(errorMessage);
    }, [onTranslationError]);
    const handlePlaybackStart = useCallback(() => {
        dispatch({ type: 'PLAYBACK_START' });
        onPlaybackStart?.();
        onPlay?.(); // Compatibilidade com VLibrasPlayer
    }, [onPlaybackStart, onPlay]);
    const handlePlaybackPause = useCallback(() => {
        dispatch({ type: 'PLAYBACK_PAUSE' });
        onPlaybackPause?.();
        onPause?.(); // Compatibilidade com VLibrasPlayer
    }, [onPlaybackPause, onPause]);
    const handlePlaybackResume = useCallback(() => {
        dispatch({ type: 'PLAYBACK_RESUME' });
        onPlaybackResume?.();
        onResume?.(); // Compatibilidade com VLibrasPlayer
    }, [onPlaybackResume, onResume]);
    const handlePlaybackStop = useCallback(() => {
        dispatch({ type: 'PLAYBACK_STOP' });
        onPlaybackEnd?.();
        onStop?.(); // Compatibilidade com VLibrasPlayer
    }, [onPlaybackEnd, onStop]);
    const handlePlaybackRestart = useCallback(() => {
        dispatch({ type: 'PLAYBACK_RESTART' });
        onPlaybackRestart?.();
        onRestart?.(); // Compatibilidade com VLibrasPlayer
    }, [onPlaybackRestart, onRestart]);
    // ========================================
    // FUNÇÕES DE CONTROLE DO PLAYER
    // ========================================
    const initializePlayer = useCallback(async () => {
        if (!containerRef?.current || playerRef.current || state.isLoading) {
            return;
        }
        // Cancelar operação anterior se existir
        if (abortControllerRef.current) {
            abortControllerRef.current.abort();
        }
        abortControllerRef.current = new AbortController();
        dispatch({ type: 'INITIALIZE_START' });
        try {
            const player = new VLibrasPlayer({
                ...playerOptions,
                // Mapear callbacks do hook para os callbacks da classe
                onLoad: () => {
                    const playerState = player.getState();
                    if (playerState.loaded) {
                        handlePlayerReady();
                    }
                },
                onPlayerError: handlePlayerError,
                onTranslationStart: () => handleTranslationStart(state.currentText || ''),
                onTranslationEnd: handleTranslationEnd,
                onTranslationError: handleTranslationError,
                onPlay: handlePlaybackStart,
                onPause: handlePlaybackPause,
                onResume: handlePlaybackResume,
                onStop: handlePlaybackStop,
                onRestart: handlePlaybackRestart,
            });
            await player.load(containerRef.current);
            // Verificar se a operação foi cancelada
            if (abortControllerRef.current?.signal.aborted) {
                player.dispose();
                return;
            }
            playerRef.current = player;
            retryCountRef.current = 0;
        }
        catch (error) {
            if (abortControllerRef.current?.signal.aborted) {
                return;
            }
            const errorMessage = error instanceof Error ? error.message : 'Erro desconhecido ao inicializar player';
            // Tentar novamente se configurado
            if (retryOnError && retryCountRef.current < maxRetries) {
                retryCountRef.current++;
                setTimeout(() => initializePlayer(), 1000 * retryCountRef.current);
                return;
            }
            dispatch({ type: 'INITIALIZE_ERROR', payload: errorMessage });
        }
    }, [
        containerRef,
        playerOptions,
        state.isLoading,
        state.currentText,
        handlePlayerReady,
        handlePlayerError,
        handleTranslationStart,
        handleTranslationEnd,
        handleTranslationError,
        handlePlaybackStart,
        handlePlaybackPause,
        handlePlaybackResume,
        handlePlaybackStop,
        handlePlaybackRestart,
        retryOnError,
        maxRetries
    ]);
    const translate = useCallback(async (text, translationOptions) => {
        if (!playerRef.current) {
            throw new Error('Player não inicializado. Chame initializePlayer() primeiro.');
        }
        if (!state.isReady) {
            throw new Error('Player não está pronto. Aguarde a inicialização.');
        }
        // Debounce para evitar traduções muito rápidas
        if (debounceTimerRef.current) {
            clearTimeout(debounceTimerRef.current);
        }
        return new Promise((resolve, reject) => {
            debounceTimerRef.current = setTimeout(async () => {
                try {
                    await playerRef.current.translate(text, translationOptions);
                    resolve();
                }
                catch (error) {
                    reject(error);
                }
            }, debounceMs);
        });
    }, [state.isReady, debounceMs]);
    // Métodos de controle memoizados
    const controls = useMemo(() => ({
        play: (gloss, options) => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            return playerRef.current.play(gloss, options);
        },
        pause: () => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            return playerRef.current.pause();
        },
        resume: () => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            return playerRef.current.resume();
        },
        stop: () => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            return playerRef.current.stop();
        },
        restart: () => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            return playerRef.current.restart();
        },
        repeat: () => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            return playerRef.current.repeat();
        },
    }), []);
    // Métodos de configuração memoizados
    const settings = useMemo(() => ({
        setSpeed: (speed) => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            playerRef.current.setSpeed(speed);
        },
        setRegion: (region) => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            playerRef.current.setRegion(region);
            dispatch({ type: 'REGION_CHANGE', payload: region });
        },
        changeAvatar: (avatarName) => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            playerRef.current.changeAvatar(avatarName);
        },
        toggleSubtitle: () => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            playerRef.current.toggleSubtitle();
        },
        playWelcome: () => {
            if (!playerRef.current)
                throw new Error('Player não inicializado');
            playerRef.current.playWelcome();
        },
    }), []);
    // Estados derivados memoizados
    const derivedState = useMemo(() => ({
        canTranslate: state.isReady && !state.isTranslating,
        canPause: state.isPlaying && !state.isPaused,
        canResume: state.isPaused,
        canStop: state.isPlaying || state.isPaused,
        canRestart: state.isReady && (state.isPlaying || state.isPaused || state.lastTranslation),
        hasWarnings: state.errors.warnings.length > 0,
        isBusy: state.isLoading || state.isTranslating,
    }), [state]);
    // Utilitários
    const utils = useMemo(() => ({
        clearWarnings: () => dispatch({ type: 'WARNINGS_CLEAR' }),
        reset: () => {
            if (playerRef.current) {
                playerRef.current.dispose();
                playerRef.current = null;
            }
            dispatch({ type: 'RESET' });
        },
        getPlayer: () => playerRef.current,
        retry: () => {
            retryCountRef.current = 0;
            return initializePlayer();
        },
    }), [initializePlayer]);
    // ========================================
    // EFEITOS
    // ========================================
    // Callback para mudanças de estado
    useEffect(() => {
        onStateChange?.(state);
    }, [state, onStateChange]);
    // Inicialização automática
    useEffect(() => {
        if (autoInit && containerRef?.current && !playerRef.current && !state.isLoading) {
            initializePlayer();
        }
    }, [autoInit, containerRef, initializePlayer, state.isLoading]);
    // Limpeza ao desmontar
    useEffect(() => {
        return () => {
            if (debounceTimerRef.current) {
                clearTimeout(debounceTimerRef.current);
            }
            if (abortControllerRef.current) {
                abortControllerRef.current.abort();
            }
            if (playerRef.current) {
                playerRef.current.dispose();
            }
        };
    }, []);
    // ========================================
    // RETORNO DO HOOK
    // ========================================
    return {
        // Estado principal
        ...state,
        ...derivedState,
        // Métodos principais
        translate,
        initializePlayer,
        // Controles agrupados
        controls,
        settings,
        utils,
        // Compatibilidade (métodos individuais)
        play: controls.play,
        pause: controls.pause,
        resume: controls.resume,
        stop: controls.stop,
        restart: controls.restart,
        repeat: controls.repeat,
        setSpeed: settings.setSpeed,
        setRegion: settings.setRegion,
        changeAvatar: settings.changeAvatar,
        toggleSubtitle: settings.toggleSubtitle,
        playWelcome: settings.playWelcome,
        // Dados legados para compatibilidade
        player: {
            status: state.status,
            loaded: state.isReady,
            translated: !!state.lastTranslation,
            progress: state.progress,
            region: state.region,
            isTranslating: state.isTranslating,
            isPlaying: state.isPlaying,
            text: state.currentText,
            gloss: state.currentGloss,
        },
        error: state.errors.fatal,
        isLoading: state.isLoading,
        isReady: state.isReady,
        isTranslating: state.isTranslating,
        isPlaying: state.isPlaying,
    };
}

// Hooks React para controle avançado
// Versão da biblioteca
const VERSION = '2.4.1';

export { DEFAULT_PLAYER_OPTIONS, PLAYER_STATUSES, UnityLoader, UnityPlayerManager, UnityStateManager, VERSION, VLibrasPlayer, VLibrasTranslator, defaultConfig, useVLibrasPlayer };
//# sourceMappingURL=index.esm.js.map