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.

Files changed (162) hide show
  1. agent_starter_pack-0.0.1b0.dist-info/METADATA +143 -0
  2. agent_starter_pack-0.0.1b0.dist-info/RECORD +162 -0
  3. agent_starter_pack-0.0.1b0.dist-info/WHEEL +4 -0
  4. agent_starter_pack-0.0.1b0.dist-info/entry_points.txt +2 -0
  5. agent_starter_pack-0.0.1b0.dist-info/licenses/LICENSE +201 -0
  6. agents/agentic_rag_vertexai_search/README.md +22 -0
  7. agents/agentic_rag_vertexai_search/app/agent.py +145 -0
  8. agents/agentic_rag_vertexai_search/app/retrievers.py +79 -0
  9. agents/agentic_rag_vertexai_search/app/templates.py +53 -0
  10. agents/agentic_rag_vertexai_search/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
  11. agents/agentic_rag_vertexai_search/template/.templateconfig.yaml +14 -0
  12. agents/agentic_rag_vertexai_search/tests/integration/test_agent.py +57 -0
  13. agents/crewai_coding_crew/README.md +34 -0
  14. agents/crewai_coding_crew/app/agent.py +86 -0
  15. agents/crewai_coding_crew/app/crew/config/agents.yaml +39 -0
  16. agents/crewai_coding_crew/app/crew/config/tasks.yaml +37 -0
  17. agents/crewai_coding_crew/app/crew/crew.py +71 -0
  18. agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +1571 -0
  19. agents/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
  20. agents/crewai_coding_crew/template/.templateconfig.yaml +12 -0
  21. agents/crewai_coding_crew/tests/integration/test_agent.py +47 -0
  22. agents/langgraph_base_react/README.md +9 -0
  23. agents/langgraph_base_react/app/agent.py +73 -0
  24. agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
  25. agents/langgraph_base_react/template/.templateconfig.yaml +13 -0
  26. agents/langgraph_base_react/tests/integration/test_agent.py +48 -0
  27. agents/multimodal_live_api/README.md +50 -0
  28. agents/multimodal_live_api/app/agent.py +86 -0
  29. agents/multimodal_live_api/app/server.py +193 -0
  30. agents/multimodal_live_api/app/templates.py +51 -0
  31. agents/multimodal_live_api/app/vector_store.py +55 -0
  32. agents/multimodal_live_api/template/.templateconfig.yaml +15 -0
  33. agents/multimodal_live_api/tests/integration/test_server_e2e.py +254 -0
  34. agents/multimodal_live_api/tests/load_test/load_test.py +40 -0
  35. agents/multimodal_live_api/tests/unit/test_server.py +143 -0
  36. src/base_template/.gitignore +197 -0
  37. src/base_template/Makefile +37 -0
  38. src/base_template/README.md +91 -0
  39. src/base_template/app/utils/tracing.py +143 -0
  40. src/base_template/app/utils/typing.py +115 -0
  41. src/base_template/deployment/README.md +123 -0
  42. src/base_template/deployment/cd/deploy-to-prod.yaml +98 -0
  43. src/base_template/deployment/cd/staging.yaml +215 -0
  44. src/base_template/deployment/ci/pr_checks.yaml +51 -0
  45. src/base_template/deployment/terraform/apis.tf +34 -0
  46. src/base_template/deployment/terraform/build_triggers.tf +122 -0
  47. src/base_template/deployment/terraform/dev/apis.tf +42 -0
  48. src/base_template/deployment/terraform/dev/iam.tf +90 -0
  49. src/base_template/deployment/terraform/dev/log_sinks.tf +66 -0
  50. src/base_template/deployment/terraform/dev/providers.tf +29 -0
  51. src/base_template/deployment/terraform/dev/storage.tf +76 -0
  52. src/base_template/deployment/terraform/dev/variables.tf +126 -0
  53. src/base_template/deployment/terraform/dev/vars/env.tfvars +21 -0
  54. src/base_template/deployment/terraform/iam.tf +130 -0
  55. src/base_template/deployment/terraform/locals.tf +50 -0
  56. src/base_template/deployment/terraform/log_sinks.tf +72 -0
  57. src/base_template/deployment/terraform/providers.tf +35 -0
  58. src/base_template/deployment/terraform/service_accounts.tf +42 -0
  59. src/base_template/deployment/terraform/storage.tf +100 -0
  60. src/base_template/deployment/terraform/variables.tf +202 -0
  61. src/base_template/deployment/terraform/vars/env.tfvars +43 -0
  62. src/base_template/pyproject.toml +113 -0
  63. src/base_template/tests/unit/test_utils/test_tracing_exporter.py +140 -0
  64. src/cli/commands/create.py +534 -0
  65. src/cli/commands/setup_cicd.py +730 -0
  66. src/cli/main.py +35 -0
  67. src/cli/utils/__init__.py +35 -0
  68. src/cli/utils/cicd.py +662 -0
  69. src/cli/utils/gcp.py +120 -0
  70. src/cli/utils/logging.py +51 -0
  71. src/cli/utils/template.py +644 -0
  72. src/data_ingestion/README.md +79 -0
  73. src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +175 -0
  74. src/data_ingestion/data_ingestion_pipeline/components/process_data.py +321 -0
  75. src/data_ingestion/data_ingestion_pipeline/pipeline.py +58 -0
  76. src/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +184 -0
  77. src/data_ingestion/pyproject.toml +17 -0
  78. src/data_ingestion/uv.lock +999 -0
  79. src/deployment_targets/agent_engine/app/agent_engine_app.py +238 -0
  80. src/deployment_targets/agent_engine/app/utils/gcs.py +42 -0
  81. src/deployment_targets/agent_engine/deployment_metadata.json +4 -0
  82. src/deployment_targets/agent_engine/notebooks/intro_reasoning_engine.ipynb +869 -0
  83. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +120 -0
  84. src/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
  85. src/deployment_targets/agent_engine/tests/load_test/.results/report.html +264 -0
  86. src/deployment_targets/agent_engine/tests/load_test/.results/results_exceptions.csv +1 -0
  87. src/deployment_targets/agent_engine/tests/load_test/.results/results_failures.csv +1 -0
  88. src/deployment_targets/agent_engine/tests/load_test/.results/results_stats.csv +3 -0
  89. src/deployment_targets/agent_engine/tests/load_test/.results/results_stats_history.csv +22 -0
  90. src/deployment_targets/agent_engine/tests/load_test/README.md +42 -0
  91. src/deployment_targets/agent_engine/tests/load_test/load_test.py +100 -0
  92. src/deployment_targets/agent_engine/tests/unit/test_dummy.py +22 -0
  93. src/deployment_targets/cloud_run/Dockerfile +29 -0
  94. src/deployment_targets/cloud_run/app/server.py +128 -0
  95. src/deployment_targets/cloud_run/deployment/terraform/artifact_registry.tf +22 -0
  96. src/deployment_targets/cloud_run/deployment/terraform/dev/service_accounts.tf +20 -0
  97. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +192 -0
  98. src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
  99. src/deployment_targets/cloud_run/tests/load_test/README.md +79 -0
  100. src/deployment_targets/cloud_run/tests/load_test/load_test.py +85 -0
  101. src/deployment_targets/cloud_run/tests/unit/test_server.py +142 -0
  102. src/deployment_targets/cloud_run/uv.lock +6952 -0
  103. src/frontends/live_api_react/frontend/package-lock.json +19405 -0
  104. src/frontends/live_api_react/frontend/package.json +56 -0
  105. src/frontends/live_api_react/frontend/public/favicon.ico +0 -0
  106. src/frontends/live_api_react/frontend/public/index.html +62 -0
  107. src/frontends/live_api_react/frontend/public/robots.txt +3 -0
  108. src/frontends/live_api_react/frontend/src/App.scss +189 -0
  109. src/frontends/live_api_react/frontend/src/App.test.tsx +25 -0
  110. src/frontends/live_api_react/frontend/src/App.tsx +205 -0
  111. src/frontends/live_api_react/frontend/src/components/audio-pulse/AudioPulse.tsx +64 -0
  112. src/frontends/live_api_react/frontend/src/components/audio-pulse/audio-pulse.scss +68 -0
  113. src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +217 -0
  114. src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +201 -0
  115. src/frontends/live_api_react/frontend/src/components/logger/Logger.tsx +241 -0
  116. src/frontends/live_api_react/frontend/src/components/logger/logger.scss +133 -0
  117. src/frontends/live_api_react/frontend/src/components/logger/mock-logs.ts +151 -0
  118. src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +161 -0
  119. src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +285 -0
  120. src/frontends/live_api_react/frontend/src/contexts/LiveAPIContext.tsx +48 -0
  121. src/frontends/live_api_react/frontend/src/hooks/use-live-api.ts +115 -0
  122. src/frontends/live_api_react/frontend/src/hooks/use-media-stream-mux.ts +23 -0
  123. src/frontends/live_api_react/frontend/src/hooks/use-screen-capture.ts +72 -0
  124. src/frontends/live_api_react/frontend/src/hooks/use-webcam.ts +69 -0
  125. src/frontends/live_api_react/frontend/src/index.css +28 -0
  126. src/frontends/live_api_react/frontend/src/index.tsx +35 -0
  127. src/frontends/live_api_react/frontend/src/multimodal-live-types.ts +242 -0
  128. src/frontends/live_api_react/frontend/src/react-app-env.d.ts +17 -0
  129. src/frontends/live_api_react/frontend/src/reportWebVitals.ts +31 -0
  130. src/frontends/live_api_react/frontend/src/setupTests.ts +21 -0
  131. src/frontends/live_api_react/frontend/src/utils/audio-recorder.ts +111 -0
  132. src/frontends/live_api_react/frontend/src/utils/audio-streamer.ts +270 -0
  133. src/frontends/live_api_react/frontend/src/utils/audioworklet-registry.ts +43 -0
  134. src/frontends/live_api_react/frontend/src/utils/multimodal-live-client.ts +329 -0
  135. src/frontends/live_api_react/frontend/src/utils/store-logger.ts +64 -0
  136. src/frontends/live_api_react/frontend/src/utils/utils.ts +86 -0
  137. src/frontends/live_api_react/frontend/src/utils/worklets/audio-processing.ts +73 -0
  138. src/frontends/live_api_react/frontend/src/utils/worklets/vol-meter.ts +65 -0
  139. src/frontends/live_api_react/frontend/tsconfig.json +25 -0
  140. src/frontends/streamlit/frontend/side_bar.py +213 -0
  141. src/frontends/streamlit/frontend/streamlit_app.py +263 -0
  142. src/frontends/streamlit/frontend/style/app_markdown.py +37 -0
  143. src/frontends/streamlit/frontend/utils/chat_utils.py +67 -0
  144. src/frontends/streamlit/frontend/utils/local_chat_history.py +125 -0
  145. src/frontends/streamlit/frontend/utils/message_editing.py +59 -0
  146. src/frontends/streamlit/frontend/utils/multimodal_utils.py +217 -0
  147. src/frontends/streamlit/frontend/utils/stream_handler.py +282 -0
  148. src/frontends/streamlit/frontend/utils/title_summary.py +77 -0
  149. src/resources/containers/data_processing/Dockerfile +25 -0
  150. src/resources/locks/uv-agentic_rag_vertexai_search-agent_engine.lock +4684 -0
  151. src/resources/locks/uv-agentic_rag_vertexai_search-cloud_run.lock +5799 -0
  152. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +5509 -0
  153. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +6688 -0
  154. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +4595 -0
  155. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +5710 -0
  156. src/resources/locks/uv-multimodal_live_api-cloud_run.lock +5665 -0
  157. src/resources/setup_cicd/cicd_variables.tf +36 -0
  158. src/resources/setup_cicd/github.tf +85 -0
  159. src/resources/setup_cicd/providers.tf +39 -0
  160. src/utils/generate_locks.py +135 -0
  161. src/utils/lock_utils.py +82 -0
  162. 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
+ };