OpenAI Realtime Console
์ด OpenAI Realtime Console ํ๋ก์ ํธ๋ OpenAI Realtime API๋ฅผ ๊ฒ์ฌํ๊ณ ๋๋ฒ๊น ํ๋ ๋ฐ ๋์์ด ๋๋ ๋๊ตฌ์ ๋๋ค. ๋ํ Realtime API๋ฅผ ์ฌ์ฉํ๋ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ๋ ๋ฐ ๋์์ด ๋๋ ์ฐธ์กฐ ํด๋ผ์ด์ธํธ์ ์ค๋์ค ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
OpenAI Realtime Console์ OpenAI Realtime API๋ฅผ ์ํ ๊ฒ์ฌ๊ธฐ ๋ฐ ์ธํฐ๋ํฐ๋ธ API ์ฐธ๊ณ ์๋ฃ๋ก ์ค๊ณ๋์์ต๋๋ค. ์ด ํ๋ก์ ํธ๋ ๋ ๊ฐ์ ์ ํธ๋ฆฌํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจ๊ป ์ ๊ณต๋๋ฉฐ, ํ๋๋ ๋ธ๋ผ์ฐ์ ์ Node.js์ฉ ์ฐธ์กฐ ํด๋ผ์ด์ธํธ ์ญํ ์ ํ๋ openai/openai-realtime-api-beta์ด๊ณ , ๋ค๋ฅธ ํ๋๋ ๋ธ๋ผ์ฐ์ ์์ ๊ฐ๋จํ ์ค๋์ค ๊ด๋ฆฌ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ /src/lib/wavtools
์
๋๋ค.
์์ํ๊ธฐ
๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ํด๋ก ํ๊ณ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค.
$ git clone org-14957082@github.com:openai/openai-realtime-console.git
$ cd openai-realtime-console
$ npm i
์๋ฒ ์์ํ๊ธฐ
$ npm start
๋ธ๋ผ์ฐ์ ๋ฅผ ์ด๊ณ localhost:3000
์ ์ ์ํฉ๋๋ค.
์ฌ์ฉํ๊ธฐ
Realtime API์ ์ก์ธ์คํ ์ ์๋ OpenAI API ํค๊ฐ ํ์ํฉ๋๋ค. ์์ ์ ์
๋ ฅํ๋ผ๋ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค. ์ด ํค๋ localStorage
๋ฅผ ํตํด ์ ์ฅ๋๋ฉฐ, UI์์ ์ธ์ ๋ ์ง ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
์ธ์ ์ ์์ํ๋ ค๋ฉด ์ฐ๊ฒฐ(connect) ํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํด์๋ ๋ง์ดํฌ ์ก์ธ์ค๊ฐ ํ์ํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ manual(Push-to-talk) ๋ฐ vad(์์ฑ ํ๋ ๊ฐ์ง) ๋ํ ๋ชจ๋ ์ค ํ๋๋ฅผ ์ ํํ๊ณ ์ธ์ ๋ ์ง ์ ํํ ์ ์์ต๋๋ค.
๋ ๊ฐ์ง ๊ธฐ๋ฅ์ด ํ์ฑํ๋์ด ์์ต๋๋ค;
get_weather
: ์ด๋์๋ ๋ ์จ๋ฅผ ๋ฌผ์ด๋ณด๋ฉด ๋ชจ๋ธ์ด ์ต์ ์ ๋คํด ์์น๋ฅผ ํ์ ํ๊ณ ์ง๋์ ํ์ํ๋ฉฐ ํด๋น ์์น์ ๋ ์จ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์์น ์ก์ธ์ค ๊ถํ์ด ์์ผ๋ฉฐ ๋ชจ๋ธ์ ํ์ต ๋ฐ์ดํฐ์์ ์ขํ๋ฅผ '์ถ์ธก'ํ๋ฏ๋ก ์ ํ๋๊ฐ ์๋ฒฝํ์ง ์์ ์ ์๋ค๋ ์ ์ ์ ์ํ์ธ์.set_memory
: ๋ชจ๋ธ์ ์ ๋ณด๋ฅผ ๊ธฐ์ตํ๋๋ก ์์ฒญํ๋ฉด ๋ชจ๋ธ์ด ์ผ์ชฝ์ JSON ๋ธ๋กญ์ ์ ๋ณด๋ฅผ ์ ์ฅํฉ๋๋ค.
ํธ์ ํฌ ํ ํฌ(Push-to-talk) ๋๋ VAD ๋ชจ๋์์ ์ธ์ ๋ ์ง ๋ชจ๋ธ์ ์์ ๋กญ๊ฒ ์ค๋จํ ์ ์์ต๋๋ค.
๋ฆด๋ ์ด ์๋ฒ ์ฌ์ฉํ๊ธฐ
๋ณด๋ค ๊ฐ๋ ฅํ ๊ตฌํ์ ๊ตฌ์ถํ๊ณ ์์ฒด ์๋ฒ๋ฅผ ์ฌ์ฉํ์ฌ ์ฐธ์กฐ ํด๋ผ์ด์ธํธ๋ฅผ ์ฌ์ฉํด๋ณด๊ณ ์ถ๋ค๋ฉด Node.js ๋ฆด๋ ์ด ์๋ฒ๋ฅผ ํฌํจํ์ต๋๋ค.
$ npm run relay
localhost:8081
์์ ์๋์ผ๋ก ์์๋ฉ๋๋ค.
๋ค์ ๊ตฌ์ฑ์ผ๋ก .env
ํ์ผ์ ๋ง๋ค์ด์ผ ํฉ๋๋ค:
OPENAI_API_KEY=YOUR_API_KEY
REACT_APP_LOCAL_RELAY_SERVER_URL=http://localhost:8081
.env.
๋ณ๊ฒฝ ์ฌํญ์ ์ ์ฉํ๋ ค๋ฉด React ์ฑ๊ณผ ๋ฆด๋ ์ด ์๋ฒ๋ฅผ ๋ชจ๋ ์ฌ์์ํด์ผ ํฉ๋๋ค. ๋ก์ปฌ ์๋ฒ URL์ ConsolePage.tsx
๋ฅผ ํตํด ๋ก๋๋ฉ๋๋ค. ์ธ์ ๋ ์ง ๋ฆด๋ ์ด ์๋ฒ ์ฌ์ฉ์ ์ค์งํ๋ ค๋ฉด ํ๊ฒฝ ๋ณ์๋ฅผ ์ญ์ ํ๊ฑฐ๋ ๋น ๋ฌธ์์ด๋ก ์ค์ ํ๋ฉด ๋ฉ๋๋ค.
/**
* ๋ก์ปฌ ๋ฆด๋ ์ด ์๋ฒ๋ฅผ ์คํํ๋ฉด
* API ํค๋ฅผ ์จ๊ธฐ๊ณ ์๋ฒ์์ ์ฌ์ฉ์ ์ง์ ๋ก์ง์ ์คํํ ์ ์์ต๋๋ค.
*
* ๋ก์ปฌ ๋ฆด๋ ์ด ์๋ฒ ์ฃผ์๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํฉ๋๋ค:
* REACT_APP_LOCAL_RELAY_SERVER_URL=http://localhost:8081
*
* `.env` ํ์ผ์ OPENAI_API_KEY=๋ฅผ ์ค์ ํด์ผ ํฉ๋๋ค.
* `npm run relay`๋ฅผ `npm start`๊ณผ ๋ณ๋ ฌ๋ก ์คํํ ์ ์์ต๋๋ค.
*/
const LOCAL_RELAY_SERVER_URL: string =
process.env.REACT_APP_LOCAL_RELAY_SERVER_URL || '';
์ด ์๋ฒ๋ ๋จ์ ๋ฉ์์ง ์ค๊ณ ์ฉ๋๋ก๋ง ์ฌ์ฉ๋์ง๋ง ํ์ฅํ ์ ์์ต๋๋ค:
- ์จ๋ผ์ธ์ผ๋ก ํ๋ ์ดํ ์ฑ์ ์ถ์ํ๋ ค๋ ๊ฒฝ์ฐ API ์๊ฒฉ ์ฆ๋ช ์จ๊ธฐ๊ธฐ
- ์๋ฒ์์ ๋น๋ฐ๋ก ์ ์งํ๋ ค๋ ํน์ ํธ์ถ(์:
instructions
)์ ์ง์ ์ฒ๋ฆฌํฉ๋๋ค. - ํด๋ผ์ด์ธํธ๊ฐ ์์ ๋ฐ ์ ์กํ ์ ์๋ ์ด๋ฒคํธ ์ ํ ์ ํํ๊ธฐ
์ด๋ฌํ ๊ธฐ๋ฅ์ ์ง์ ๊ตฌํํด์ผ ํฉ๋๋ค.
์ค์๊ฐ API ๋ ํผ๋ฐ์ค ํด๋ผ์ด์ธํธ
์ต์ ๋ ํผ๋ฐ์ค ํด๋ผ์ด์ธํธ ๋ฐ ๋ฌธ์๋ GitHub์์ openai/openai-realtime-api-beta์์ ํ์ธํ ์ ์์ต๋๋ค.
์ด ํด๋ผ์ด์ธํธ๋ ๋ชจ๋ React(ํ๋ก ํธ์๋) ๋๋ Node.js ํ๋ก์ ํธ์์ ์ง์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ ์ฒด ๋ฌธ์๋ GitHub ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์ฐธ์กฐํ์ธ์. ํ์ง๋ง ์ฌ๊ธฐ ๊ฐ์ด๋๋ฅผ ์์ํ๊ธฐ ์ํ ์ ๋ฌธ์๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
import { RealtimeClient } from '/src/lib/realtime-api-beta/index.js';
const client = new RealtimeClient({ apiKey: process.env.OPENAI_API_KEY });
// ์ฐ๊ฒฐ์ ์์ ๋งค๊ฐ๋ณ์ ์ค์ ๊ฐ๋ฅ
client.updateSession({ instructions: 'You are a great, upbeat friend.' });
client.updateSession({ voice: 'alloy' });
client.updateSession({ turn_detection: 'server_vad' });
client.updateSession({ input_audio_transcription: { model: 'whisper-1' } });
// ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ค์
client.on('conversation.updated', ({ item, delta }) => {
const items = client.conversation.getItems(); // ์ด๊ฒ์ ์ฌ์ฉํ์ฌ ๋ชจ๋ ํญ๋ชฉ์ ๋ ๋๋งํ ์ ์์ต๋๋ค.
/* ๋ํ์ ๋ํ ๋ชจ๋ ๋ณ๊ฒฝ ์ฌํญ์ ํฌํจํ๋ฉฐ, ๋ธํ(delta)๊ฐ ์ฑ์์ง ์ ์์ต๋๋ค. */
});
// Realtime API์ ์ฐ๊ฒฐ
await client.connect();
// ์์ดํ
์ ์ก ๋ฐ ์์ฑ ํธ๋ฆฌ๊ฑฐ
client.sendUserMessageContent([{ type: 'text', text: `How are you?` }]);
์คํธ๋ฆฌ๋ฐ ์ค๋์ค ๋ณด๋ด๊ธฐ
์คํธ๋ฆฌ๋ฐ ์ค๋์ค๋ฅผ ๋ณด๋ด๋ ค๋ฉด .appendInputAudio()
๋ฉ์๋๋ฅผ ์ฌ์ฉํฉ๋๋ค. turn_detection: 'disabled'
๋ชจ๋์ธ ๊ฒฝ์ฐ .generate()
๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ธ์ ์๋ตํ๋๋ก ์ง์ํด์ผ ํฉ๋๋ค.
// ์ฌ์ฉ์ ์ค๋์ค ์ ์ก, Int16Array ๋๋ ArrayBuffer์ฌ์ผ ํฉ๋๋ค.
// ๊ธฐ๋ณธ ์ค๋์ค ํฌ๋งท์ 24,000Hz ์ํ ์๋์ pcm16์
๋๋ค.
// 0.1์ด ๋จ์๋ก 1์ด ๋ถ๋์ ๋
ธ์ด์ฆ๋ฅผ ์ฑ์๋๋ค.
for (let i = 0; i < 10; i++) {
const data = new Int16Array(2400);
for (let n = 0; n < 2400; n++) {
const value = Math.floor((Math.random() * 2 - 1) * 0x8000);
data[n] = value;
}
client.appendInputAudio(data);
}
// ๋ณด๋ฅ ์ค์ธ ์ค๋์ค๊ฐ ์ปค๋ฐ๋๊ณ ๋ชจ๋ธ์ ๋ค์์ ์์ฑํ๋๋ก ์์ฒญํฉ๋๋ค.
client.createResponse();
๋๊ตฌ ์ถ๊ฐ ๋ฐ ์ฌ์ฉ
๋๊ตฌ ์์
์ ๊ฐ๋จํฉ๋๋ค. .addTool()
์ ํธ์ถํ๊ณ ์ฝ๋ฐฑ์ ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ์ค์ ํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค. ์ฝ๋ฐฑ์ ๋๊ตฌ์ ๋งค๊ฐ๋ณ์์ ํจ๊ป ์คํ๋๊ณ ๊ฒฐ๊ณผ๋ ์๋์ผ๋ก ๋ชจ๋ธ๋ก ๋ค์ ์ ์ก๋ฉ๋๋ค.
// ์ฝ๋ฐฑ์ ์ง์ ํ์ฌ ๋๊ตฌ๋ฅผ ์ถ๊ฐํ ์๋ ์์ต๋๋ค.
client.addTool(
{
name: 'get_weather',
description:
'Retrieves the weather for a given lat, lng coordinate pair. Specify a label for the location.',
parameters: {
type: 'object',
properties: {
lat: {
type: 'number',
description: 'Latitude',
},
lng: {
type: 'number',
description: 'Longitude',
},
location: {
type: 'string',
description: 'Name of the location',
},
},
required: ['lat', 'lng', 'location'],
},
},
async ({ lat, lng, location }) => {
const result = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lng}¤t=temperature_2m,wind_speed_10m`
);
const json = await result.json();
return json;
}
);
๋ชจ๋ธ ์ค๋จํ๊ธฐ
ํนํ turn_detection: 'disabled'
๋ชจ๋์์ ๋ชจ๋ธ์ ์๋์ผ๋ก ์ค๋จํ๊ณ ์ถ์ ์ ์์ต๋๋ค. ์ด๋ฅผ ์ํด ๋ค์์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
// id๋ ํ์ฌ ์์ฑ ์ค์ธ ํญ๋ชฉ์ ID์
๋๋ค.
// sampleCount๋ ์ฒญ์ทจ์๊ฐ ๋ค์ ์ค๋์ค ์ํ์ ์์
๋๋ค.
client.cancelResponse(id, sampleCount);
์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋ชจ๋ธ์ด ์ฆ์ ์์ฑ์ ์ค๋จํ ๋ฟ๋ง ์๋๋ผsampleCount
์ดํ์ ๋ชจ๋ ์ค๋์ค๋ฅผ ์ ๊ฑฐํ๊ณ ํ
์คํธ ์๋ต์ ์ง์์ผ๋ก์จ ์ฌ์ ์ค์ธ ํญ๋ชฉ๋ ์๋ฆฝ๋๋ค. ์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋ชจ๋ธ์ ์ค๋จํ๊ณ ์ฌ์ฉ์ ์ํ๋ณด๋ค ์์ ์์ฑ๋ ๋ชจ๋ ๊ฒ์ '๊ธฐ์ต'ํ์ง ๋ชปํ๊ฒ ํ ์ ์์ต๋๋ค.
์ฐธ์กฐ ํด๋ผ์ด์ธํธ ์ด๋ฒคํธ
RealtimeClient
์๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์ด ํ๋ฆ์ ์ํ ๋ค์ฏ ๊ฐ์ง ์ฃผ์ ํด๋ผ์ด์ธํธ ์ด๋ฒคํธ๊ฐ ์์ต๋๋ค. ์ด๊ฒ์ ํด๋ผ์ด์ธํธ ์ฌ์ฉ์ ๋ํ ๊ฐ์์ผ ๋ฟ์ด๋ฉฐ, ์ ์ฒด ์ค์๊ฐ API ์ด๋ฒคํธ ์ฌ์์ ์๋นํ ๋ฐฉ๋ํ๋ฏ๋ก ๋ ๋ง์ ์ ์ด๊ฐ ํ์ํ ๊ฒฝ์ฐ GitHub ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ํ์ธํ์ธ์: openai/openai-realtime-api-beta.
// ์ฐ๊ฒฐ ์คํจ์ ๊ฐ์ ์ค๋ฅ
client.on('error', (event) => {
/* do something */
});
// VAD ๋ชจ๋์์ ์ฌ์ฉ์๊ฐ ๋งํ๊ธฐ ์์ํฉ๋๋ค.
// ํ์ํ ๊ฒฝ์ฐ ์ด๋ฅผ ์ฌ์ฉํ์ฌ ์ด์ ์๋ต์ ์ค๋์ค ์ฌ์์ ์ค์งํ ์ ์์ต๋๋ค.
client.on('conversation.interrupted', () => {
/* do something */
});
// ๋ํ์ ๋ํ ๋ชจ๋ ๋ณ๊ฒฝ ์ฌํญ์ ํฌํจํฉ๋๋ค.
// ๋ธํ(delta)๊ฐ ์ฑ์์ง ์ ์์ต๋๋ค.
client.on('conversation.updated', ({ item, delta }) => {
// get all items, e.g. if you need to update a chat window
const items = client.conversation.getItems();
switch (item.type) {
case 'message':
// system, user, or assistant message (item.role)
break;
case 'function_call':
// always a function call from the model
break;
case 'function_call_output':
// always a response from the user / application
break;
}
if (delta) {
// ํน์ ์ด๋ฒคํธ์ ๋ํด ๋ค์ ์ค ํ๋๋ง ์ฑ์์ง๋๋ค.
// delta.audio = Int16Array, audio added
// delta.transcript = string, transcript added
// delta.arguments = string, function arguments added
}
});
// ๋ํ์ ํญ๋ชฉ์ด ์ถ๊ฐ๋ ํ์๋ง ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค.
client.on('conversation.item.appended', ({ item }) => {
/* item ์ํ๋ 'in_progress' ๋๋ 'completed' ์ผ ์ ์์ต๋๋ค. */
});
// ๋ํ์์ ํญ๋ชฉ์ด ์๋ฃ๋ ํ์๋ง ํธ๋ฆฌ๊ฑฐ๋จ
// ํญ์ conversation.item.appended ์ดํ์ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค.
client.on('conversation.item.completed', ({ item }) => {
/* item ์ํ๋ ํญ์ 'completed'์
๋๋ค. */
});
Wavtools
Wavtools์๋ ๋ธ๋ผ์ฐ์ ์์ PCM16 ์ค๋์ค ์คํธ๋ฆผ์ ์ฝ๊ฒ ๊ด๋ฆฌํ๊ณ ๋ น์ ๋ฐ ์ฌ์ํ ์ ์๋ ๊ธฐ๋ฅ์ด ํฌํจ๋์ด ์์ต๋๋ค.
WavRecorder ํต์คํํธ
import { WavRecorder } from '/src/lib/wavtools/index.js';
const wavRecorder = new WavRecorder({ sampleRate: 24000 });
wavRecorder.getStatus(); // "ended"
// ๊ถํ ์์ฒญ, ๋ง์ดํฌ ์ฐ๊ฒฐ
await wavRecorder.begin();
wavRecorder.getStatus(); // "paused"
// ๋
น์ ์์
// ์ด ์ฝ๋ฐฑ์ ๊ธฐ๋ณธ์ ์ผ๋ก 8192๊ฐ์ ์ํ ์ฒญํฌ๋ก ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค.
// { mono, raw }๋ Int16Array(PCM16) ๋ชจ๋
ธ ๋ฐ ์ ์ฒด ์ฑ๋ ๋ฐ์ดํฐ์
๋๋ค.
await wavRecorder.record((data) => {
const { mono, raw } = data;
});
wavRecorder.getStatus(); // "recording"
// ๋
น์ ์ค์ง
await wavRecorder.pause();
wavRecorder.getStatus(); // "paused"
// "audio/wav" ์ค๋์ค ํ์ผ ์ถ๋ ฅ
const audio = await wavRecorder.save();
// ํ์ฌ ์ค๋์ค ๋ฒํผ๋ฅผ ์ง์ฐ๊ณ ๋
น์์ ์์ํฉ๋๋ค.
await wavRecorder.clear();
await wavRecorder.record();
// ์๊ฐํ๋ฅผ ์ํ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
const frequencyData = wavRecorder.getFrequencies();
// ๋
น์ ์ค์ง, ๋ง์ดํฌ, ์ถ๋ ฅ ํ์ผ ์ฐ๊ฒฐ ํด์
await wavRecorder.pause();
const finalAudio = await wavRecorder.end();
// ์ฅ์น ๋ณ๊ฒฝ์ ์์ ํฉ๋๋ค(์: ๋๊ตฐ๊ฐ ๋ง์ดํฌ๋ฅผ ๋ถ๋ฆฌํ๋ ๊ฒฝ์ฐ)
// deviceList๋ MediaDeviceInfo[] + `default` ์์ฑ์ ๋ฐฐ์ด์
๋๋ค.
wavRecorder.listenForDeviceChange((deviceList) => {});
WavStreamPlayer ํต์คํํธ
import { WavStreamPlayer } from '/src/lib/wavtools/index.js';
const wavStreamPlayer = new WavStreamPlayer({ sampleRate: 24000 });
// ์ค๋์ค ์ถ๋ ฅ์ ์ฐ๊ฒฐ
await wavStreamPlayer.connect();
// 1์ด ๋ถ๋์ ๋น PCM16 ์ค๋์ค ์์ฑ
const audio = new Int16Array(24000);
// 3์ด ๋ถ๋์ ์ค๋์ค๋ฅผ ๋๊ธฐ์ด์ ์ถ๊ฐํ๋ฉด ์ฆ์ ์ฌ์์ด ์์๋ฉ๋๋ค.
wavStreamPlayer.add16BitPCM(audio, 'my-track');
wavStreamPlayer.add16BitPCM(audio, 'my-track');
wavStreamPlayer.add16BitPCM(audio, 'my-track');
// ์๊ฐํ๋ฅผ ์ํ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
const frequencyData = wavStreamPlayer.getFrequencies();
// ์ธ์ ๋ ์ง ์ค๋์ค๋ฅผ ์ค๋จ(์ฌ์ ์ค์ง)ํ ์ ์์ต๋๋ค.
// ๋ค์ ์์ํ๋ ค๋ฉด .add16BitPCM()์ ๋ค์ ํธ์ถํด์ผ ํฉ๋๋ค.
const trackOffset = await wavStreamPlayer.interrupt();
trackOffset.trackId; // "my-track"
trackOffset.offset; // sample number
trackOffset.currentTime; // time in track