agent-starter-pack 0.0.1b0__py3-none-any.whl
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.
Potentially problematic release.
This version of agent-starter-pack might be problematic. Click here for more details.
- agent_starter_pack-0.0.1b0.dist-info/METADATA +143 -0
- agent_starter_pack-0.0.1b0.dist-info/RECORD +162 -0
- agent_starter_pack-0.0.1b0.dist-info/WHEEL +4 -0
- agent_starter_pack-0.0.1b0.dist-info/entry_points.txt +2 -0
- agent_starter_pack-0.0.1b0.dist-info/licenses/LICENSE +201 -0
- agents/agentic_rag_vertexai_search/README.md +22 -0
- agents/agentic_rag_vertexai_search/app/agent.py +145 -0
- agents/agentic_rag_vertexai_search/app/retrievers.py +79 -0
- agents/agentic_rag_vertexai_search/app/templates.py +53 -0
- agents/agentic_rag_vertexai_search/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
- agents/agentic_rag_vertexai_search/template/.templateconfig.yaml +14 -0
- agents/agentic_rag_vertexai_search/tests/integration/test_agent.py +57 -0
- agents/crewai_coding_crew/README.md +34 -0
- agents/crewai_coding_crew/app/agent.py +86 -0
- agents/crewai_coding_crew/app/crew/config/agents.yaml +39 -0
- agents/crewai_coding_crew/app/crew/config/tasks.yaml +37 -0
- agents/crewai_coding_crew/app/crew/crew.py +71 -0
- agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +1571 -0
- agents/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
- agents/crewai_coding_crew/template/.templateconfig.yaml +12 -0
- agents/crewai_coding_crew/tests/integration/test_agent.py +47 -0
- agents/langgraph_base_react/README.md +9 -0
- agents/langgraph_base_react/app/agent.py +73 -0
- agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
- agents/langgraph_base_react/template/.templateconfig.yaml +13 -0
- agents/langgraph_base_react/tests/integration/test_agent.py +48 -0
- agents/multimodal_live_api/README.md +50 -0
- agents/multimodal_live_api/app/agent.py +86 -0
- agents/multimodal_live_api/app/server.py +193 -0
- agents/multimodal_live_api/app/templates.py +51 -0
- agents/multimodal_live_api/app/vector_store.py +55 -0
- agents/multimodal_live_api/template/.templateconfig.yaml +15 -0
- agents/multimodal_live_api/tests/integration/test_server_e2e.py +254 -0
- agents/multimodal_live_api/tests/load_test/load_test.py +40 -0
- agents/multimodal_live_api/tests/unit/test_server.py +143 -0
- src/base_template/.gitignore +197 -0
- src/base_template/Makefile +37 -0
- src/base_template/README.md +91 -0
- src/base_template/app/utils/tracing.py +143 -0
- src/base_template/app/utils/typing.py +115 -0
- src/base_template/deployment/README.md +123 -0
- src/base_template/deployment/cd/deploy-to-prod.yaml +98 -0
- src/base_template/deployment/cd/staging.yaml +215 -0
- src/base_template/deployment/ci/pr_checks.yaml +51 -0
- src/base_template/deployment/terraform/apis.tf +34 -0
- src/base_template/deployment/terraform/build_triggers.tf +122 -0
- src/base_template/deployment/terraform/dev/apis.tf +42 -0
- src/base_template/deployment/terraform/dev/iam.tf +90 -0
- src/base_template/deployment/terraform/dev/log_sinks.tf +66 -0
- src/base_template/deployment/terraform/dev/providers.tf +29 -0
- src/base_template/deployment/terraform/dev/storage.tf +76 -0
- src/base_template/deployment/terraform/dev/variables.tf +126 -0
- src/base_template/deployment/terraform/dev/vars/env.tfvars +21 -0
- src/base_template/deployment/terraform/iam.tf +130 -0
- src/base_template/deployment/terraform/locals.tf +50 -0
- src/base_template/deployment/terraform/log_sinks.tf +72 -0
- src/base_template/deployment/terraform/providers.tf +35 -0
- src/base_template/deployment/terraform/service_accounts.tf +42 -0
- src/base_template/deployment/terraform/storage.tf +100 -0
- src/base_template/deployment/terraform/variables.tf +202 -0
- src/base_template/deployment/terraform/vars/env.tfvars +43 -0
- src/base_template/pyproject.toml +113 -0
- src/base_template/tests/unit/test_utils/test_tracing_exporter.py +140 -0
- src/cli/commands/create.py +534 -0
- src/cli/commands/setup_cicd.py +730 -0
- src/cli/main.py +35 -0
- src/cli/utils/__init__.py +35 -0
- src/cli/utils/cicd.py +662 -0
- src/cli/utils/gcp.py +120 -0
- src/cli/utils/logging.py +51 -0
- src/cli/utils/template.py +644 -0
- src/data_ingestion/README.md +79 -0
- src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +175 -0
- src/data_ingestion/data_ingestion_pipeline/components/process_data.py +321 -0
- src/data_ingestion/data_ingestion_pipeline/pipeline.py +58 -0
- src/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +184 -0
- src/data_ingestion/pyproject.toml +17 -0
- src/data_ingestion/uv.lock +999 -0
- src/deployment_targets/agent_engine/app/agent_engine_app.py +238 -0
- src/deployment_targets/agent_engine/app/utils/gcs.py +42 -0
- src/deployment_targets/agent_engine/deployment_metadata.json +4 -0
- src/deployment_targets/agent_engine/notebooks/intro_reasoning_engine.ipynb +869 -0
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +120 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/report.html +264 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/results_exceptions.csv +1 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/results_failures.csv +1 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/results_stats.csv +3 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/results_stats_history.csv +22 -0
- src/deployment_targets/agent_engine/tests/load_test/README.md +42 -0
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +100 -0
- src/deployment_targets/agent_engine/tests/unit/test_dummy.py +22 -0
- src/deployment_targets/cloud_run/Dockerfile +29 -0
- src/deployment_targets/cloud_run/app/server.py +128 -0
- src/deployment_targets/cloud_run/deployment/terraform/artifact_registry.tf +22 -0
- src/deployment_targets/cloud_run/deployment/terraform/dev/service_accounts.tf +20 -0
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +192 -0
- src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
- src/deployment_targets/cloud_run/tests/load_test/README.md +79 -0
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +85 -0
- src/deployment_targets/cloud_run/tests/unit/test_server.py +142 -0
- src/deployment_targets/cloud_run/uv.lock +6952 -0
- src/frontends/live_api_react/frontend/package-lock.json +19405 -0
- src/frontends/live_api_react/frontend/package.json +56 -0
- src/frontends/live_api_react/frontend/public/favicon.ico +0 -0
- src/frontends/live_api_react/frontend/public/index.html +62 -0
- src/frontends/live_api_react/frontend/public/robots.txt +3 -0
- src/frontends/live_api_react/frontend/src/App.scss +189 -0
- src/frontends/live_api_react/frontend/src/App.test.tsx +25 -0
- src/frontends/live_api_react/frontend/src/App.tsx +205 -0
- src/frontends/live_api_react/frontend/src/components/audio-pulse/AudioPulse.tsx +64 -0
- src/frontends/live_api_react/frontend/src/components/audio-pulse/audio-pulse.scss +68 -0
- src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +217 -0
- src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +201 -0
- src/frontends/live_api_react/frontend/src/components/logger/Logger.tsx +241 -0
- src/frontends/live_api_react/frontend/src/components/logger/logger.scss +133 -0
- src/frontends/live_api_react/frontend/src/components/logger/mock-logs.ts +151 -0
- src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +161 -0
- src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +285 -0
- src/frontends/live_api_react/frontend/src/contexts/LiveAPIContext.tsx +48 -0
- src/frontends/live_api_react/frontend/src/hooks/use-live-api.ts +115 -0
- src/frontends/live_api_react/frontend/src/hooks/use-media-stream-mux.ts +23 -0
- src/frontends/live_api_react/frontend/src/hooks/use-screen-capture.ts +72 -0
- src/frontends/live_api_react/frontend/src/hooks/use-webcam.ts +69 -0
- src/frontends/live_api_react/frontend/src/index.css +28 -0
- src/frontends/live_api_react/frontend/src/index.tsx +35 -0
- src/frontends/live_api_react/frontend/src/multimodal-live-types.ts +242 -0
- src/frontends/live_api_react/frontend/src/react-app-env.d.ts +17 -0
- src/frontends/live_api_react/frontend/src/reportWebVitals.ts +31 -0
- src/frontends/live_api_react/frontend/src/setupTests.ts +21 -0
- src/frontends/live_api_react/frontend/src/utils/audio-recorder.ts +111 -0
- src/frontends/live_api_react/frontend/src/utils/audio-streamer.ts +270 -0
- src/frontends/live_api_react/frontend/src/utils/audioworklet-registry.ts +43 -0
- src/frontends/live_api_react/frontend/src/utils/multimodal-live-client.ts +329 -0
- src/frontends/live_api_react/frontend/src/utils/store-logger.ts +64 -0
- src/frontends/live_api_react/frontend/src/utils/utils.ts +86 -0
- src/frontends/live_api_react/frontend/src/utils/worklets/audio-processing.ts +73 -0
- src/frontends/live_api_react/frontend/src/utils/worklets/vol-meter.ts +65 -0
- src/frontends/live_api_react/frontend/tsconfig.json +25 -0
- src/frontends/streamlit/frontend/side_bar.py +213 -0
- src/frontends/streamlit/frontend/streamlit_app.py +263 -0
- src/frontends/streamlit/frontend/style/app_markdown.py +37 -0
- src/frontends/streamlit/frontend/utils/chat_utils.py +67 -0
- src/frontends/streamlit/frontend/utils/local_chat_history.py +125 -0
- src/frontends/streamlit/frontend/utils/message_editing.py +59 -0
- src/frontends/streamlit/frontend/utils/multimodal_utils.py +217 -0
- src/frontends/streamlit/frontend/utils/stream_handler.py +282 -0
- src/frontends/streamlit/frontend/utils/title_summary.py +77 -0
- src/resources/containers/data_processing/Dockerfile +25 -0
- src/resources/locks/uv-agentic_rag_vertexai_search-agent_engine.lock +4684 -0
- src/resources/locks/uv-agentic_rag_vertexai_search-cloud_run.lock +5799 -0
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +5509 -0
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +6688 -0
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +4595 -0
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +5710 -0
- src/resources/locks/uv-multimodal_live_api-cloud_run.lock +5665 -0
- src/resources/setup_cicd/cicd_variables.tf +36 -0
- src/resources/setup_cicd/github.tf +85 -0
- src/resources/setup_cicd/providers.tf +39 -0
- src/utils/generate_locks.py +135 -0
- src/utils/lock_utils.py +82 -0
- src/utils/watch_and_rebuild.py +190 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2024 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
18
|
+
// allows you to do things like:
|
|
19
|
+
// expect(element).toHaveTextContent(/react/i)
|
|
20
|
+
// learn more: https://github.com/testing-library/jest-dom
|
|
21
|
+
import "@testing-library/jest-dom";
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2024 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { audioContext } from "./utils";
|
|
18
|
+
import AudioRecordingWorklet from "./worklets/audio-processing";
|
|
19
|
+
import VolMeterWorket from "./worklets/vol-meter";
|
|
20
|
+
|
|
21
|
+
import { createWorketFromSrc } from "./audioworklet-registry";
|
|
22
|
+
import EventEmitter from "eventemitter3";
|
|
23
|
+
|
|
24
|
+
function arrayBufferToBase64(buffer: ArrayBuffer) {
|
|
25
|
+
var binary = "";
|
|
26
|
+
var bytes = new Uint8Array(buffer);
|
|
27
|
+
var len = bytes.byteLength;
|
|
28
|
+
for (var i = 0; i < len; i++) {
|
|
29
|
+
binary += String.fromCharCode(bytes[i]);
|
|
30
|
+
}
|
|
31
|
+
return window.btoa(binary);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class AudioRecorder extends EventEmitter {
|
|
35
|
+
stream: MediaStream | undefined;
|
|
36
|
+
audioContext: AudioContext | undefined;
|
|
37
|
+
source: MediaStreamAudioSourceNode | undefined;
|
|
38
|
+
recording: boolean = false;
|
|
39
|
+
recordingWorklet: AudioWorkletNode | undefined;
|
|
40
|
+
vuWorklet: AudioWorkletNode | undefined;
|
|
41
|
+
|
|
42
|
+
private starting: Promise<void> | null = null;
|
|
43
|
+
|
|
44
|
+
constructor(public sampleRate = 16000) {
|
|
45
|
+
super();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async start() {
|
|
49
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
50
|
+
throw new Error("Could not request user media");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.starting = new Promise(async (resolve, reject) => {
|
|
54
|
+
this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
55
|
+
this.audioContext = await audioContext({ sampleRate: this.sampleRate });
|
|
56
|
+
this.source = this.audioContext.createMediaStreamSource(this.stream);
|
|
57
|
+
|
|
58
|
+
const workletName = "audio-recorder-worklet";
|
|
59
|
+
const src = createWorketFromSrc(workletName, AudioRecordingWorklet);
|
|
60
|
+
|
|
61
|
+
await this.audioContext.audioWorklet.addModule(src);
|
|
62
|
+
this.recordingWorklet = new AudioWorkletNode(
|
|
63
|
+
this.audioContext,
|
|
64
|
+
workletName,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
this.recordingWorklet.port.onmessage = async (ev: MessageEvent) => {
|
|
68
|
+
// worklet processes recording floats and messages converted buffer
|
|
69
|
+
const arrayBuffer = ev.data.data.int16arrayBuffer;
|
|
70
|
+
|
|
71
|
+
if (arrayBuffer) {
|
|
72
|
+
const arrayBufferString = arrayBufferToBase64(arrayBuffer);
|
|
73
|
+
this.emit("data", arrayBufferString);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
this.source.connect(this.recordingWorklet);
|
|
77
|
+
|
|
78
|
+
// vu meter worklet
|
|
79
|
+
const vuWorkletName = "vu-meter";
|
|
80
|
+
await this.audioContext.audioWorklet.addModule(
|
|
81
|
+
createWorketFromSrc(vuWorkletName, VolMeterWorket),
|
|
82
|
+
);
|
|
83
|
+
this.vuWorklet = new AudioWorkletNode(this.audioContext, vuWorkletName);
|
|
84
|
+
this.vuWorklet.port.onmessage = (ev: MessageEvent) => {
|
|
85
|
+
this.emit("volume", ev.data.volume);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
this.source.connect(this.vuWorklet);
|
|
89
|
+
this.recording = true;
|
|
90
|
+
resolve();
|
|
91
|
+
this.starting = null;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
stop() {
|
|
96
|
+
// its plausible that stop would be called before start completes
|
|
97
|
+
// such as if the websocket immediately hangs up
|
|
98
|
+
const handleStop = () => {
|
|
99
|
+
this.source?.disconnect();
|
|
100
|
+
this.stream?.getTracks().forEach((track) => track.stop());
|
|
101
|
+
this.stream = undefined;
|
|
102
|
+
this.recordingWorklet = undefined;
|
|
103
|
+
this.vuWorklet = undefined;
|
|
104
|
+
};
|
|
105
|
+
if (this.starting) {
|
|
106
|
+
this.starting.then(handleStop);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
handleStop();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2024 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
createWorketFromSrc,
|
|
19
|
+
registeredWorklets,
|
|
20
|
+
} from "./audioworklet-registry";
|
|
21
|
+
|
|
22
|
+
export class AudioStreamer {
|
|
23
|
+
public audioQueue: Float32Array[] = [];
|
|
24
|
+
private isPlaying: boolean = false;
|
|
25
|
+
private sampleRate: number = 24000;
|
|
26
|
+
private bufferSize: number = 7680;
|
|
27
|
+
private processingBuffer: Float32Array = new Float32Array(0);
|
|
28
|
+
private scheduledTime: number = 0;
|
|
29
|
+
public gainNode: GainNode;
|
|
30
|
+
public source: AudioBufferSourceNode;
|
|
31
|
+
private isStreamComplete: boolean = false;
|
|
32
|
+
private checkInterval: number | null = null;
|
|
33
|
+
private initialBufferTime: number = 0.1; //0.1 // 100ms initial buffer
|
|
34
|
+
private endOfQueueAudioSource: AudioBufferSourceNode | null = null;
|
|
35
|
+
|
|
36
|
+
public onComplete = () => {};
|
|
37
|
+
|
|
38
|
+
constructor(public context: AudioContext) {
|
|
39
|
+
this.gainNode = this.context.createGain();
|
|
40
|
+
this.source = this.context.createBufferSource();
|
|
41
|
+
this.gainNode.connect(this.context.destination);
|
|
42
|
+
this.addPCM16 = this.addPCM16.bind(this);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async addWorklet<T extends (d: any) => void>(
|
|
46
|
+
workletName: string,
|
|
47
|
+
workletSrc: string,
|
|
48
|
+
handler: T,
|
|
49
|
+
): Promise<this> {
|
|
50
|
+
let workletsRecord = registeredWorklets.get(this.context);
|
|
51
|
+
if (workletsRecord && workletsRecord[workletName]) {
|
|
52
|
+
// the worklet already exists on this context
|
|
53
|
+
// add the new handler to it
|
|
54
|
+
workletsRecord[workletName].handlers.push(handler);
|
|
55
|
+
return Promise.resolve(this);
|
|
56
|
+
//throw new Error(`Worklet ${workletName} already exists on context`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!workletsRecord) {
|
|
60
|
+
registeredWorklets.set(this.context, {});
|
|
61
|
+
workletsRecord = registeredWorklets.get(this.context)!;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// create new record to fill in as becomes available
|
|
65
|
+
workletsRecord[workletName] = { handlers: [handler] };
|
|
66
|
+
|
|
67
|
+
const src = createWorketFromSrc(workletName, workletSrc);
|
|
68
|
+
await this.context.audioWorklet.addModule(src);
|
|
69
|
+
const worklet = new AudioWorkletNode(this.context, workletName);
|
|
70
|
+
|
|
71
|
+
//add the node into the map
|
|
72
|
+
workletsRecord[workletName].node = worklet;
|
|
73
|
+
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
addPCM16(chunk: Uint8Array) {
|
|
78
|
+
const float32Array = new Float32Array(chunk.length / 2);
|
|
79
|
+
const dataView = new DataView(chunk.buffer);
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < chunk.length / 2; i++) {
|
|
82
|
+
try {
|
|
83
|
+
const int16 = dataView.getInt16(i * 2, true);
|
|
84
|
+
float32Array[i] = int16 / 32768;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error(e);
|
|
87
|
+
// console.log(
|
|
88
|
+
// `dataView.length: ${dataView.byteLength}, i * 2: ${i * 2}`,
|
|
89
|
+
// );
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const newBuffer = new Float32Array(
|
|
94
|
+
this.processingBuffer.length + float32Array.length,
|
|
95
|
+
);
|
|
96
|
+
newBuffer.set(this.processingBuffer);
|
|
97
|
+
newBuffer.set(float32Array, this.processingBuffer.length);
|
|
98
|
+
this.processingBuffer = newBuffer;
|
|
99
|
+
|
|
100
|
+
while (this.processingBuffer.length >= this.bufferSize) {
|
|
101
|
+
const buffer = this.processingBuffer.slice(0, this.bufferSize);
|
|
102
|
+
this.audioQueue.push(buffer);
|
|
103
|
+
this.processingBuffer = this.processingBuffer.slice(this.bufferSize);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!this.isPlaying) {
|
|
107
|
+
this.isPlaying = true;
|
|
108
|
+
// Initialize scheduledTime only when we start playing
|
|
109
|
+
this.scheduledTime = this.context.currentTime + this.initialBufferTime;
|
|
110
|
+
this.scheduleNextBuffer();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private createAudioBuffer(audioData: Float32Array): AudioBuffer {
|
|
115
|
+
const audioBuffer = this.context.createBuffer(
|
|
116
|
+
1,
|
|
117
|
+
audioData.length,
|
|
118
|
+
this.sampleRate,
|
|
119
|
+
);
|
|
120
|
+
audioBuffer.getChannelData(0).set(audioData);
|
|
121
|
+
return audioBuffer;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private scheduleNextBuffer() {
|
|
125
|
+
const SCHEDULE_AHEAD_TIME = 0.2;
|
|
126
|
+
|
|
127
|
+
while (
|
|
128
|
+
this.audioQueue.length > 0 &&
|
|
129
|
+
this.scheduledTime < this.context.currentTime + SCHEDULE_AHEAD_TIME
|
|
130
|
+
) {
|
|
131
|
+
const audioData = this.audioQueue.shift()!;
|
|
132
|
+
const audioBuffer = this.createAudioBuffer(audioData);
|
|
133
|
+
const source = this.context.createBufferSource();
|
|
134
|
+
|
|
135
|
+
if (this.audioQueue.length === 0) {
|
|
136
|
+
if (this.endOfQueueAudioSource) {
|
|
137
|
+
this.endOfQueueAudioSource.onended = null;
|
|
138
|
+
}
|
|
139
|
+
this.endOfQueueAudioSource = source;
|
|
140
|
+
source.onended = () => {
|
|
141
|
+
if (
|
|
142
|
+
!this.audioQueue.length &&
|
|
143
|
+
this.endOfQueueAudioSource === source
|
|
144
|
+
) {
|
|
145
|
+
this.endOfQueueAudioSource = null;
|
|
146
|
+
this.onComplete();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
source.buffer = audioBuffer;
|
|
152
|
+
source.connect(this.gainNode);
|
|
153
|
+
|
|
154
|
+
const worklets = registeredWorklets.get(this.context);
|
|
155
|
+
|
|
156
|
+
if (worklets) {
|
|
157
|
+
Object.entries(worklets).forEach(([workletName, graph]) => {
|
|
158
|
+
const { node, handlers } = graph;
|
|
159
|
+
if (node) {
|
|
160
|
+
source.connect(node);
|
|
161
|
+
node.port.onmessage = function (ev: MessageEvent) {
|
|
162
|
+
handlers.forEach((handler) => {
|
|
163
|
+
handler.call(node.port, ev);
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
node.connect(this.context.destination);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// i added this trying to fix clicks
|
|
172
|
+
// this.gainNode.gain.setValueAtTime(0, 0);
|
|
173
|
+
// this.gainNode.gain.linearRampToValueAtTime(1, 1);
|
|
174
|
+
|
|
175
|
+
// Ensure we never schedule in the past
|
|
176
|
+
const startTime = Math.max(this.scheduledTime, this.context.currentTime);
|
|
177
|
+
source.start(startTime);
|
|
178
|
+
|
|
179
|
+
this.scheduledTime = startTime + audioBuffer.duration;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.audioQueue.length === 0 && this.processingBuffer.length === 0) {
|
|
183
|
+
if (this.isStreamComplete) {
|
|
184
|
+
this.isPlaying = false;
|
|
185
|
+
if (this.checkInterval) {
|
|
186
|
+
clearInterval(this.checkInterval);
|
|
187
|
+
this.checkInterval = null;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
if (!this.checkInterval) {
|
|
191
|
+
this.checkInterval = window.setInterval(() => {
|
|
192
|
+
if (
|
|
193
|
+
this.audioQueue.length > 0 ||
|
|
194
|
+
this.processingBuffer.length >= this.bufferSize
|
|
195
|
+
) {
|
|
196
|
+
this.scheduleNextBuffer();
|
|
197
|
+
}
|
|
198
|
+
}, 100) as unknown as number;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
const nextCheckTime =
|
|
203
|
+
(this.scheduledTime - this.context.currentTime) * 1000;
|
|
204
|
+
setTimeout(
|
|
205
|
+
() => this.scheduleNextBuffer(),
|
|
206
|
+
Math.max(0, nextCheckTime - 50),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
stop() {
|
|
212
|
+
this.isPlaying = false;
|
|
213
|
+
this.isStreamComplete = true;
|
|
214
|
+
this.audioQueue = [];
|
|
215
|
+
this.processingBuffer = new Float32Array(0);
|
|
216
|
+
this.scheduledTime = this.context.currentTime;
|
|
217
|
+
|
|
218
|
+
if (this.checkInterval) {
|
|
219
|
+
clearInterval(this.checkInterval);
|
|
220
|
+
this.checkInterval = null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.gainNode.gain.linearRampToValueAtTime(
|
|
224
|
+
0,
|
|
225
|
+
this.context.currentTime + 0.1,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
this.gainNode.disconnect();
|
|
230
|
+
this.gainNode = this.context.createGain();
|
|
231
|
+
this.gainNode.connect(this.context.destination);
|
|
232
|
+
}, 200);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async resume() {
|
|
236
|
+
if (this.context.state === "suspended") {
|
|
237
|
+
await this.context.resume();
|
|
238
|
+
}
|
|
239
|
+
this.isStreamComplete = false;
|
|
240
|
+
this.scheduledTime = this.context.currentTime + this.initialBufferTime;
|
|
241
|
+
this.gainNode.gain.setValueAtTime(1, this.context.currentTime);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
complete() {
|
|
245
|
+
this.isStreamComplete = true;
|
|
246
|
+
if (this.processingBuffer.length > 0) {
|
|
247
|
+
this.audioQueue.push(this.processingBuffer);
|
|
248
|
+
this.processingBuffer = new Float32Array(0);
|
|
249
|
+
if (this.isPlaying) {
|
|
250
|
+
this.scheduleNextBuffer();
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
this.onComplete();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// // Usage example:
|
|
259
|
+
// const audioStreamer = new AudioStreamer();
|
|
260
|
+
//
|
|
261
|
+
// // In your streaming code:
|
|
262
|
+
// function handleChunk(chunk: Uint8Array) {
|
|
263
|
+
// audioStreamer.handleChunk(chunk);
|
|
264
|
+
// }
|
|
265
|
+
//
|
|
266
|
+
// // To start playing (call this in response to a user interaction)
|
|
267
|
+
// await audioStreamer.resume();
|
|
268
|
+
//
|
|
269
|
+
// // To stop playing
|
|
270
|
+
// // audioStreamer.stop();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2024 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A registry to map attached worklets by their audio-context
|
|
19
|
+
* any module using `audioContext.audioWorklet.addModule(` should register the worklet here
|
|
20
|
+
*/
|
|
21
|
+
export type WorkletGraph = {
|
|
22
|
+
node?: AudioWorkletNode;
|
|
23
|
+
handlers: Array<(this: MessagePort, ev: MessageEvent) => any>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const registeredWorklets: Map<
|
|
27
|
+
AudioContext,
|
|
28
|
+
Record<string, WorkletGraph>
|
|
29
|
+
> = new Map();
|
|
30
|
+
|
|
31
|
+
export const createWorketFromSrc = (
|
|
32
|
+
workletName: string,
|
|
33
|
+
workletSrc: string,
|
|
34
|
+
) => {
|
|
35
|
+
const script = new Blob(
|
|
36
|
+
[`registerProcessor("${workletName}", ${workletSrc})`],
|
|
37
|
+
{
|
|
38
|
+
type: "application/javascript",
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return URL.createObjectURL(script);
|
|
43
|
+
};
|