[251016] fix: VUI Test-1
🕐 커밋 시간: 2025. 10. 16. 14:10:17 📊 변경 통계: • 총 파일: 3개 • 추가: +160줄 • 삭제: -3줄 📁 추가된 파일: + com.twin.app.shoptime/vui-guide.2.md 📝 수정된 파일: ~ com.twin.app.shoptime/src/App/App.js ~ com.twin.app.shoptime/src/actions/voiceActions.js 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/App/App.js (javascript): ✅ Added: processArgs(), sendVoiceLogToPanel() 📄 com.twin.app.shoptime/src/actions/voiceActions.js (javascript): 🔄 Modified: addLog() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • 개발 문서 및 가이드 개선
This commit is contained in:
@@ -63,6 +63,7 @@ import css from './App.module.less';
|
|||||||
import { handleBypassLink } from './bypassLinkHandler';
|
import { handleBypassLink } from './bypassLinkHandler';
|
||||||
import { handleDeepLink } from './deepLinkHandler';
|
import { handleDeepLink } from './deepLinkHandler';
|
||||||
import { sendLogTotalRecommend } from '../actions/logActions';
|
import { sendLogTotalRecommend } from '../actions/logActions';
|
||||||
|
import { types } from '../actions/actionTypes';
|
||||||
// import {
|
// import {
|
||||||
// startFocusMonitoring,
|
// startFocusMonitoring,
|
||||||
// stopFocusMonitoring,
|
// stopFocusMonitoring,
|
||||||
@@ -109,15 +110,158 @@ const processArgs = (args) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Voice 관련 로그를 VoicePanel로 전송하는 헬퍼 함수
|
||||||
|
const sendVoiceLogToPanel = (args) => {
|
||||||
|
try {
|
||||||
|
const firstArg = args[0];
|
||||||
|
// [Voice] 또는 [VoiceConductor] 태그 확인
|
||||||
|
if (
|
||||||
|
typeof firstArg === 'string' &&
|
||||||
|
(firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]'))
|
||||||
|
) {
|
||||||
|
// 로그 타입 결정
|
||||||
|
let logType = 'INFO';
|
||||||
|
let title = firstArg;
|
||||||
|
|
||||||
|
if (
|
||||||
|
firstArg.includes('ERROR') ||
|
||||||
|
firstArg.includes('Error') ||
|
||||||
|
firstArg.includes('Failed') ||
|
||||||
|
firstArg.includes('failed')
|
||||||
|
) {
|
||||||
|
logType = 'ERROR';
|
||||||
|
} else if (
|
||||||
|
firstArg.includes('Response') ||
|
||||||
|
firstArg.includes('response') ||
|
||||||
|
firstArg.includes('success')
|
||||||
|
) {
|
||||||
|
logType = 'RESPONSE';
|
||||||
|
} else if (
|
||||||
|
firstArg.includes('Registering') ||
|
||||||
|
firstArg.includes('Sending') ||
|
||||||
|
firstArg.includes('request')
|
||||||
|
) {
|
||||||
|
logType = 'REQUEST';
|
||||||
|
} else if (
|
||||||
|
firstArg.includes('received') ||
|
||||||
|
firstArg.includes('Handling') ||
|
||||||
|
firstArg.includes('⭐')
|
||||||
|
) {
|
||||||
|
logType = 'ACTION';
|
||||||
|
} else if (firstArg.includes('command')) {
|
||||||
|
logType = 'COMMAND';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 데이터 수집
|
||||||
|
const logData = {};
|
||||||
|
if (args.length > 1) {
|
||||||
|
args.slice(1).forEach((arg, index) => {
|
||||||
|
if (typeof arg === 'object') {
|
||||||
|
Object.assign(logData, arg);
|
||||||
|
} else {
|
||||||
|
logData[`arg${index + 1}`] = arg;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redux로 dispatch
|
||||||
|
store.dispatch({
|
||||||
|
type: types.VOICE_ADD_LOG,
|
||||||
|
payload: {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
type: logType,
|
||||||
|
title: title.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''),
|
||||||
|
data: Object.keys(logData).length > 0 ? logData : { message: firstArg },
|
||||||
|
success: logType !== 'ERROR',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 로깅 중 에러가 발생해도 원래 로그는 출력되어야 함
|
||||||
|
originalConsoleLog.call(console, '[VoiceLog] Error sending to panel:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
console.log = function (...args) {
|
console.log = function (...args) {
|
||||||
|
// Voice 로그를 VoicePanel로 전송
|
||||||
|
sendVoiceLogToPanel(args);
|
||||||
|
// 원래 console.log 실행
|
||||||
originalConsoleLog.apply(console, processArgs(args));
|
originalConsoleLog.apply(console, processArgs(args));
|
||||||
};
|
};
|
||||||
|
|
||||||
console.error = function (...args) {
|
console.error = function (...args) {
|
||||||
|
// Voice 로그를 VoicePanel로 전송 (에러는 강제로 ERROR 타입)
|
||||||
|
try {
|
||||||
|
const firstArg = args[0];
|
||||||
|
if (
|
||||||
|
typeof firstArg === 'string' &&
|
||||||
|
(firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]'))
|
||||||
|
) {
|
||||||
|
const logData = {};
|
||||||
|
if (args.length > 1) {
|
||||||
|
args.slice(1).forEach((arg, index) => {
|
||||||
|
if (typeof arg === 'object') {
|
||||||
|
Object.assign(logData, arg);
|
||||||
|
} else {
|
||||||
|
logData[`arg${index + 1}`] = arg;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch({
|
||||||
|
type: types.VOICE_ADD_LOG,
|
||||||
|
payload: {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
type: 'ERROR',
|
||||||
|
title: firstArg.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''),
|
||||||
|
data: Object.keys(logData).length > 0 ? logData : { message: firstArg },
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
originalConsoleError.call(console, '[VoiceLog] Error sending error to panel:', error);
|
||||||
|
}
|
||||||
|
|
||||||
originalConsoleError.apply(console, processArgs(args));
|
originalConsoleError.apply(console, processArgs(args));
|
||||||
};
|
};
|
||||||
|
|
||||||
console.warn = function (...args) {
|
console.warn = function (...args) {
|
||||||
|
// Voice 로그를 VoicePanel로 전송 (경고는 ERROR 타입으로)
|
||||||
|
try {
|
||||||
|
const firstArg = args[0];
|
||||||
|
if (
|
||||||
|
typeof firstArg === 'string' &&
|
||||||
|
(firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]'))
|
||||||
|
) {
|
||||||
|
const logData = {};
|
||||||
|
if (args.length > 1) {
|
||||||
|
args.slice(1).forEach((arg, index) => {
|
||||||
|
if (typeof arg === 'object') {
|
||||||
|
Object.assign(logData, arg);
|
||||||
|
} else {
|
||||||
|
logData[`arg${index + 1}`] = arg;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch({
|
||||||
|
type: types.VOICE_ADD_LOG,
|
||||||
|
payload: {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
type: 'ERROR',
|
||||||
|
title:
|
||||||
|
'WARNING: ' +
|
||||||
|
firstArg.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''),
|
||||||
|
data: Object.keys(logData).length > 0 ? logData : { message: firstArg },
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
originalConsoleWarn.call(console, '[VoiceLog] Error sending warning to panel:', error);
|
||||||
|
}
|
||||||
|
|
||||||
originalConsoleWarn.apply(console, processArgs(args));
|
originalConsoleWarn.apply(console, processArgs(args));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -99,20 +99,30 @@ export const registerVoiceFramework = () => (dispatch, getState) => {
|
|||||||
|
|
||||||
// performAction command received
|
// performAction command received
|
||||||
if (res.command === 'performAction' && res.action) {
|
if (res.command === 'performAction' && res.action) {
|
||||||
console.log('[Voice] performAction command received:', res.action);
|
console.log('[Voice] ⭐⭐⭐ performAction command received:', res.action);
|
||||||
|
|
||||||
|
// ⭐ 중요: performAction 수신 성공 로그 (명확하게)
|
||||||
dispatch(
|
dispatch(
|
||||||
addLog('COMMAND', 'performAction Command Received', {
|
addLog('COMMAND', '✅ performAction RECEIVED!', {
|
||||||
|
message: '✅ SUCCESS! Voice framework sent performAction event.',
|
||||||
command: res.command,
|
command: res.command,
|
||||||
action: res.action,
|
action: res.action,
|
||||||
|
intent: res.action?.intent,
|
||||||
|
value: res.action?.value || res.action?.itemId,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.VOICE_PERFORM_ACTION,
|
type: types.VOICE_PERFORM_ACTION,
|
||||||
payload: res.action,
|
payload: res.action,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get voiceTicket from Redux state (performAction response doesn't include voiceTicket)
|
||||||
|
const { voiceTicket } = getState().voice;
|
||||||
|
console.log('[Voice] Using voiceTicket from state:', voiceTicket);
|
||||||
|
|
||||||
// Process the action and report result
|
// Process the action and report result
|
||||||
dispatch(handleVoiceAction(res.voiceTicket, res.action));
|
dispatch(handleVoiceAction(voiceTicket, res.action));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -215,6 +225,36 @@ export const sendVoiceIntents = (voiceTicket) => (dispatch, getState) => {
|
|||||||
type: types.VOICE_SET_CONTEXT_SUCCESS,
|
type: types.VOICE_SET_CONTEXT_SUCCESS,
|
||||||
payload: res,
|
payload: res,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ⭐ 중요: Voice input 대기 상태 로그
|
||||||
|
dispatch(
|
||||||
|
addLog('ACTION', '🎤 Ready for Voice Input', {
|
||||||
|
message: 'Context set successfully. Press the MIC button on remote and speak.',
|
||||||
|
nextStep: 'Waiting for performAction event...',
|
||||||
|
voiceTicket: voiceTicket,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 15초 후에도 performAction이 안 오면 경고 로그 (타이머)
|
||||||
|
setTimeout(() => {
|
||||||
|
const currentState = getState().voice;
|
||||||
|
// lastSTTText가 업데이트되지 않았으면 (performAction이 안 왔으면)
|
||||||
|
if (!currentState.lastSTTText || currentState.sttTimestamp < Date.now() - 14000) {
|
||||||
|
dispatch(
|
||||||
|
addLog('ERROR', '⚠️ No performAction received yet', {
|
||||||
|
message: 'performAction event was not received within 15 seconds after setContext.',
|
||||||
|
possibleReasons: [
|
||||||
|
'1. Did you press the MIC button on the remote control?',
|
||||||
|
'2. Did you speak after pressing the MIC button?',
|
||||||
|
'3. UseIME intent might not be supported on this webOS version',
|
||||||
|
'4. Voice framework might not be routing events correctly',
|
||||||
|
],
|
||||||
|
suggestion:
|
||||||
|
'Try pressing the remote MIC button and speaking clearly. Check VoicePanel logs for performAction event.',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 15000);
|
||||||
},
|
},
|
||||||
|
|
||||||
onFailure: (err) => {
|
onFailure: (err) => {
|
||||||
|
|||||||
347
com.twin.app.shoptime/vui-guide.2.md
Normal file
347
com.twin.app.shoptime/vui-guide.2.md
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
# `com.webos.service.voiceconductor` v1.1 API 문서
|
||||||
|
|
||||||
|
> **생성일**: 2024/04/18
|
||||||
|
> **최종 수정자**: soonwon.hong
|
||||||
|
> **문서 생성 도구**: APIEditorScript (`apieditor.script`)
|
||||||
|
> **대상 플랫폼**: webOS TV
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 요약 (Summary)
|
||||||
|
|
||||||
|
`voiceconductor`는 webOS TV의 **음성 프레임워크의 핵심 서비스**로, 전체 음성 처리 흐름을 조율(Conduct)합니다.
|
||||||
|
모든 음성 관련 요청은 이 서비스를 통해 시작되며, 다음 주요 기능을 제공합니다:
|
||||||
|
|
||||||
|
- **STT (Speech-to-Text)**: 사용자 음성을 텍스트로 변환
|
||||||
|
- **Intent 인식**: 음성 또는 텍스트를 기반으로 사용자의 의도(Intent) 분석
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 개요 (Overview)
|
||||||
|
|
||||||
|
### API 카테고리
|
||||||
|
|
||||||
|
| 카테고리 | 설명 |
|
||||||
|
|--------|------|
|
||||||
|
| `/` | 국가별 비즈니스 계약에 따라 **Google, Alexa, Alibaba, LG** 등 다양한 플러그인 중 하나가 자동 선택되어 실행 |
|
||||||
|
| `/system` | **LG 자사 플러그인**을 강제로 사용 (기본값). `/`와 동일한 API 형식을 가지며, 중복 설명 생략 |
|
||||||
|
| `/interactor` | **In-App Control** 기능 지원 (webOS 4.5+) — 앱 내에서 음성 명령 처리 가능 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 메서드 (Methods)
|
||||||
|
|
||||||
|
### `/getRecognitionStatus`
|
||||||
|
> 현재 진행 중인 음성 인식 작업 상태 조회 (Subscription 지원)
|
||||||
|
|
||||||
|
- **지원 플랫폼**: 없음 (`[Private]`)
|
||||||
|
- **ACG**: 없음
|
||||||
|
- **파라미터**:
|
||||||
|
- `taskTypes` (String[]): `["fullVoiceNlp", "voiceNlp", "textNlp", "stt"]`
|
||||||
|
- `subscribe` (Boolean): 구독 여부
|
||||||
|
- **반환**:
|
||||||
|
- `tasks`: 실행 중인 `voiceTask` 객체 배열
|
||||||
|
- `subscribed`, `returnValue`, `errorCode`, `errorText`
|
||||||
|
|
||||||
|
✅ **예시**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"voiceTicket": "V00001",
|
||||||
|
"taskType": "fullVoiceNlp",
|
||||||
|
"voiceEngine": "googleAssistant",
|
||||||
|
"recognitionSource": { "input": "voiceinput", "type": "remote", "key": "mrcu" },
|
||||||
|
"status": "init"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subscribed": true,
|
||||||
|
"returnValue": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/cancelRecognition`
|
||||||
|
> 진행 중인 음성 인식 작업 취소
|
||||||
|
|
||||||
|
- **지원**: TV (`[Private]`)
|
||||||
|
- **파라미터**: `voiceTicket` (String)
|
||||||
|
- **반환**: `returnValue`, `errorCode`, `errorText`
|
||||||
|
|
||||||
|
> ⚠️ `stopRecordingVoice`와 달리, 녹음 중이 아닐 경우 STT 플러그인에 종료 요청을 보내지 않음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/checkUpdate`
|
||||||
|
> 특정 패키지에 대한 음성 기능 업데이트 필요 여부 확인
|
||||||
|
|
||||||
|
- **지원**: TV (`[Do Not Publish]`)
|
||||||
|
- **파라미터**:
|
||||||
|
- `type`: `"package"` (고정)
|
||||||
|
- `id`: 패키지 ID (예: `"amazon.alexa"`)
|
||||||
|
- **반환**: `needUpdate` (Boolean)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/getSupportedLanguages`
|
||||||
|
> 현재 지원하는 언어 목록 조회
|
||||||
|
|
||||||
|
- **지원**: TV (`[Public]`)
|
||||||
|
- **파라미터**:
|
||||||
|
- `languageCodes`: BCP-47 언어 코드 배열 (예: `["ko-KR", "en-US"]`)
|
||||||
|
- `voiceEngine`: `"stt"` (기본) 또는 `"nlp"`
|
||||||
|
- `showAll`: 엔진별 상세 언어 반환 여부
|
||||||
|
- `loadDefault`: 서버 없이 기본 언어 반환
|
||||||
|
- **반환**:
|
||||||
|
- `voiceLanguages`: `{ "ko-KR": "ko-KR", "xx-XX": "notSupported" }`
|
||||||
|
- `voiceEngines`: (showAll=true 시)
|
||||||
|
|
||||||
|
✅ **성공 응답**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"returnValue": true,
|
||||||
|
"voiceLanguages": {
|
||||||
|
"ko-KR": "ko-KR",
|
||||||
|
"en-US": "en-US",
|
||||||
|
"xx-XX": "notSupported"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/getUtteranceEvents`
|
||||||
|
> **`getVoiceUiEvents`와 동일** — 설명 생략
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/getVoiceKey`
|
||||||
|
> 서버 통신에 필요한 보안 키 반환
|
||||||
|
|
||||||
|
- **반환**: `vsn`, `staticVoiceKey`, `dynamicVoiceKey`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/getVoiceUiEvents`
|
||||||
|
> 음성 처리 중 UI 이벤트 수신 (Subscription 필수)
|
||||||
|
|
||||||
|
- **지원**: TV (`[Public]`)
|
||||||
|
- **이벤트 종류**:
|
||||||
|
- `registered`, `sttStart`, `sttVoiceLevel`, `sttPartialResult`, `sttResult`, `sttEnd`
|
||||||
|
- `nlpStart`, `nlpEnd`, `actionFeedback`, `sessionEnd`, `error`
|
||||||
|
|
||||||
|
✅ **이벤트 예시**:
|
||||||
|
```json
|
||||||
|
{ "event": "sttVoiceLevel", "level": 45, "subscribed": true }
|
||||||
|
{ "event": "actionFeedback", "feedback": { "systemUtterance": "{{1로}} 검색 결과입니다." } }
|
||||||
|
{ "event": "sessionEnd", "subscribed": false }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 In-App Control (`/interactor`)
|
||||||
|
|
||||||
|
### `/interactor/register`
|
||||||
|
> 앱을 음성 프레임워크에 등록 (Subscription 필수)
|
||||||
|
|
||||||
|
- **명령어**:
|
||||||
|
- `setContext`: 앱이 `setContext` 호출 필요
|
||||||
|
- `performAction`: 앱이 액션 수행 후 `reportActionResult` 호출
|
||||||
|
|
||||||
|
✅ **응답 예시**:
|
||||||
|
```json
|
||||||
|
{ "command": "setContext", "voiceTicket": "V00000006" }
|
||||||
|
{ "command": "performAction", "action": { "intent": "Select", "itemId": "test" } }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/interactor/reportActionResult`
|
||||||
|
> In-App 액션 처리 결과 보고
|
||||||
|
|
||||||
|
- **필수 파라미터**: `voiceTicket`, `result` (Boolean)
|
||||||
|
- **옵션**: `feedback` (`inAppFeedback`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/interactor/setContext`
|
||||||
|
> 앱이 처리 가능한 Intent 목록 등록
|
||||||
|
|
||||||
|
- **파라미터**: `voiceTicket`, `inAppIntents` (배열)
|
||||||
|
- **Intent 유형**: `Select`, `Scroll`, `PlayContent`, `Zoom` 등 9가지
|
||||||
|
|
||||||
|
✅ **예시**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"inAppIntents": [
|
||||||
|
{
|
||||||
|
"intent": "Select",
|
||||||
|
"items": [{ "itemId": "test", "value": ["test"] }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎤 음성 인식 API
|
||||||
|
|
||||||
|
### `/recognizeIntentByText`
|
||||||
|
> 텍스트 기반 Intent 분석 및 실행
|
||||||
|
|
||||||
|
- **지원**: TV (`[Public]`)
|
||||||
|
- **파라미터**: `text`, `language`, `runVoiceUi`, `inAppControl`, `source`
|
||||||
|
- **반환**: `serverResponse` (`responseCode`, `responseMessage`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/recognizeIntentByVoice`
|
||||||
|
> PCM 파일 기반 음성 Intent 분석 (**테스트 전용**)
|
||||||
|
|
||||||
|
- **지원**: TV (`[Private]`)
|
||||||
|
- **필수**: `pcmPath`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/recognizeVoice`
|
||||||
|
> 음성을 텍스트로 변환 (STT)
|
||||||
|
|
||||||
|
- **반환**: `text` (String 배열)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/recognizeVoiceWithDetails`
|
||||||
|
> STT 과정의 상세 이벤트 스트림 (Subscription 필수)
|
||||||
|
|
||||||
|
- **이벤트**: `sttVoiceLevel`, `sttPartialResult`, `sttResult`, `sessionEnd` 등
|
||||||
|
- **반환**: `event`, `text`, `level`, `feedback`, `voiceTicket`
|
||||||
|
|
||||||
|
✅ **응답 예시**:
|
||||||
|
```json
|
||||||
|
{ "event": "sttResult", "text": ["배트맨", "베트맨"], "voiceTicket": "V00000007" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/setInputEvent`
|
||||||
|
> 하드웨어/소프트웨어 버튼 이벤트 처리
|
||||||
|
|
||||||
|
- **inputType**: `"hold"` (down/up), `"wakeword"`
|
||||||
|
- **deviceId**: `"remote"`, `"amazonAlexa"` 등
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `/recordVoice` & `/stopRecordingVoice`
|
||||||
|
> 음성 데이터 스트리밍 녹음 시작/종료
|
||||||
|
|
||||||
|
- **recordVoice**: WebSocket URL 반환 (`websocketUrl`)
|
||||||
|
- **stopRecordingVoice**: 녹음 중지
|
||||||
|
|
||||||
|
✅ **recordVoice 응답**:
|
||||||
|
```json
|
||||||
|
{ "event": "recordingStart", "websocketUrl": "wss://..." }
|
||||||
|
{ "event": "recordingEnd" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 시스템 API
|
||||||
|
|
||||||
|
### `/system/checkUpdate`
|
||||||
|
> LG 자사 플러그인 기반 업데이트 확인
|
||||||
|
|
||||||
|
- **동작**: 필요한 경우 팝업 표시 후 음성 기능 중단
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❌ 오류 코드 참조
|
||||||
|
|
||||||
|
| 코드 | 텍스트 | 설명 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 100 | `bad params` | 파라미터 오류 |
|
||||||
|
| 101 | `deprecated api` | 더 이상 지원되지 않음 |
|
||||||
|
| 300 | `precondition not satisfied` | 네트워크/설정 미완료 |
|
||||||
|
| 301 | `internal processing error` | 내부 오류 (메모리 등) |
|
||||||
|
| 302 | `failed recognize voice` | STT 실패 |
|
||||||
|
| 303 | `failed recognize intent` | NLP 실패 |
|
||||||
|
| 304 | `unsupported language` | 언어 미지원 |
|
||||||
|
| 305 | `already processing another request` | 다른 요청 진행 중 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧱 객체 정의 (Objects)
|
||||||
|
|
||||||
|
### `actionFeedback`
|
||||||
|
- `systemUtterance`: 사용자에게 표시할 문구
|
||||||
|
- `exception`: 예외 유형 (예: `"alreadyCompleted"`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `serverResponse`
|
||||||
|
- `responseCode`: 처리 결과 코드
|
||||||
|
- `responseMessage`: 피드백 메시지
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `inAppAction`
|
||||||
|
- `type`: `"IntentMatch"`
|
||||||
|
- `intent`: `"Select"`, `"Scroll"`, `"Zoom"` 등
|
||||||
|
- `itemId`: 앱 내 고유 ID
|
||||||
|
- 추가 필드: `scroll`, `checked`, `state`, `control`, `zoom`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `inAppIntent`
|
||||||
|
- `intent`: 지원 Intent 유형
|
||||||
|
- `items`: 선택 가능한 항목 (`inAppIntentItem` 배열)
|
||||||
|
- `itemId`: 스크롤/줌 등 단일 액션용 ID
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `inAppIntentItem`
|
||||||
|
- `itemId`: 고유 ID
|
||||||
|
- `value`: STT 매칭 문구 배열
|
||||||
|
- `title`: UI 표시 문구
|
||||||
|
- `checked` / `state`: 체크박스/토글 상태
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `inAppFeedback`
|
||||||
|
- `voiceUi`: `actionFeedback` 객체
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `voiceEngines`
|
||||||
|
- 엔진별 언어 지원 정보:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"thinQtv": { "ko-KR": "ko-KR" },
|
||||||
|
"googleAssistant": { "ko-KR": "notSupported" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `voiceTask`
|
||||||
|
- `voiceTicket`: 세션 ID
|
||||||
|
- `status`: `"init"`, `"running"`, `"finished"`
|
||||||
|
- `taskType`: `"stt"`, `"fullVoiceNlp"` 등
|
||||||
|
- `voiceEngine`: 사용 중인 엔진 (예: `"googleAssistant"`)
|
||||||
|
- `recognitionSource`: 입력 소스 정보
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `voiceRecognitionSource`
|
||||||
|
- `input`: `"voiceinput"` 또는 `"text"`
|
||||||
|
- `type`: `"remote"`, `"mrcu"`, `"amazonEcho"` 등
|
||||||
|
- `key`: 소스 세부 키 (예: `"mrcu"`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> 📎 **관련 문서**: [LG Collab 문서](http://collab.lge.com/main/pages/viewpage.action?pageId=789627390)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
✅ 이 문서는 **webOS TV 음성 프레임워크 개발자**를 위한 전체 API 참조입니다.
|
||||||
|
🔒 `[Private]` 또는 `[Do Not Publish]`로 표시된 API는 외부 공개용이 아닙니다.
|
||||||
Reference in New Issue
Block a user