[251021] fix: audioContext삭제,디버그화면 개선
🕐 커밋 시간: 2025. 10. 21. 20:52:42 📊 변경 통계: • 총 파일: 1개 • 추가: +29줄 • 삭제: -113줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator(), clearAllTimers() Performance: 코드 최적화로 성능 개선 기대
This commit is contained in:
@@ -33,8 +33,8 @@ const OverlayContainer = SpotlightContainerDecorator(
|
||||
|
||||
const SpottableMicButton = Spottable('div');
|
||||
|
||||
// Debug mode constant
|
||||
const DEBUG_MODE = process.env.NODE_ENV === 'development';
|
||||
// Debug mode constant - 항상 디버그 화면 표시
|
||||
const DEBUG_MODE = true;
|
||||
|
||||
// Voice overlay 모드 상수
|
||||
export const VOICE_MODES = {
|
||||
@@ -81,10 +81,6 @@ const DEFAULT_SUGGESTIONS = [
|
||||
// false로 설정하면 이 기능은 완전히 비활성화됩니다
|
||||
const ENABLE_WAKE_WORD = false;
|
||||
|
||||
// 🔧 실험적 기능: Beep Sound on Listening Start
|
||||
// false로 설정하면 Beep 소리가 재생되지 않습니다
|
||||
const ENABLE_BEEP_SOUND = true;
|
||||
|
||||
// Utility function to clear a single timer ref (timeout)
|
||||
const clearTimerRef = (timerRef) => {
|
||||
if (timerRef.current) {
|
||||
@@ -127,7 +123,6 @@ const VoiceInputOverlay = ({
|
||||
const dispatch = useDispatch();
|
||||
const lastFocusedElement = useRef(null);
|
||||
const listeningTimerRef = useRef(null);
|
||||
const audioContextRef = useRef(null);
|
||||
const interimTextRef = useRef(''); // Interim text 추적용 ref
|
||||
|
||||
// Timer refs for cleanup
|
||||
@@ -193,81 +188,6 @@ const VoiceInputOverlay = ({
|
||||
}
|
||||
}, [currentMode, isVisible]);
|
||||
|
||||
// 🔊 Beep 소리 재생 함수 - zero dependencies
|
||||
const playBeep = useCallback(() => {
|
||||
if (!ENABLE_BEEP_SOUND) return;
|
||||
|
||||
try {
|
||||
// AudioContext 지원 여부 확인 (TV 환경에서는 지원되지 않을 수 있음)
|
||||
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
||||
if (!AudioContextClass) {
|
||||
if (DEBUG_MODE) {
|
||||
console.warn(
|
||||
'[VoiceInputOverlay.v2] AudioContext not supported in this environment, skipping beep'
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// AudioContext 생성 (재사용)
|
||||
if (!audioContextRef.current) {
|
||||
try {
|
||||
audioContextRef.current = new AudioContextClass();
|
||||
} catch (contextErr) {
|
||||
if (DEBUG_MODE) {
|
||||
console.warn('[VoiceInputOverlay.v2] Failed to create AudioContext:', contextErr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const audioContext = audioContextRef.current;
|
||||
|
||||
// null 또는 undefined 체크
|
||||
if (!audioContext) {
|
||||
if (DEBUG_MODE) {
|
||||
console.warn('[VoiceInputOverlay.v2] AudioContext is null or undefined, skipping beep');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// AudioContext 메서드 존재 여부 확인
|
||||
if (
|
||||
typeof audioContext.createOscillator !== 'function' ||
|
||||
typeof audioContext.createGain !== 'function'
|
||||
) {
|
||||
if (DEBUG_MODE) {
|
||||
console.warn('[VoiceInputOverlay.v2] AudioContext methods not available, skipping beep');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
oscillator.frequency.value = 800; // 800Hz (높은 피치)
|
||||
oscillator.type = 'sine'; // 부드러운 소리
|
||||
|
||||
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); // 볼륨 30%
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1); // 페이드아웃
|
||||
|
||||
oscillator.start(audioContext.currentTime);
|
||||
oscillator.stop(audioContext.currentTime + 0.1); // 0.1초 재생
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔊 [VoiceInputOverlay.v2] Beep sound played successfully');
|
||||
}
|
||||
} catch (err) {
|
||||
// 어떤 오류가 발생하더라도 앱이 멈추지 않도록 조용히 처리
|
||||
if (DEBUG_MODE) {
|
||||
console.warn('[VoiceInputOverlay.v2] Failed to play beep sound (non-critical):', err);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 🔍 검색 기록 저장 함수 (성능 최적화: stable reference)
|
||||
const addToSearchHistory = useCallback((searchText) => {
|
||||
if (!searchText || searchText.trim().length < 3) return;
|
||||
@@ -674,25 +594,11 @@ const VoiceInputOverlay = ({
|
||||
};
|
||||
}, [isVisible, mode, dispatch]);
|
||||
|
||||
// Cleanup all timers and AudioContext on component unmount
|
||||
// Cleanup all timers on component unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Clear all timer refs
|
||||
clearAllTimers(allTimerRefs);
|
||||
|
||||
// Close AudioContext to free audio resources
|
||||
if (audioContextRef.current) {
|
||||
try {
|
||||
if (typeof audioContextRef.current.close === 'function') {
|
||||
audioContextRef.current.close();
|
||||
}
|
||||
} catch (err) {
|
||||
if (DEBUG_MODE) {
|
||||
console.warn('[VoiceInputOverlay.v2] Failed to close AudioContext:', err);
|
||||
}
|
||||
}
|
||||
audioContextRef.current = null;
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@@ -997,9 +903,6 @@ const VoiceInputOverlay = ({
|
||||
// ✅ Interim text ref 초기화
|
||||
interimTextRef.current = '';
|
||||
|
||||
// 🔊 Beep 소리 재생
|
||||
playBeep();
|
||||
|
||||
// 기존 타이머 정리
|
||||
clearTimerRef(listeningTimerRef);
|
||||
clearTimerRef(silenceDetectionTimerRef);
|
||||
@@ -1025,7 +928,6 @@ const VoiceInputOverlay = ({
|
||||
[
|
||||
currentMode,
|
||||
handleClose,
|
||||
playBeep,
|
||||
startListening,
|
||||
stopListening,
|
||||
onSearchChange,
|
||||
@@ -1104,10 +1006,8 @@ const VoiceInputOverlay = ({
|
||||
handleMicBlur,
|
||||
]);
|
||||
|
||||
// Memoize debug UI (only render when DEBUG_MODE is true)
|
||||
// Memoize debug UI (always render for TV debugging)
|
||||
const debugUI = useMemo(() => {
|
||||
if (!DEBUG_MODE) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -1116,24 +1016,114 @@ const VoiceInputOverlay = ({
|
||||
right: 10,
|
||||
color: '#fff',
|
||||
zIndex: 10000,
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
padding: '10px',
|
||||
borderRadius: '5px',
|
||||
fontSize: '14px',
|
||||
backgroundColor: 'rgba(0,0,0,0.9)',
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
fontSize: '18px',
|
||||
minWidth: '350px',
|
||||
lineHeight: '1.6',
|
||||
fontFamily: 'monospace',
|
||||
}}
|
||||
>
|
||||
<div>Voice Version: {voiceVersion}</div>
|
||||
<div>Input Mode: {voiceInputMode || 'None'}</div>
|
||||
<div>Current Mode: {currentMode}</div>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '12px',
|
||||
borderBottom: '2px solid #fff',
|
||||
paddingBottom: '8px',
|
||||
fontSize: '20px',
|
||||
}}
|
||||
>
|
||||
🔊 Voice Input Debug Info
|
||||
</div>
|
||||
<div>
|
||||
Voice Version: <span style={{ color: '#4CAF50' }}>{voiceVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
Input Mode: <span style={{ color: '#2196F3' }}>{voiceInputMode || 'None'}</span>
|
||||
</div>
|
||||
<div>
|
||||
Current Mode: <span style={{ color: '#FF9800' }}>{currentMode}</span>
|
||||
</div>
|
||||
<div>
|
||||
isSupported:{' '}
|
||||
{isSupported ? (
|
||||
<span style={{ color: '#4CAF50' }}>✅ YES</span>
|
||||
) : (
|
||||
<span style={{ color: '#F44336' }}>❌ NO</span>
|
||||
)}
|
||||
</div>
|
||||
{voiceVersion === VOICE_VERSION.WEB_SPEECH && (
|
||||
<>
|
||||
<div>isListening: {isListening ? '🎤 YES' : '❌ NO'}</div>
|
||||
<div>Interim: {interimText || 'N/A'}</div>
|
||||
<div>
|
||||
isListening:{' '}
|
||||
{isListening ? (
|
||||
<span style={{ color: '#4CAF50' }}>🎤 YES</span>
|
||||
) : (
|
||||
<span style={{ color: '#F44336' }}>❌ NO</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
Interim:{' '}
|
||||
<span style={{ color: '#9C27B0', wordBreak: 'break-all' }}>
|
||||
"{interimText || 'N/A'}"
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Ref Text:{' '}
|
||||
<span style={{ color: '#9C27B0', wordBreak: 'break-all' }}>
|
||||
"{interimTextRef.current || 'N/A'}"
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Countdown:{' '}
|
||||
<span style={{ color: '#FF5722', fontSize: '20px', fontWeight: 'bold' }}>
|
||||
{countdown}s
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
STT Result:{' '}
|
||||
<span style={{ color: '#607D8B', wordBreak: 'break-all' }}>
|
||||
"{lastSTTText || 'None'}"
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
STT Time:{' '}
|
||||
<span style={{ color: '#795548' }}>
|
||||
{sttTimestamp ? new Date(sttTimestamp).toLocaleTimeString() : 'None'}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
marginTop: '12px',
|
||||
fontSize: '14px',
|
||||
color: '#ccc',
|
||||
borderTop: '1px solid #666',
|
||||
paddingTop: '8px',
|
||||
}}
|
||||
>
|
||||
Timers:{' '}
|
||||
{listeningTimerRef.current ? (
|
||||
<span style={{ color: '#FF5722' }}>⏰ LISTENING ACTIVE</span>
|
||||
) : (
|
||||
<span style={{ color: '#666' }}>None</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [voiceVersion, voiceInputMode, currentMode, isListening, interimText]);
|
||||
}, [
|
||||
voiceVersion,
|
||||
voiceInputMode,
|
||||
currentMode,
|
||||
isListening,
|
||||
interimText,
|
||||
isSupported,
|
||||
countdown,
|
||||
lastSTTText,
|
||||
sttTimestamp,
|
||||
]);
|
||||
|
||||
return (
|
||||
<TFullPopup
|
||||
|
||||
Reference in New Issue
Block a user