@tanstack/ai-gemini 0.19.1 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/adapters/audio.d.ts +1 -1
- package/dist/esm/adapters/audio.js.map +1 -1
- package/dist/esm/adapters/image.d.ts +1 -1
- package/dist/esm/adapters/image.js +17 -39
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/summarize.d.ts +1 -1
- package/dist/esm/adapters/summarize.js.map +1 -1
- package/dist/esm/adapters/text.d.ts +1 -1
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/tts.d.ts +1 -1
- package/dist/esm/adapters/tts.js.map +1 -1
- package/dist/esm/adapters/video.d.ts +60 -11
- package/dist/esm/adapters/video.js +205 -6
- package/dist/esm/adapters/video.js.map +1 -1
- package/dist/esm/experimental/text-interactions/adapter.d.ts +1 -1
- package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
- package/dist/esm/index.d.ts +6 -3
- package/dist/esm/index.js +9 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/model-meta.d.ts +11 -3
- package/dist/esm/model-meta.js +9 -1
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/realtime/adapter.d.ts +22 -0
- package/dist/esm/realtime/adapter.js +233 -0
- package/dist/esm/realtime/adapter.js.map +1 -0
- package/dist/esm/realtime/client.d.ts +98 -0
- package/dist/esm/realtime/client.js +389 -0
- package/dist/esm/realtime/client.js.map +1 -0
- package/dist/esm/realtime/index.d.ts +3 -0
- package/dist/esm/realtime/token.d.ts +26 -0
- package/dist/esm/realtime/token.js +39 -0
- package/dist/esm/realtime/token.js.map +1 -0
- package/dist/esm/realtime/types.d.ts +51 -0
- package/dist/esm/realtime/utils.d.ts +40 -0
- package/dist/esm/realtime/utils.js +350 -0
- package/dist/esm/realtime/utils.js.map +1 -0
- package/dist/esm/video/video-provider-options.d.ts +59 -14
- package/dist/esm/video/video-provider-options.js +15 -2
- package/dist/esm/video/video-provider-options.js.map +1 -1
- package/package.json +4 -4
- package/src/adapters/audio.ts +1 -1
- package/src/adapters/image.ts +25 -49
- package/src/adapters/summarize.ts +1 -1
- package/src/adapters/text.ts +1 -1
- package/src/adapters/tts.ts +1 -1
- package/src/adapters/video.ts +333 -16
- package/src/experimental/text-interactions/adapter.ts +2 -2
- package/src/index.ts +20 -2
- package/src/model-meta.ts +45 -2
- package/src/realtime/adapter.ts +311 -0
- package/src/realtime/client.ts +547 -0
- package/src/realtime/index.ts +14 -0
- package/src/realtime/token.ts +70 -0
- package/src/realtime/types.ts +94 -0
- package/src/realtime/utils.ts +439 -0
- package/src/video/video-provider-options.ts +95 -15
package/src/index.ts
CHANGED
|
@@ -61,9 +61,9 @@ export {
|
|
|
61
61
|
type GeminiAudioProviderOptions,
|
|
62
62
|
} from './adapters/audio'
|
|
63
63
|
|
|
64
|
-
// Video
|
|
64
|
+
// Video generation adapter — Veo + Gemini Omni Flash (experimental)
|
|
65
65
|
/**
|
|
66
|
-
* @experimental
|
|
66
|
+
* @experimental Video generation is an experimental feature and may change.
|
|
67
67
|
*/
|
|
68
68
|
export {
|
|
69
69
|
GeminiVideoAdapter,
|
|
@@ -74,8 +74,11 @@ export {
|
|
|
74
74
|
export {
|
|
75
75
|
GEMINI_VIDEO_DURATIONS,
|
|
76
76
|
getGeminiVideoDurationOptions,
|
|
77
|
+
isInteractionsVideoModel,
|
|
77
78
|
} from './video/video-provider-options'
|
|
78
79
|
export type {
|
|
80
|
+
GeminiInteractionsVideoModel,
|
|
81
|
+
GeminiOmniVideoProviderOptions,
|
|
79
82
|
GeminiVideoModel,
|
|
80
83
|
GeminiVideoModelDurationByName,
|
|
81
84
|
GeminiVideoModelInputModalitiesByName,
|
|
@@ -96,6 +99,7 @@ export { GEMINI_TTS_MODELS as GeminiTTSModels } from './model-meta'
|
|
|
96
99
|
export { GEMINI_TTS_VOICES as GeminiTTSVoices } from './model-meta'
|
|
97
100
|
export { GEMINI_AUDIO_MODELS as GeminiAudioModels } from './model-meta'
|
|
98
101
|
export { GEMINI_VIDEO_MODELS as GeminiVideoModels } from './model-meta'
|
|
102
|
+
export { GEMINI_INTERACTIONS_VIDEO_MODELS as GeminiInteractionsVideoModels } from './model-meta'
|
|
99
103
|
export type { GeminiModels as GeminiTextModel } from './model-meta'
|
|
100
104
|
export type { GeminiImageModels as GeminiImageModel } from './model-meta'
|
|
101
105
|
export type { GeminiTTSVoice } from './model-meta'
|
|
@@ -125,3 +129,17 @@ export type {
|
|
|
125
129
|
|
|
126
130
|
// Export provider usage types
|
|
127
131
|
export type { GeminiProviderUsageDetails } from './usage'
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Realtime (Voice) Adapters
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
export { geminiRealtime, geminiRealtimeToken } from './realtime/index'
|
|
138
|
+
|
|
139
|
+
export type {
|
|
140
|
+
GeminiRealtimeModel,
|
|
141
|
+
GeminiRealtimeOptions,
|
|
142
|
+
GeminiRealtimeProviderOptions,
|
|
143
|
+
GeminiRealtimeTokenOptions,
|
|
144
|
+
GeminiRealtimeVoice,
|
|
145
|
+
} from './realtime/index'
|
package/src/model-meta.ts
CHANGED
|
@@ -712,6 +712,37 @@ const VEO_3_1_LITE_PREVIEW = {
|
|
|
712
712
|
GeminiCachedContentOptions
|
|
713
713
|
>
|
|
714
714
|
|
|
715
|
+
/**
|
|
716
|
+
* Gemini Omni Flash — multimodal video generation with conversational
|
|
717
|
+
* editing. Serves only the Interactions API (`generateContent` rejects it),
|
|
718
|
+
* so it routes through the interactions-based path of the video adapter,
|
|
719
|
+
* not Veo's `:predictLongRunning` flow. Pricing is per second of generated
|
|
720
|
+
* video ($0.10/sec). 720p / 24 FPS, 3–10 second clips (default 10s).
|
|
721
|
+
* @experimental Omni video generation is an experimental feature and may change.
|
|
722
|
+
*/
|
|
723
|
+
const GEMINI_OMNI_FLASH_PREVIEW = {
|
|
724
|
+
name: 'gemini-omni-flash-preview',
|
|
725
|
+
max_input_tokens: 1_048_576,
|
|
726
|
+
max_output_tokens: 1,
|
|
727
|
+
supports: {
|
|
728
|
+
input: ['text', 'image', 'video'],
|
|
729
|
+
output: ['video', 'audio'],
|
|
730
|
+
},
|
|
731
|
+
pricing: {
|
|
732
|
+
input: {
|
|
733
|
+
normal: 0,
|
|
734
|
+
},
|
|
735
|
+
output: {
|
|
736
|
+
normal: 0.1,
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
} as const satisfies ModelMeta<
|
|
740
|
+
GeminiToolConfigOptions &
|
|
741
|
+
GeminiSafetyOptions &
|
|
742
|
+
GeminiCommonConfigOptions &
|
|
743
|
+
GeminiCachedContentOptions
|
|
744
|
+
>
|
|
745
|
+
|
|
715
746
|
const GEMINI_3_5_FLASH = {
|
|
716
747
|
name: 'gemini-3.5-flash',
|
|
717
748
|
max_input_tokens: 1_048_576,
|
|
@@ -845,13 +876,25 @@ export const GEMINI_TTS_VOICES = [
|
|
|
845
876
|
export type GeminiTTSVoice = (typeof GEMINI_TTS_VOICES)[number]
|
|
846
877
|
|
|
847
878
|
/**
|
|
848
|
-
*
|
|
849
|
-
*
|
|
879
|
+
* Video generation models. Veo models run on the long-running
|
|
880
|
+
* `:predictLongRunning` flow; Gemini Omni Flash runs on the Interactions
|
|
881
|
+
* API — the video adapter routes by model.
|
|
882
|
+
* @experimental Video generation is an experimental feature and may change.
|
|
850
883
|
*/
|
|
851
884
|
export const GEMINI_VIDEO_MODELS = [
|
|
852
885
|
VEO_3_1_PREVIEW.name,
|
|
853
886
|
VEO_3_1_FAST_PREVIEW.name,
|
|
854
887
|
VEO_3_1_LITE_PREVIEW.name,
|
|
888
|
+
GEMINI_OMNI_FLASH_PREVIEW.name,
|
|
889
|
+
] as const
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Video models served by the Interactions API rather than Veo's
|
|
893
|
+
* `:predictLongRunning` operations flow.
|
|
894
|
+
* @experimental Omni video generation is an experimental feature and may change.
|
|
895
|
+
*/
|
|
896
|
+
export const GEMINI_INTERACTIONS_VIDEO_MODELS = [
|
|
897
|
+
GEMINI_OMNI_FLASH_PREVIEW.name,
|
|
855
898
|
] as const
|
|
856
899
|
|
|
857
900
|
// Manual type map for per-model provider options
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { createRealtimeEventEmitter } from '@tanstack/ai'
|
|
2
|
+
import { AudioPlayer, AudioStreamer, base64ToArrayBuffer } from './utils'
|
|
3
|
+
import { GeminiLiveClient } from './client'
|
|
4
|
+
import type { LiveResponse } from './client'
|
|
5
|
+
import type {
|
|
6
|
+
AnyClientTool,
|
|
7
|
+
AudioVisualization,
|
|
8
|
+
RealtimeAdapter,
|
|
9
|
+
RealtimeConnection,
|
|
10
|
+
RealtimeMessage,
|
|
11
|
+
RealtimeMode,
|
|
12
|
+
RealtimeToken,
|
|
13
|
+
} from '@tanstack/ai'
|
|
14
|
+
import type { GeminiRealtimeModel, GeminiRealtimeOptions } from './types'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a Gemini realtime adapter for client-side use.
|
|
18
|
+
*
|
|
19
|
+
* @param options - Optional configuration
|
|
20
|
+
* @returns A RealtimeAdapter for use with RealtimeClient
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { RealtimeClient } from '@tanstack/ai-client'
|
|
25
|
+
* import { geminiRealtime } from '@tanstack/ai-gemini'
|
|
26
|
+
*
|
|
27
|
+
* const client = new RealtimeClient({
|
|
28
|
+
* getToken: () => fetch('/api/realtime-token').then(r => r.json()),
|
|
29
|
+
* adapter: geminiRealtime(),
|
|
30
|
+
* onGoAway: () => client.updateSession({ ... }) // Resume session with new config (available only for Gemini Live adapter)
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function geminiRealtime(
|
|
36
|
+
options: GeminiRealtimeOptions = {},
|
|
37
|
+
): RealtimeAdapter {
|
|
38
|
+
return {
|
|
39
|
+
provider: 'gemini',
|
|
40
|
+
|
|
41
|
+
connect(
|
|
42
|
+
token: RealtimeToken,
|
|
43
|
+
clientTools?: ReadonlyArray<AnyClientTool>,
|
|
44
|
+
): Promise<RealtimeConnection> {
|
|
45
|
+
return createWebSocketConnection(token, options.model, clientTools)
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates a WebSocket connection to Gemini's realtime API
|
|
52
|
+
*/
|
|
53
|
+
async function createWebSocketConnection(
|
|
54
|
+
token: RealtimeToken,
|
|
55
|
+
model: GeminiRealtimeModel = 'gemini-3.1-flash-live-preview',
|
|
56
|
+
tools?: ReadonlyArray<AnyClientTool>,
|
|
57
|
+
): Promise<RealtimeConnection> {
|
|
58
|
+
const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter()
|
|
59
|
+
|
|
60
|
+
// Current state
|
|
61
|
+
let currentMode: RealtimeMode = 'idle'
|
|
62
|
+
let currentMessageId: string | null = null
|
|
63
|
+
let messageIdCounter = 0
|
|
64
|
+
|
|
65
|
+
function generateMessageId(): string {
|
|
66
|
+
return `gemini-msg-${Date.now()}-${++messageIdCounter}`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const client = new GeminiLiveClient(token.token, model, tools)
|
|
70
|
+
|
|
71
|
+
let message: RealtimeMessage = {
|
|
72
|
+
id: '',
|
|
73
|
+
role: 'assistant',
|
|
74
|
+
timestamp: 0,
|
|
75
|
+
parts: [],
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let pendingAssistantResponse = ''
|
|
79
|
+
|
|
80
|
+
client.onClose = () => {
|
|
81
|
+
emit('status_change', { status: 'idle' })
|
|
82
|
+
emit('mode_change', { mode: 'idle' })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
client.onError = (error) => {
|
|
86
|
+
emit('error', { error })
|
|
87
|
+
emit('status_change', { status: 'error' })
|
|
88
|
+
emit('mode_change', { mode: 'idle' })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
client.onReceiveResponse = (response: LiveResponse) => {
|
|
92
|
+
switch (response.type) {
|
|
93
|
+
case 'text':
|
|
94
|
+
message.parts.push({
|
|
95
|
+
type: 'text',
|
|
96
|
+
content: response.data,
|
|
97
|
+
})
|
|
98
|
+
break
|
|
99
|
+
case 'audio': {
|
|
100
|
+
// Decode once, then reuse for both the stored message part and playback.
|
|
101
|
+
const pcm = base64ToArrayBuffer(response.data.audioData)
|
|
102
|
+
message.parts.push({
|
|
103
|
+
type: 'audio',
|
|
104
|
+
transcript: response.data.transcript,
|
|
105
|
+
audioData: pcm,
|
|
106
|
+
})
|
|
107
|
+
if (currentMode !== 'speaking') {
|
|
108
|
+
currentMode = 'speaking'
|
|
109
|
+
emit('mode_change', { mode: 'speaking' })
|
|
110
|
+
}
|
|
111
|
+
audioPlayer.play(pcm).catch((error) => emit('error', { error }))
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
case 'go_away':
|
|
115
|
+
emit('go_away', { timeLeft: response.data.timeLeft })
|
|
116
|
+
break
|
|
117
|
+
case 'usage_metadata':
|
|
118
|
+
emit('usage', {
|
|
119
|
+
completionTokens: response.data.responseTokenCount ?? 0,
|
|
120
|
+
promptTokens: response.data.promptTokenCount ?? 0,
|
|
121
|
+
totalTokens: response.data.totalTokenCount ?? 0,
|
|
122
|
+
})
|
|
123
|
+
break
|
|
124
|
+
case 'input_transcription':
|
|
125
|
+
if (response.data.finished && currentMode !== 'thinking') {
|
|
126
|
+
currentMode = 'thinking'
|
|
127
|
+
emit('mode_change', { mode: 'thinking' })
|
|
128
|
+
}
|
|
129
|
+
emit('transcript', {
|
|
130
|
+
isFinal: response.data.finished,
|
|
131
|
+
transcript: response.data.text,
|
|
132
|
+
role: 'user',
|
|
133
|
+
})
|
|
134
|
+
break
|
|
135
|
+
case 'output_transcription':
|
|
136
|
+
pendingAssistantResponse += response.data.text
|
|
137
|
+
emit('transcript', {
|
|
138
|
+
isFinal: response.data.finished,
|
|
139
|
+
transcript: pendingAssistantResponse,
|
|
140
|
+
role: 'assistant',
|
|
141
|
+
})
|
|
142
|
+
break
|
|
143
|
+
case 'interrupted':
|
|
144
|
+
audioPlayer.interrupt()
|
|
145
|
+
currentMode = 'listening'
|
|
146
|
+
emit('mode_change', { mode: 'listening' })
|
|
147
|
+
emit('interrupted', { messageId: currentMessageId ?? undefined })
|
|
148
|
+
break
|
|
149
|
+
case 'tool_call':
|
|
150
|
+
for (const tool of response.data.functionCalls || []) {
|
|
151
|
+
if (tool.id && tool.name) {
|
|
152
|
+
emit('tool_call', {
|
|
153
|
+
toolCallId: tool.id,
|
|
154
|
+
input: tool.args,
|
|
155
|
+
toolName: tool.name,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
break
|
|
160
|
+
case 'turn_complete':
|
|
161
|
+
currentMessageId = generateMessageId()
|
|
162
|
+
message.id = currentMessageId
|
|
163
|
+
message.timestamp = Date.now()
|
|
164
|
+
message.parts.push({
|
|
165
|
+
type: 'text',
|
|
166
|
+
content: pendingAssistantResponse,
|
|
167
|
+
})
|
|
168
|
+
emit('message_complete', { message })
|
|
169
|
+
|
|
170
|
+
pendingAssistantResponse = ''
|
|
171
|
+
message = {
|
|
172
|
+
id: '',
|
|
173
|
+
role: 'assistant',
|
|
174
|
+
timestamp: 0,
|
|
175
|
+
parts: [],
|
|
176
|
+
}
|
|
177
|
+
currentMode = 'listening'
|
|
178
|
+
emit('mode_change', { mode: 'listening' })
|
|
179
|
+
break
|
|
180
|
+
case 'setup_complete':
|
|
181
|
+
emit('status_change', { status: 'connected' })
|
|
182
|
+
break
|
|
183
|
+
case 'error':
|
|
184
|
+
emit('error', {
|
|
185
|
+
error: new Error(response.data),
|
|
186
|
+
})
|
|
187
|
+
break
|
|
188
|
+
case 'thought':
|
|
189
|
+
case 'session_resumption_update':
|
|
190
|
+
// Handled internally by GeminiLiveClient; not surfaced to the client.
|
|
191
|
+
break
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await client.connect()
|
|
196
|
+
|
|
197
|
+
const audioStreamer = new AudioStreamer(client)
|
|
198
|
+
const audioPlayer = new AudioPlayer()
|
|
199
|
+
try {
|
|
200
|
+
await audioPlayer.init()
|
|
201
|
+
await audioStreamer.start()
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// Tear down the socket + audio graph if mic/worklet setup fails (e.g. the
|
|
204
|
+
// user denied microphone access) rather than leaking an open connection.
|
|
205
|
+
audioStreamer.stop()
|
|
206
|
+
audioPlayer.destroy()
|
|
207
|
+
client.disconnect()
|
|
208
|
+
throw error
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const connection: RealtimeConnection = {
|
|
212
|
+
async disconnect() {
|
|
213
|
+
audioStreamer.stop()
|
|
214
|
+
audioPlayer.destroy()
|
|
215
|
+
client.disconnect()
|
|
216
|
+
currentMode = 'idle'
|
|
217
|
+
emit('status_change', { status: 'idle' })
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
async startAudioCapture() {
|
|
221
|
+
// Audio capture is established during connection setup
|
|
222
|
+
audioStreamer.startAudioCapture()
|
|
223
|
+
currentMode = 'listening'
|
|
224
|
+
emit('mode_change', { mode: 'listening' })
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
stopAudioCapture() {
|
|
228
|
+
audioStreamer.stopAudioCapture()
|
|
229
|
+
currentMode = 'idle'
|
|
230
|
+
emit('mode_change', { mode: 'idle' })
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
sendText(text: string) {
|
|
234
|
+
client.sendTextMessage(text)
|
|
235
|
+
currentMode = 'thinking'
|
|
236
|
+
emit('mode_change', { mode: 'thinking' })
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
sendImage(imageData: string, mimeType: string) {
|
|
240
|
+
client.sendImageMessage(imageData, mimeType)
|
|
241
|
+
currentMode = 'thinking'
|
|
242
|
+
emit('mode_change', { mode: 'thinking' })
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
sendToolResult(callId: string, result: string) {
|
|
246
|
+
client.sendToolResponse([
|
|
247
|
+
{
|
|
248
|
+
id: callId,
|
|
249
|
+
response: {
|
|
250
|
+
result,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
])
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
updateSession(config) {
|
|
257
|
+
client.updateSession(config).catch((error) => emit('error', { error }))
|
|
258
|
+
emit('status_change', { status: 'reconnecting' })
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
updateToken(newToken) {
|
|
262
|
+
client.updateToken(newToken)
|
|
263
|
+
emit('status_change', { status: 'reconnecting' })
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
interrupt() {
|
|
267
|
+
audioPlayer.interrupt()
|
|
268
|
+
currentMode = 'listening'
|
|
269
|
+
emit('mode_change', { mode: 'listening' })
|
|
270
|
+
emit('interrupted', { messageId: currentMessageId ?? undefined })
|
|
271
|
+
},
|
|
272
|
+
on: realtimeEventEmitterOn,
|
|
273
|
+
getAudioVisualization(): AudioVisualization {
|
|
274
|
+
return {
|
|
275
|
+
get inputLevel() {
|
|
276
|
+
return audioStreamer.inputLevel
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
get outputLevel() {
|
|
280
|
+
return audioPlayer.outputLevel
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
getInputFrequencyData() {
|
|
284
|
+
return audioStreamer.inputFrequencyData
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
getOutputFrequencyData() {
|
|
288
|
+
return audioPlayer.outputFrequencyData
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
getInputTimeDomainData() {
|
|
292
|
+
return audioStreamer.inputTimeDomainData
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
getOutputTimeDomainData() {
|
|
296
|
+
return audioPlayer.outputTimeDomainData
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
get inputSampleRate() {
|
|
300
|
+
return audioStreamer.inputSampleRate
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
get outputSampleRate() {
|
|
304
|
+
return audioPlayer.outputSampleRate
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return connection
|
|
311
|
+
}
|